自定义简单RecycleView

本篇主要以自定义简单的 RecycleView 为例,带我们走进自定义 view 的世界,为了与原生的RecycleView区分这里我将我的简易版定义为ZlRecycleView以免大家搞混了

简介

自定义view有三种

  1. 继承view 的控件
  2. 继承ViewGroup控件容器
  3. 组合view

主要方法
onMeasure() ,onLayout() ,ondraw()
事件处理方法
onInterceptTouchEvent() ,dispatchTouchEvent(),onTouchEvent(),scrollBy()

开始自定义

主要功能实现

 ZlRecycleView 毫无疑问需要继承自ViewGroup,它可以容纳各种各样的View
 控件,它的item可以复用所以我们也给他定义了回收池(ZlRecycle),当 item 
 滑出屏幕给它添加到回收池,底下item 加载到屏幕时先看回收池中有没有缓存,
 有的话复用,没有的话创建、绑定数据展示

自定义详解

  1. 准备工作
//ZlRecycleView 
public class ZlRecycleView extends ViewGroup {
//自定义 view 必不可少的构造函数,传递上下文和属性
 public ZlRecycleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
    //recycleview 需要的 Adapter
    public interface Adapter{
        View onCreateViewHodler(int position , View convertView , ViewGroup parent);
        View onBindViewHodler(int position , View convertView , ViewGroup parent);
        int getItemViewType(int row);
        int getViewTypeCount();
        int getCount();
        int getHeight(int i);
    }
}

public class ZlRacycle {
    // 回收池   栈数组 ,多少种view类型就有多少栈
    private Stack<View>[] views;
    //
    public ZlRacycle (int count) { //缓存多少种view
        views = new Stack[count];
        for (int i = 0; i <count ; i++) {
            views[i] = new Stack<View>();
        }
    }
    // 取
    public  View getRecycleView(int type){ 
    // 取那个类型的view用	type识别
        try{
            return views[type].pop();
        }catch (Exception e) {
            return null;
        }
    }
    // 存
    public  void  addRecycleVeiew(int type,View view){ 
    // 取那个类型的view用type识别
         views[type].push(view);
    }
}

以上代码中定义了一个自定义view类和回收池类比较简单,
回收池使用栈方便存取

  1. 对ZlRacycleView初始化
private void init(Context context){
        needRelayout = true;
        //初始化屏幕中展示的View
        viewList = new ArrayList<>();
        //获得最小滑动距离
        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        touchSlop = viewConfiguration.getScaledTouchSlop();
    }
  1. 在layout 中摆放childview
 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //遍历childview 重新摆放childview 位置 耗时操作尽量避免
        //  needRelayout boolean类型 判断调用时是否需要重新摆放childView
        if(needRelayout || changed){ // 父控件改变或者需要重新摆放childview
            needRelayout = false;
            viewList.clear();
            removeAllViews();
            if(adapter != null ){
            //childview 的数量
                rowcount = adapter.getCount();
                heights = new int[rowcount];
                for (int i = 0; i < rowcount; i++) {
                   heights[i] += adapter.getHeight(i); //累计view的高度
                }
                withd = r -1 ;
                height = b -1;
                int top = 0,bottom ;
                for (int i = 0; i < rowcount && top< height; i++) {
                //view从上向下显示,第一个view的bottom 相当于第二个view的top 
                    bottom = top + heights[i];
                    //创建新的childview或者从缓存池里获取
                   View view = makeAndSetup(i, l , r, t,b );
                   viewList.add(view);
                    top = bottom;
                }
            }
        }

    }

    private View makeAndSetup(int indexData, int l, int r, int t , int b) {
        View view = botain(indexData , r-l , b -t );
        //计算好后需要调用view.layout 摆放childview 的位置
        view.layout(l ,t ,r, b);
        return view;
    }

    private View botain(int row , int width , int height) {
        //先从回收池里找
        int type = adapter.getItemViewType(row);
        View recycleView = recycle.getRecycleView(type);
        View view ;
        if(recycleView == null){
            //回收池没有,创建
            view = adapter.onCreateViewHodler(row ,null, this );
            if (view == null) {
                throw new RuntimeException("onCreateViewHodler must be init");
            }
        } else {
            view  = adapter.onBindViewHodler(row , recycleView , this);
        }
        if(view == null){
            throw new RuntimeException("onCreateViewHodler view = null");
        }
        view.setTag(R.id.tag_type_view , type);
        //设置MeasureSpec  由view的宽高和 MeasureSpec的状态组成
        view.measure(MeasureSpec.makeMeasureSpec(width , MeasureSpec.EXACTLY) ,
                MeasureSpec.makeMeasureSpec(height , MeasureSpec.EXACTLY) );
        addView(view , 0);
      return view;
    }

  1. 移除childview 时添加到缓存池
@Override
    public void removeView(View view) {
        super.removeView(view);
        //移除 view从屏幕中移除添加到回收池
        int type = (int) view.getTag(R.id.tag_type_view );
        recycle.addRecycleVeiew(type , view);
    }
  1. 事件拦截和消费
@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //事件冲突  滑动时recycleview 消费 点击时itemview消费
//滑动拦截

        boolean intercapt = false; // 是否再滑动
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
            //按下时距离屏幕的顶端的距离
                currentY = (int) ev.getRawY();
                break;
                case MotionEvent.ACTION_MOVE:
                    int y2 = Math.abs(currentY(int)ev.getRawY());
                    //按下移动时大于最小移动距离要拦截到时间给onTouchEvent()消费
                    if (y2 > touchSlop) {
                        intercapt = true;
                    }
                    break;
        }
        return intercapt;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
            //消费滑动时间 判断上滑还是下滑
                int y2 = (int) ev.getRawY();
                // 从上往下滑是负值   从下往上滑是正值
                int diff  = currentY - y2;  
                scrollBy(0 , diff);
                break;
        }
        return super.onTouchEvent(ev);
    }

  1. 滑动事件
  @Override
    public void scrollBy(int x, int diff) {
        scrollY += diff;
        if(scrollY > 0){
            //向上滑
            //firstRow屏幕中第一个childview再viewlist中的position
            //滑动高度大于第一个view的高度说明它花出去了,将它移除
            while (heights[firstRow]  < scrollY){
                if (!viewList.isEmpty()) {
                    removeView(viewList.remove(0));
                }
                //重置scrollY  和 firstRow 
                scrollY -= heights[firstRow];
                firstRow ++ ;
            }
        }
        //获取屏幕中第一个view到最后的一个view的高度,小于recycleview 的高度 就需要创建新view或者从缓存中取出一个展示
        //因为有缓存池 所以加载一屏后 viewList 的size就是固定的了 firstRow + size就是下一个添加的view的索引
        while (getfilledHeight() < height){
            final int size = viewList.size();
            int dataIndex = firstRow + size;
            View view = botain(dataIndex , withd,heights[dataIndex]);
            viewList.add(view);
        }
//        repositionViews();
    }

    private int  getfilledHeight() {
        return sumArray(heights , firstRow , viewList.size()) - scrollY;
    }

    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;
    }

完结

以上文字叙述比较简单,主要代码都添加了注释,哪有不对忘指正。
没有用到onMeasure 和 onDraw 方法 不知道大家发现了嘛,这两个方法也是自定义view中重要的方法。onMeasure 用来测量自身宽高 ,onDraw 可以再画布中绘制想要的图案,这里以后再单独介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值