Android 使用 RecyclerView 实现轮播图

850 阅读4分钟

一、前言

之前一篇博客使用 ViewPager 实现轮播图《Android ViewPager 实现循环轮播图》,但是 ViewPager 有个天生的缺陷是 View 无法重用,此外 ViewPager 的滑动过程会频繁 requestLayout,尽管可以通过 addViewInLayout 和 removeViewInLayout 配合 PagerAdapter 的 startUpdate 和 finishUpdate 可以减少重绘,但在 ListView 和 RecyclerView 中仍然达不到最好的效果。因此,使用一种新的方式十分必要。

二、代码实现

我们这里主要需要处理几个代码

2.1 onTouch事件处理

第一个是事件拦截和处理,手指按下时停止播放,手指抬起时恢复播放

    @Override
    public boolean onTouchEvent(MotionEvent e) {

        int action = e.getAction();
        if(action==MotionEvent.ACTION_DOWN){
            lastIsPlayState = isPlaying;
            if(lastIsPlayState){
                stopPlay();
            }
       
        }else if(action==MotionEvent.ACTION_UP || action==MotionEvent.ACTION_CANCEL){
            if(lastIsPlayState){
                startPlay();
            }
        }

        return super.onTouchEvent(e);
    }

2.2 fling事件处理

fling可以理解为瞬间滑动,其中核心的两个因素是velocityX和velocityY, 用来测算可以匀减速运动多少距离。

为什么要处理这个事件呢?主要还是担心一下滑动超过一个页面,主要操作为

滑动慢的时候判断是否超过了屏幕一半,超过的情况滑动到下一个Page,否者返回去
滑动很快的话直接到下个页面

另外,Flinger事件被拦截,那就意味着RecyclerView中的ViewFlinger不能处理Fling效果。

2.3 SRCOLL_STATE_IDEL 状态处理

偶然会遇到微小的滑动,但是这个我们在onTouch中处理可能影响RecyclerView的一些内部机制,因此需要对SCROLL_SATTE_IDEL 做补偿处理,防止出现IDLE状态后页面没有正确归位。

2.4 全部代码

RecyclerPagerView

public class RecyclerPagerView extends RecyclerView implements Handler.Callback {

    private static final long TASK_TIMEOUT = 3000;
    public OnPageChangeListener onPageChangeListener;

    private final Handler mRecyclerHandler;
    private final int MSG_PLAY_NEXT  = 112233;
    private volatile boolean isPlaying = false;
    private boolean lastIsPlayState = false;
    private int realPosition = -1;

    public RecyclerPagerView(Context context) {
        this(context,null);
    }

    public RecyclerPagerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RecyclerPagerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mRecyclerHandler = new Handler(Looper.getMainLooper(),this);
    }

    public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
        this.onPageChangeListener = onPageChangeListener;
        if(this.onPageChangeListener!=null){
            addOnScrollListener(this.onPageChangeListener);
            int currentItem = getCurrentItem();
            this.onPageChangeListener.onPageSelection(currentItem);
        }
    }

    public int getCurrentItem(){
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
        return linearLayoutManager.findFirstVisibleItemPosition();
    }

    public void setCurrentItem(int position,boolean isAnimate){
        Adapter adapter = getAdapter();
        if(adapter==null || adapter.getItemCount()<=position){
            return;
        }
        if(!isAnimate)
        {
            scrollToPosition(position);
        }else {
            smoothScrollToPosition(position);
        }
    }
    public void setCurrentItem(int position ){
       setCurrentItem(position,true);
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {

        int action = e.getAction();
        if(action==MotionEvent.ACTION_DOWN){
            lastIsPlayState = isPlaying;
            if(lastIsPlayState){
                stopPlay();
            }
       
        }else if(action==MotionEvent.ACTION_UP || action==MotionEvent.ACTION_CANCEL){
            if(lastIsPlayState){
                startPlay();
            }
        }

        return super.onTouchEvent(e);
    }

    @Override
    public boolean fling(int velocityX, int velocityY) {

        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();

        int screenWidth = getWidth();

        // views on the screen
        int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
        View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
        int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
        View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);

        // distance we need to scroll
        int leftMargin = (screenWidth - lastView.getWidth()) / 2;
        int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
        int leftEdge = lastView.getLeft();
        int rightEdge = firstView.getRight();
        int scrollDistanceLeft = leftEdge - leftMargin;
        int scrollDistanceRight = rightMargin - rightEdge;

        int targetPosition;

        if (Math.abs(velocityX) < 1500) {
            // 滑动慢的时候判断是否超过了屏幕一半,超过的情况滑动到下一个Page,否者返回去

            if (leftEdge > screenWidth / 2) {
                // go to next page
                smoothScrollBy(-scrollDistanceRight, 0);
                targetPosition = firstVisibleItemPosition;

            } else if (rightEdge < screenWidth / 2) {
                // go to next page
                smoothScrollBy(scrollDistanceLeft, 0);
                targetPosition = firstVisibleItemPosition+1;
            } else {
                // stay at current page
                if (velocityX > 0) {
                    smoothScrollBy(-scrollDistanceRight, 0);
                } else {
                    smoothScrollBy(scrollDistanceLeft, 0);
                }
                targetPosition = firstVisibleItemPosition;
            }
        } else {
            // 滑动很快的话直接到下个页面

            if (velocityX > 0) {
                smoothScrollBy(scrollDistanceLeft, 0);
                targetPosition = firstVisibleItemPosition+1;
            } else {
                smoothScrollBy(-scrollDistanceRight, 0);
                targetPosition = firstVisibleItemPosition;
            }

        }

        Log.e("RecyclerPagerView","nextPage="+targetPosition);
        if(this.onPageChangeListener!=null){
            realPosition = targetPosition;
            this.onPageChangeListener.onPageSelection(targetPosition);
        }
        return true;
    }



    @Override
    public void onScrollStateChanged(final  int state) {
        super.onScrollStateChanged(state);

        if (state == SCROLL_STATE_IDLE) {

            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();

            int screenWidth = getWidth();

            int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
            View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
            int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
            View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);

            // distance we need to scroll
            int leftMargin = (screenWidth - lastView.getWidth()) / 2;
            int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
            int leftEdge = lastView.getLeft();
            int rightEdge = firstView.getRight();
            int scrollDistanceLeft = leftEdge - leftMargin;
            int scrollDistanceRight = rightMargin - rightEdge;
            int  targetPosition = -1;
            if (leftEdge > screenWidth / 2) {
                smoothScrollBy(-scrollDistanceRight, 0);
                targetPosition = firstVisibleItemPosition+1;
            } else if (rightEdge < screenWidth / 2) {
                smoothScrollBy(scrollDistanceLeft, 0);
                targetPosition = lastVisibleItemPosition;
            }else{
                targetPosition = firstVisibleItemPosition;
            }
            if(this.onPageChangeListener!=null){
                realPosition = targetPosition;
                this.onPageChangeListener.onPageSelection(targetPosition);
            }
        }

    }

    @Override
    public boolean handleMessage(Message msg) {
        int what = msg.what;
        switch (what){
            case MSG_PLAY_NEXT:
                showNextPage();
                break;
        }

        return false;
    }

    private void showNextPage() {
        if(!isPlaying){
            return;
        }
        if(!canRecyclePlaying()){
            isPlaying = false;
            return;
        }
        Adapter adapter = getAdapter();
        int currentItem = getCurrentItem();
        if(adapter!=null && adapter.getItemCount()>0) {
            if (currentItem == NO_POSITION  ) {
                setCurrentItem(0);
            }else {
                setCurrentItem(currentItem+1);
            }
        }

        mRecyclerHandler.sendEmptyMessageDelayed(MSG_PLAY_NEXT,TASK_TIMEOUT);
    }

    public void startPlay(){
        if(isPlaying){
            stopPlay();
        }
        if (!canRecyclePlaying()){
            isPlaying = false;
            return;
        }

        isPlaying = true;
        mRecyclerHandler.sendEmptyMessageDelayed(MSG_PLAY_NEXT,TASK_TIMEOUT);
    }

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);

        if(canRecyclePlaying()){
            if(realPosition==-1){
                realPosition = 1000;
                if(adapter.getItemCount()==3000){
                    realPosition = realPosition - 1;
                }
            }
            setCurrentItem(realPosition,false);
        }
    }

    private boolean canRecyclePlaying() {
        Adapter adapter = getAdapter();
        if(adapter==null || adapter.getItemCount()<1) return false;
        return true;
    }

    private void stopPlay() {
        isPlaying = false;
        mRecyclerHandler.removeMessages(MSG_PLAY_NEXT);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if(lastIsPlayState){
            startPlay();
        }

    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        lastIsPlayState = isPlaying;
        stopPlay();
    }

    public static    abstract class OnPageChangeListener extends RecyclerView.OnScrollListener{
            public abstract  void onPageSelection(int position);
    }

}

Adapter+Holder 适配器

该类的作用主要用于约束和快速接入

public class QuickViewHolder extends RecyclerView.ViewHolder{
    private SparseArray<View> mViews;
    private View mConvertView;

    private QuickViewHolder(View v){
        super(v);
        mConvertView = v;
        mViews = new SparseArray<>();
    }

    public static QuickViewHolder get(ViewGroup parent, int layoutId){
        View convertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
        return new QuickViewHolder(convertView);
    }

    public <T extends View> T getView(int id){
        View v = mViews.get(id);
        if(v == null){
            v = mConvertView.findViewById(id);
            mViews.put(id, v);
        }
        return (T)v;
    }


}

BannerAdapter 实现,该类继承 QuickAdapter

 public static abstract class BaseQuickAdapter<VH extends  QuickViewHolder,DM extends Serializable> extends RecyclerPagerView.Adapter{

        private List<DM> dataset;

        public BaseQuickAdapter(List<DM> dataset){
            this.dataset = dataset;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return QuickViewHolder.get(parent,getLayoutId(viewType));
        }


        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            convert((VH)holder,getItem(position),position);
        }

        public  abstract  void convert(VH holder, DM dataModel, int position);

        public abstract int getLayoutId(int viewType);

        public DM getItem(int position) {
            if(dataset==null) return null;
            if(position<0 || position>=getItemCount()) {
                return null;
            }
            return dataset.get(position%dataset.size());
        }

        @Override
        public int getItemCount() {
            if(dataset==null || dataset.isEmpty()) return 0;
            return dataset.size()*1000;
        }
    }

    public static class BannerAdapter extends BaseQuickAdapter {


        public BannerAdapter(List<Item> dataset) {
            super(dataset);
        }

        @Override
        public void convert(QuickViewHolder holder, Serializable dataModel, int position) {
            if(!(dataModel instanceof Item)) return;
            TextView textView = holder.getView(R.id.text);
            textView.setText(((Item) dataModel).getTitle());
            ImageView imageView = holder.getView(R.id.image);
            imageView.setImageDrawable(((Item) dataModel).getImageResource());
        }

        @Override
        public int getLayoutId(int viewType) {
            return R.layout.item_banner;
        }


    }

三、使用

使用过程难度不大,依照我们实现的接口

3.1 监听器

这里的监听器主要坚挺滑动到哪个位置了,方便获取当前卡片

    public static class  PagerChangeListener extends RecyclerPagerView.OnPageChangeListener {

        private TextView tipTextView;
        private int size;

        public PagerChangeListener(TextView tipTextView,int size) {
            this.tipTextView = tipTextView;
            this.size = size;
        }

        @Override
        public void onPageSelection(int position) {
            tipTextView.setText((position%size+1)+"/"+size);
        }
    }

2.1 数据集,展示卡片

public static class Item implements Serializable {
         Drawable drawable;
         String title;

        public Item(int color, String s) {
            this.drawable = new ColorDrawable(color);
            this.title = s;
        }

        public Drawable getImageResource() {
            return drawable;
        }

        public String getTitle() {
            return title;
        }


        public static List<Item>  getSampleData(){

            int[] colors = {
                    	0xffDC143C,
                        0xff00BFFF,
                        0xffA52A2A

            };
            List<Item> lst = new ArrayList<>();
            for (int i=0;i<colors.length;i++) {
                Item item =  new Item(colors[i%colors.length],"第"+(i%colors.length+1)+"张图");
                lst.add(item);
            }

            return lst;
        }
    }

四、总结

我们实现了很多RecyclerView相关的东西,基本上都是内部处理,因此,后续的文章可能会重点转向LayoutManager,就目前而言,RecyclerView相比约束布局来说,还不是全家桶,没有太臃肿的逻辑,总体上感官不差,