带你手写一个简单的RecycleView

RecyclerView的用法

RecyclerView一般作为Android显示列表的控件,有诸多优异的性能。
1)回收池策略能加载上亿级数据不发生卡顿,
2)适配器模式能展示任意显示需求

RecyclerView架构中核心组件

1、回收池:能回收任意Item控件,并返回符合类型的Item控件; 比如onBinderViewHolder方法中的第一个参数是从回收池中返回的
2、适配器: Adapter接口,经常辅助RecyclerView实现列表展示; 适配器模式将用户界面展示与交互分离
3、RecyclerView: 是做触摸事件的交互,主要实现边界值判断;
根据用户的触摸反馈,协调回收池对象与适配器对象之间的工作。

RecyclerView架构在工作中的体现

传送带的工作机制

上货 传动 到达 新增
传送带的始端空出位置 将货物放入传送带 传送带开始传动 货物到达传送带终点,将新的货物放入传送带

RecyclerView架构实现

加载 滑动 滑出 加载
加载第一屏数据 用户手指开始滑动 用户将不需要的,信息划出屏幕 幕底端新增用户,需要看到的数据

RecyclerView的架构思考

架构:充分利用传送带原理,只有用户看到的数据才会加载到内存,看不到的在等待被加载。传送带能够源源不断地传送亿级货物,RecyclerView也能够显示加载亿级ltem。
传送带的工作机制可以比作生产者与消费者模式

回收池的回收策略

在这里插入图片描述

回收池的填充策略

在这里插入图片描述

回收池的设计

在这里插入图片描述

存和取是回收池策略必须实现的

在这里插入图片描述

需要重写的方法

  • onMeasure
  • onLayout
  • onInterceptTouchEvent
  • onTouchEvent
  • scrollBy

参考代码

/**
 * 1 List<View> viewList
 * 缓存已经加载到屏幕上的View这些View不存在回收池中,
 * 需要用集合表示,方便后续查找和移除
 * 2 nt currentY:记录在Y轴 上滑动的距离
 * 3 int rowCount:记录在RecyclerView中加载的总数据条数
 * 4 int firstRow;记录在屏幕中第一个View在数据中的位置,比如当前是第34个元
 * 素在屏幕的第一个位置
 * 5 Recycler recycler:持有一个回收池的引用
 * 6 int scrollY: RecyclerView中 第一个View的左上顶点离屏幕的距离
 */
public class RecyclerView extends ViewGroup {
    
    
    private Adapter adapter;
    //当前显示的View
    private List<View> viewList;
    //当前滑动的y值
    private int currentY;
    //行数
    private int rowCount;
    //view的第一行  是占内容的几行
    private int firstRow;
    //y偏移量
    private int scrollY;
    //初始化  第一屏最慢
    private boolean needRelayout;
    private int width;

    private int height;
    private int[] heights;//item  高度
    Recycler recycler;
    //最小滑动距离
    private int touchSlop;

    public Adapter getAdapter() {
    
    
        return adapter;
    }

    public void setAdapter(Adapter adapter) {
    
    
        this.adapter = adapter;
        if (adapter != null) {
    
    
            //回收池初始化
            recycler = new Recycler(adapter.getViewTypeCount());
            scrollY = 0;
            firstRow = 0;
            needRelayout = true;
            requestLayout();//1  onMeasure   2  onLayout
        }
    }

    public RecyclerView(Context context, AttributeSet attrs) {
    
    
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
    
    
        ViewConfiguration configuration = ViewConfiguration.get(context);
        this.touchSlop = configuration.getScaledTouchSlop();
        this.viewList = new ArrayList<>();
        this.needRelayout = true;
    }

    //初始化
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
    
        if (needRelayout || changed) {
    
    
            needRelayout = false;

            viewList.clear();
            removeAllViews();
            if (adapter != null) {
    
    
                //               摆放
                width = r - l;
                height = b - t;
                int left, top = 0, right, bottom;
                for (int i = 0; i < rowCount && top < height; i++) {
    
    
                    right = width;
                    bottom = top + heights[i];
//                    生成一个View
                    View view = makeAndStep(i, 0, top, width, bottom);
                    viewList.add(view);
                    top = bottom;//循环摆放
                }

            }

        }
    }

    private View makeAndStep(int row, int left, int top, int right, int bottom) {
    
    
        View view = obtainView(row, right - left, bottom - top);
        view.layout(left, top, right, bottom);
        return view;
    }

    private View obtainView(int row, int width, int height) {
    
    
//        key type
        int itemType = adapter.getItemViewType(row);
//       取不到
        View recyclerView = recycler.get(itemType);
        View view = null;
        if (recyclerView == null) {
    
    
            view = adapter.onCreateViewHolder(row, recyclerView, this);
            if (view == null) {
    
    
                throw new RuntimeException("onCreateViewHolder  必须填充布局");
            }
        } else {
    
    
            view = adapter.onBinderViewHolder(row, recyclerView, this);
        }
        view.setTag(R.id.tag_type_view, itemType);
        view.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
                , MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        addView(view, 0);
        return view;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int h = 0;
        if (adapter != null) {
    
    
            this.rowCount = adapter.getCount();
            heights = new int[rowCount];
            for (int i = 0; i < heights.length; i++) {
    
    
                heights[i] = adapter.getHeight(i);
            }
        }
//        数据的高度
        int tmpH = sumArray(heights, 0, heights.length);
        h = Math.min(heightSize, tmpH);
        setMeasuredDimension(widthSize, h);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    //    firstIndex  firstIndex+count
    private int sumArray(int array[], int firstIndex, int count) {
    
    
        int sum = 0;
        count += firstIndex;
        for (int i = firstIndex; i < count; i++) {
    
    
            sum += array[i];
        }
        return sum;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
    
    
        boolean intercept = false;
        switch (event.getAction()) {
    
    
            case MotionEvent.ACTION_DOWN: {
    
    
                currentY = (int) event.getRawY();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
    
    
                int y2 = Math.abs(currentY - (int) event.getRawY());
                if (y2 > touchSlop) {
    
    
                    intercept = true;
                }
            }
        }
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
    
        switch (event.getAction()) {
    
    
            case MotionEvent.ACTION_MOVE: {
    
    
//                移动的距离   y方向
                int y2 = (int) event.getRawY();
//         //            上滑正  下滑负
                int diffY = currentY - y2;
                currentY = y2;//不加响应会变慢
//                画布移动  并不影响子控件的位置
                scrollBy(0, diffY);
            }
        }
        return super.onTouchEvent(event);
    }


    @Override
    public void scrollBy(int x, int y) {
    
    
        //修正
//        scrollY表示 第一个可见Item的左上顶点 距离屏幕的左上顶点的距离
        scrollY += y;
        scrollY = scrollBounds(scrollY);
//        scrolly
        if (scrollY > 0) {
    
    
//              上滑正  下滑负  边界值
            while (scrollY > heights[firstRow]) {
    
    
//      1 上滑移除  2 上划加载  3下滑移除  4 下滑加载
                removeView(viewList.remove(0));
                scrollY -= heights[firstRow];
                firstRow++;
            }
            while (getFillHeight() < height) {
    
    
                int addLast = firstRow + viewList.size();
                View view = obtainView(addLast, width, heights[addLast]);
                viewList.add(viewList.size(), view);
            }


        } else if (scrollY < 0) {
    
    
//            4 下滑加载
            while (scrollY < 0) {
    
    
                int firstAddRow = firstRow - 1;
                View view = obtainView(firstAddRow, width, heights[firstAddRow]);
                viewList.add(0, view);
                firstRow--;
                scrollY += heights[firstRow + 1];
            }
//             3下滑移除
            while (sumArray(heights, firstRow, viewList.size()) - scrollY - heights[firstRow + viewList.size() - 1] >= height) {
    
    
                removeView(viewList.remove(viewList.size() - 1));
            }

        } else {
    
    
        }
        repositionViews();
    }

    private int scrollBounds(int scrollY) {
    
    
//        上滑
        if (scrollY > 0) {
    
    
            scrollY = Math.min(scrollY, sumArray(heights, firstRow, heights.length - firstRow) - height);
        } else {
    
    
//            极限值  会取零  非极限值的情况下   socrlly
            scrollY = Math.max(scrollY, -sumArray(heights, 0, firstRow));

        }
        return scrollY;
//        下滑
    }

    private void repositionViews() {
    
    
        int left, top, right, bottom, i;
        top = -scrollY;
        i = firstRow;
        for (View view : viewList) {
    
    
            bottom = top + heights[i++];
            view.layout(0, top, width, bottom);
            top = bottom;
        }
    }

    private int getFillHeight() {
    
    
//        数据的高度 -scrollY
        return sumArray(heights, firstRow, viewList.size()) - scrollY;
    }


    @Override
    public void removeView(View view) {
    
    
        super.removeView(view);
        int key = (int) view.getTag(R.id.tag_type_view);
        recycler.put(view, key);
    }

    interface Adapter {
    
    
        View onCreateViewHolder(int position, View convertView, ViewGroup parent);

        View onBinderViewHolder(int position, View convertView, ViewGroup parent);

        //Item的类型
        int getItemViewType(int row);

        //Item的类型数量
        int getViewTypeCount();

        int getCount();

        int getHeight(int index);
    }
}

示例效果

在这里插入图片描述


推荐阅读

终于明白我为啥来网易了!

Android 开发挑战赛 | 第 2 周: 倒计时器

About

本文Demo下载

UI系列文章一览

猜你喜欢

转载自blog.csdn.net/u014158743/article/details/114364935