一步步打造一个属于自己的RecyclerView

摘要:有时候在做开发的时候,遇到需要自定义View的时候,相信很多人的第一反应就是到github上去搜一下有没有相关的已经做好的View,如果搜到了就暗暗窃喜,搜不到就悲剧了,我曾经也是其中一员,但是残酷的现实就是就算搜到了用的飞快的时候,bug来了,然后又是陷入看源码,改源码,不行再改的恶性循环中,好一个累的成狗的程序猿啊!!!

于是决定痛下决心,一改从前的恶习,自己一步步踩坑,不断调试打造一个属于自己的RecyclerView,这样不仅把ViewGroup的事件分发搞清楚了,而且还增强了一个开发者傲视一切的自信心!只要跨过这一层,就好像打通了任督二脉一样,尽情的把自定义View踩在脚下吧。

本篇博客分享的是一个菜鸟程序员如何练级的,碰到的坑也分享给大家。
一个RecyclerView,再次封装,需要哪些功能。
1.下拉刷新
2.加载更多
3.跟listview一样利用addHeaderView(),addFooterView()来任意添加头部和底部View。
4.添加蒙层。

先把不那么复杂的先给做了。先做第3个吧。headerView和footerView都是RecyclerView的一个item,RecyclerView通过Adapter的viewType来创建不同的ViewHolder,因此这个ViewType很重要。

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)

public int getItemCount() 

public int getItemViewType(int position)

通过观察,很容易得出结论,通过position返回一个ViewType,可以通过viewType来创建一个ViewHolder。position->viewType->View->ViewHolder。那么很容易联想到用HashMap来存储view,key是viewType,value是View。这里采用的是SparseArrayCompat来代替HashMap,既然android推荐用这个东西,自然有用它的道理。其内部实现了压缩算法,可以进行矩阵压缩,大大减少了存储空间,节约内存。此外它的查找算法是二分法,提高了查找的效率。

public void addHeaderView(@NonNull View view) {
        mHeaderViews.put(mHeaderViews.size() + VIEW_TYPE_NORMAL_HEADER, view);
    }

    public void addFooterView(@NonNull View view) {
        mFooterViews.put(mFooterViews.size() + VIEW_TYPE_NORMAL_FOOTER, view);
    }

自然的写出了这样一串代码。

然后来看看getItemViewType是如何做的

 @Override
        public int getItemViewType(int position) {
            if (position == 0) {
                return VIEW_TYPE_REFRESH_HEADER;
            } else if (isFooter(position)) {
                return VIEW_TYPE_LOAD_MORE_FOOTER;
            } else if (isHeaders(position)) {
                return mHeaderViews.keyAt(position - 1);
            } else if (isContents(position)) {
                return VIEW_TYPE_CONTENT;
            } else if (isFooters(position)) {
                if (adapter != null) {
                    return mFooterViews.keyAt(position - mHeaderViews.size() - adapter.getItemCount() - 1);
                } else {
                    return mFooterViews.keyAt(position - mHeaderViews.size() - 1);
                }
            } else {
                throw new RuntimeException("invalid Item type");
            }
        }


        public boolean isFooter(int position) {
            return position == getItemCount() - 1;
        }

        public boolean isHeaders(int position) {
            return mHeaderViews != null && mHeaderViews.size() > 0
                    && position <= mHeaderViews.size();
        }

        public boolean isContents(int position) {
            return adapter != null && adapter.getItemCount() > 0
                    && position <= mHeaderViews.size() + adapter.getItemCount();
        }

        public boolean isFooters(int position) {
            if (adapter == null) {
                return mFooterViews.size() > 0 &&
                        position <= mHeaderViews.size() + mFooterViews.size();
            } else {
                return mFooterViews.size() > 0 && position <= mHeaderViews.size()
                        + adapter.getItemCount() + mFooterViews.size();
            }
        }

把key值当成viewType传过去,通过viewType拿到View。一切万事大吉了。

 @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            RecyclerView.ViewHolder viewHolder = null;
            if (viewType == VIEW_TYPE_REFRESH_HEADER) {
                viewHolder = createHeaderViewHolder(parent);
            } else if (viewType == VIEW_TYPE_LOAD_MORE_FOOTER) {
                viewHolder = createFooterViewHolder(parent);
            } else if (viewType == VIEW_TYPE_CONTENT) {
                return adapter.onCreateViewHolder(parent, viewType);
            } else {
                if (mHeaderViews.get(viewType) != null) {

                    viewHolder = new ViewHolder(mHeaderViews.get(viewType)) {
                        @Override
                        public String toString() {
                            return super.toString();
                        }
                    };
                }
                if (mFooterViews.get(viewType) != null) {
                    viewHolder = new ViewHolder(mFooterViews.get(viewType)) {
                        @Override
                        public String toString() {
                            return super.toString();
                        }
                    };
                }
            }
            return viewHolder;
        }

值得注意的是我们把自己要定义的子Adapter传进来。item的个数就很好算了。


        @Override
        public int getItemCount() {
            int itemCount = 0;
            if (adapter != null) {
                itemCount = adapter.getItemCount();
            }
            return mHeaderViews.size() + mFooterViews.size() + 2 + itemCount;
        }

adapter写好了,我们需要重写RecyclerView的setAdapter这个方法。

@Override
    public void setAdapter(Adapter adapter) {
        mWrapperAdapter = new WrapperAdapter(adapter);
        super.setAdapter(mWrapperAdapter);
        adapter.registerAdapterDataObserver(dataObserver);
        dataObserver.onChanged();
    }

贴上整个Adapter的代码。

private class WrapperAdapter extends RecyclerView.Adapter {

        private Adapter adapter;

        public WrapperAdapter(Adapter adapter) {
            this.adapter = adapter;
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            RecyclerView.ViewHolder viewHolder = null;
            if (viewType == VIEW_TYPE_REFRESH_HEADER) {
                viewHolder = createHeaderViewHolder(parent);
            } else if (viewType == VIEW_TYPE_LOAD_MORE_FOOTER) {
                viewHolder = createFooterViewHolder(parent);
            } else if (viewType == VIEW_TYPE_CONTENT) {
                return adapter.onCreateViewHolder(parent, viewType);
            } else {
                if (mHeaderViews.get(viewType) != null) {

                    viewHolder = new ViewHolder(mHeaderViews.get(viewType)) {
                        @Override
                        public String toString() {
                            return super.toString();
                        }
                    };
                }
                if (mFooterViews.get(viewType) != null) {
                    viewHolder = new ViewHolder(mFooterViews.get(viewType)) {
                        @Override
                        public String toString() {
                            return super.toString();
                        }
                    };
                }
            }
            return viewHolder;
        }

        private ViewHolder createFooterViewHolder(ViewGroup parent) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_productlist_footer, parent, false);
            LoadingFooterHolder footerHolder = new LoadingFooterHolder(view);
            loadingFooterHolder = footerHolder;
            return footerHolder;
        }

        private ViewHolder createHeaderViewHolder(ViewGroup parent) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_loading_header, parent, false);
            LoadingHeaderHolder headerHolder = new LoadingHeaderHolder(view);
            loadingHeaderHolder = headerHolder;
            return headerHolder;
        }

        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
            RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
            if (manager instanceof GridLayoutManager) {
                final GridLayoutManager gridManager = ((GridLayoutManager) manager);
                gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        if (position == 0){
                            return gridManager.getSpanCount();
                        }else if (isHeaders(position)){
                            return gridManager.getSpanCount();
                        }else if (isContents(position)){
                            return 1;
                        }else{
                            return gridManager.getSpanCount();
                        }
                    }
                });
            }
        }

        @Override
        public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
            super.onViewAttachedToWindow(holder);
            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            if (lp != null
                    && lp instanceof StaggeredGridLayoutManager.LayoutParams
                    && (isHeaders(holder.getLayoutPosition()) ||
                    isFooter(holder.getLayoutPosition()) ||
                    holder.getLayoutPosition() == 0 ||
                    isFooters(holder.getLayoutPosition()))) {
                StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
                p.setFullSpan(true);
            }
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {

        }

        @Override
        public int getItemCount() {
            int itemCount = 0;
            if (adapter != null) {
                itemCount = adapter.getItemCount();
            }
            return mHeaderViews.size() + mFooterViews.size() + 2 + itemCount;
        }

        @Override
        public int getItemViewType(int position) {
            if (position == 0) {
                return VIEW_TYPE_REFRESH_HEADER;
            } else if (isFooter(position)) {
                return VIEW_TYPE_LOAD_MORE_FOOTER;
            } else if (isHeaders(position)) {
                return mHeaderViews.keyAt(position - 1);
            } else if (isContents(position)) {
                return VIEW_TYPE_CONTENT;
            } else if (isFooters(position)) {
                if (adapter != null) {
                    return mFooterViews.keyAt(position - mHeaderViews.size() - adapter.getItemCount() - 1);
                } else {
                    return mFooterViews.keyAt(position - mHeaderViews.size() - 1);
                }
            } else {
                throw new RuntimeException("invalid Item type");
            }
        }


        public boolean isFooter(int position) {
            return position == getItemCount() - 1;
        }

        public boolean isHeaders(int position) {
            return mHeaderViews != null && mHeaderViews.size() > 0
                    && position <= mHeaderViews.size();
        }

        public boolean isContents(int position) {
            return adapter != null && adapter.getItemCount() > 0
                    && position <= mHeaderViews.size() + adapter.getItemCount();
        }

        public boolean isFooters(int position) {
            if (adapter == null) {
                return mFooterViews.size() > 0 &&
                        position <= mHeaderViews.size() + mFooterViews.size();
            } else {
                return mFooterViews.size() > 0 && position <= mHeaderViews.size()
                        + adapter.getItemCount() + mFooterViews.size();
            }
        }
    }

接下来就要来实现下拉刷新了,思路是这样的,我们先把刷新布局的高度设为1px,然后重写onTouchEvent,拿到dy,通过dy来改变刷新布局的高度,当UP的时候,通过动画平移到原来的样子。可能这样实现不好,但是我当时就是这样写的。如果有什么更好的办法可以@我。

来看看onTouchEvent的代码

 @Override
    public boolean onTouchEvent(MotionEvent e) {
        if (shouldTouch()) {
            switch (e.getActionMasked()) {
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    if (status == PULLING || status == LOADING) {
                        stopRefresh();
                    } else if (status == PENDINGLOADING) {
                        if (onRefreshListener != null) {
                            onRefreshListener.onPullRefresh();
                        }
                        animateToStatus(LOADING);
                    }
                    initialX = INVALID;
                    initialY = INVALID;
                    lastY = INVALID;
                    dragging = false;
                    return super.onTouchEvent(e);
                case MotionEvent.ACTION_POINTER_UP:
                case MotionEvent.ACTION_POINTER_DOWN:
                    lastY = INVALID;
                    break;
                case MotionEvent.ACTION_MOVE:
                    int index = e.getActionIndex();
                    if (lastY == INVALID) {
                        lastY = e.getY(index);
                    }
                    if (initialX == INVALID) {
                        initialX = e.getX(index);
                        initialY = e.getY(index);
                    }
                    float x = e.getX(index);
                    float y = e.getY(index);
                    if (!dragging && status == IDLE && y - initialY > touchSlop && y - initialY > x - initialX) {
                        dragging = true;
                        setStatus(PULLING);
                    }
                    boolean consumed = false;
                    if (dragging && (status == PULLING || status == PENDINGLOADING)) {
                        float dy = y - lastY;
                        refreshLoadingViewHeight(dy);
                        consumed = true;
                    }
                    lastY = e.getY(index);
                    if (consumed) {
                        return true;
                    }
                    break;
            }
            return super.onTouchEvent(e);
        }
        return super.onTouchEvent(e);
    }

如果需要详细看的话,详见github,地址会在最后贴出来。

我们还可以添加蒙层。

 @Override
    protected void dispatchDraw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(Color.parseColor("#FF4081"));
        canvas.drawRect(0, getRootView().getTranslationY(), getWidth(), getHeight(), paint);
        super.dispatchDraw(canvas);
    }

贴上地址:https://github.com/Eddieyuan123/XRecyclerView-chelizi

欢迎测试。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值