Android Recyclerview细节与记录

1、简介

该篇文章记录的是关于我们日常开发中常用的RecyclerView的一些小的细节。开发中用的比较多的有BRVAH框架,用于快速构建Adapter,介绍了添加头布局索引值的修改,还有设置空布局。以及滑动监听事件。

不知道大家有没有关注到列表的索引,像Java中的数组和List就是从索引0开始的,像我们时常使用的BRVAH(一款Adapter帮助工具)有必要知道返回的索引值是多少。因为这关乎我们更新的那个条目,或者说修正Data的值。

这里还记录的recyclerView的局部刷新的方式,能够有效的解决图片闪烁的效果,这里还是好好的记录一下吧,防止后面会用到。

2、细节探讨

2.1索引值

探究在内部类和外部类中 help.getAdapterPosition( )中的值

 2.1.1 首先看一下没有头布局

            mAdapter = new BaseQuickAdapter(R.layout.item_scroll,mList) {
            @Override
            protected void convert(BaseViewHolder helper, String item) {
                helper.setText(R.id.tv,item);
                Log.e("RecyclerView ID",
                        "convert helper.getAdapterPosition() ="+helper.getAdapterPosition());
                Log.e("RecyclerView ID","convert helper.getLayoutPosition() = "+helper.getLayoutPosition());
            }
        };

        mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
                Log.e("RecyclerView ID","tOnItemClick  position ="+position);
            }
        });
 E/RecyclerView ID: convert helper.getAdapterPosition() =0
 E/RecyclerView ID: convert helper.getLayoutPosition() = 0
 E/RecyclerView ID: convert helper.getAdapterPosition() =1
 E/RecyclerView ID: convert helper.getLayoutPosition() = 1
 E/RecyclerView ID: convert helper.getAdapterPosition() =2
 E/RecyclerView ID: convert helper.getLayoutPosition() = 2
 E/RecyclerView ID: convert helper.getAdapterPosition() =3
 E/RecyclerView ID: convert helper.getLayoutPosition() = 3
 E/RecyclerView ID: convert helper.getAdapterPosition() =4
 E/RecyclerView ID: convert helper.getLayoutPosition() = 4
 E/RecyclerView ID: convert helper.getAdapterPosition() =5
 E/RecyclerView ID: convert helper.getLayoutPosition() = 5
 E/RecyclerView ID: convert helper.getAdapterPosition() =6
 E/RecyclerView ID: convert helper.getLayoutPosition() = 6

 点击 i = 10的条目 OK的
 E/RecyclerView ID: tOnItemClick  position =10

小结论: 该例说明索引与我们预期一致。经测试在外部类也是一致的

2.1.2 为其添加一个头部View

        View view = new View(this);
        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,50));
        view.setBackgroundColor(getResources().getColor(R.color.colorAccent));
        mAdapter = new MyTestAdapter(R.layout.item_scroll,mList);
        mAdapter.addHeaderView(view);

接着看打印的信息

 E/RecyclerView ID: convert helper.getAdapterPosition() =1
 E/RecyclerView ID: convert helper.getLayoutPosition() = 1
 E/RecyclerView ID: convert helper.getAdapterPosition() =2
 E/RecyclerView ID: convert helper.getLayoutPosition() = 2
 E/RecyclerView ID: convert helper.getAdapterPosition() =3
 E/RecyclerView ID: convert helper.getLayoutPosition() = 3
 E/RecyclerView ID: convert helper.getAdapterPosition() =4
 E/RecyclerView ID: convert helper.getLayoutPosition() = 4
 E/RecyclerView ID: convert helper.getAdapterPosition() =5
 E/RecyclerView ID: convert helper.getLayoutPosition() = 5
 E/RecyclerView ID: convert helper.getAdapterPosition() =6
 E/RecyclerView ID: convert helper.getLayoutPosition() = 6
 E/RecyclerView ID: convert helper.getAdapterPosition() =7
 E/RecyclerView ID: convert helper.getLayoutPosition() = 7

 同样我们再点击list ID为10的条目
 E/RecyclerView ID: tOnItemClick  position =10

那么对于  mAdapter.notifyItemRemoved(position); 这个API呢,比如我们移除一条数据需要刷新的话

当Head为1的时候,当我们点击10的时候,删除的确实9 ,为什么呢,因为其实际条目是从头布局开始的哦。好了懂了吧。

总结: 看清楚了没有。当我们运用BRVAH时,定当注意索引,加入你不添加头布局的话,自然是没什么感觉的,可是当你添加了头布局,那么在其绘制的过程中返回索引的话必然是在其基础上加上头部局的个数的。

比较ok的一点是,该框架里的头布局是一个linearLayout这样你只需要在原基础上进行增加1即可。

2.2 设置空布局

 BRVAH 支持设置空布局,设置空布局注意,这里最好得传入View,直接传入布局ID会报错,这里注意一下吧。

View emptyView = LayoutInflater.from(this).inflate(R.layout.empty_pond_main, null, false);
mPondMainAdapter.setEmptyView(emptyView);

2.3 recyclerView滑动监听

期望实现的滑动效果,当向下滑动的时候对应的控件显示,而向上滑动时进行隐藏。

        // 添加滑动监听事件 ---
        mRvScroll.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            // 判定纵向的滑动距离是否已经大于 控件响应的最小触摸距离
                boolean isSignificantDelta = Math.abs(dy) > ViewConfiguration.getTouchSlop();
                if (isSignificantDelta) {
           // 判定滑动的方向
                    if (dy > 0) {
                        hideLogTab();
                    } else {
                        showLogTab();
                    }
                }
            }
        });

Android Recyclerview细节与记录_第1张图片

确实达到了滑动隐藏和显示的效果了。这里还不是很完美,后面可以考虑引入比较好的动画。动画这块也必将下更大的功夫。 

2.4 RecyclerView局部刷新

在我们的开发中,有些情况需要用到局部刷新,有其是一些很消耗资源的内容,点击进行修改频繁刷新会造成一定的问题。这里尤其是对于含有图片和checkbox的情形时。下面一个小案例进行实现展示

Android Recyclerview细节与记录_第2张图片

activity点击事件代码 

    // 原始的rv绑定数据
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler);
        init();
        mRecyclerView = findViewById(R.id.recycler);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
        mRecycleAdapter = new RecycleAdapter(this,this);
        mRecycleAdapter.setData(mRecycleBeanList);
        mRecyclerView.setAdapter(mRecycleAdapter);
    }    
    
    @Override
    public void onFirstBtnClick(int position) {
        int first = mRecycleBeanList.get(position).getFirst();
        mRecycleBeanList.get(position).setFirst(first+1);
        int second = mRecycleBeanList.get(position).getSecond();
        // 改变对应position的数据源 即可完成刷新操作
        mRecycleBeanList.get(position).setSecond(second+1);
        mRecycleAdapter.notifyItemChanged(position,1);
    }

    @Override
    public void onSecondBtnClick(int position) {
        int first = mRecycleBeanList.get(position).getFirst();
        mRecycleBeanList.get(position).setFirst(first+1);
        int second = mRecycleBeanList.get(position).getSecond();
        mRecycleBeanList.get(position).setSecond(second+1);
        mRecycleAdapter.notifyItemChanged(position,2);
    }

    @Override
    public void onAllCBtnClick(int position) {
        int first = mRecycleBeanList.get(position).getFirst();
        mRecycleBeanList.get(position).setFirst(first+1);
        int second = mRecycleBeanList.get(position).getSecond();
        mRecycleBeanList.get(position).setSecond(second+1);
        mRecycleAdapter.notifyItemChanged(position);
    }

重写含有三个参数的onBindViewHolder对Flag进行判断,调用 

    @Override
    public void onBindViewHolder(ViewHolder holder, int position, List payloads) {
        if (payloads.isEmpty()){
            onBindViewHolder(holder,position);
        }else {
            int flag = (int)payloads.get(0);
            switch (flag){
                case 1:
                    holder.mTvFirst.setText("数据 "+ mRecycleBeanList.get(position).getFirst());
                    break;
                case 2:
                    holder.mTvSecond.setText("数据 "+ mRecycleBeanList.get(position).getSecond());
                    break;
                case 3:
                    onBindViewHolder(holder,position);
                    break;
                default:
                        break;
            }
        }
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.mTvFirst.setText("数据 "+ mRecycleBeanList.get(position).getFirst());
        holder.mTvSecond.setText("数据 "+ mRecycleBeanList.get(position).getSecond());
        holder.mBtnFirst.setOnClickListener(v -> {
            if (mRecyclerListener != null){
                mRecyclerListener.onFirstBtnClick(position);
            }
        });

        holder.mBtnSecond.setOnClickListener(v -> {
            if (mRecyclerListener != null){
                mRecyclerListener.onSecondBtnClick(position);
            }
        });

        holder.mBtnTotal.setOnClickListener(v -> {
            if (mRecyclerListener != null){
                mRecyclerListener.onAllCBtnClick(position);
            }
        });
    }

OK就是这么简单,以防后面有用,这里先记录下来。

重点!!!!!!!!!!!!!!!!!!!!!!

今天遇到了一个bug,就是执行局部刷新怎么也不会调用三个参数的方法,就很奇怪的那种,就只会调用2个参数这种。

后来我发现问题是,我使用了一个xRecyclerView 默认会增加一个Header这就是导致无法进行局部刷新的原因。

这就是我遇到的局部刷新payload无法传递只能执行两个参数的onBindViewHolder的Bug。

还有,是否需要放弃使用xRecyclerView呢,请希望大神可以指导下,这里确实不知道咋修改了。后面也是需要注意的,还是尽量使用外围包一层的刷子控件吧,比如说pullToRefreshLayout等,这一块也是需要后面去好好研究的哦。

2.5 recyclerview滑动到指定的地方

    /**
     * 平滑的滑动
     * @param position
     */
    public void smoothMoveToPosition(int position) {
        mRecyclerView.stopScroll();
        int firstItem = mLinearLayoutManager.findFirstVisibleItemPosition();
        int lastItem = mLinearLayoutManager.findLastVisibleItemPosition();
        if (position <= firstItem) {
            mRecyclerView.smoothScrollToPosition(position);
        } else if (position <= lastItem) {
            int top = mRecyclerView.getChildAt(position - firstItem).getTop();
            mRecyclerView.smoothScrollBy(0, top);
        } else {
            mRecyclerView.smoothScrollToPosition(position);
        }

    }

    /**
     * 直接滑动
     * @param position
     */
    public void moveToPosition(int position) {
        mRecyclerView.stopScroll();
        int firstItem = mLinearLayoutManager.findFirstVisibleItemPosition();
        int lastItem = mLinearLayoutManager.findLastVisibleItemPosition();
        if (position <= firstItem) {
            mRecyclerView.scrollToPosition(position);
        } else if (position <= lastItem) {
            int top = mRecyclerView.getChildAt(position - firstItem).getTop();
            mRecyclerView.scrollBy(0, top);
        } else {
            mRecyclerView.scrollToPosition(position);
        }
    }

2.6 RecyclerView判断是否滑动到顶部或底部的方法 

// 判断是否滑动到顶部或者底部

    private boolean isScrollTop(){
        if (mManager.findFirstVisibleItemPosition() == 0){
            View view =  mManager.findViewByPosition(0);
            if (view.getTop() == 0){
                return true;
            }
        }
        return false;
    }

    private boolean _isScrollTop2(){
        if (mManager.findFirstCompletelyVisibleItemPosition() == 0){
            return true;
        }
        return false;
    }

    private boolean _isScrollTop3(){
        if (mRvScroll.canScrollVertically(1)){
            return true;
        }
        return false;
    }


    private boolean isScrollBottom(){
        if (mManager.findLastVisibleItemPosition() == mAdapter.getItemCount()-1){
            View view =  mManager.findViewByPosition(mAdapter.getItemCount()-1);
            // 如果添加了分割线 因为会少了分割线
            if (view.getBottom() + itemDecoration.height == mRvScroll.getHeight()){
                return true;
            }

        }
        return false;
    }

    private boolean _isScrollBottom2(){
        if (mManager.findLastCompletelyVisibleItemPosition() == (mRvScroll.getAdapter().getItemCount()-1)){
            return true;
        }
        return false;
    }

    private boolean _isScrollBottom3(){
        if (mRvScroll.canScrollVertically(-1)){
            return true;
        }
        return false;
    }

2.7 自定义adapter添加头部或尾部

原生的RecyclerAdapter是无法添加头部的,我之前在遇到添加头部时都是用的人家封装的框架来实现的,趁有空我自己也去研究实现了下。说到底就还是一个多布局罢了,添加头尾布局。我的思想是,既然想用添加头尾adapter,那么我就初始化的时候就在首尾初始化一个线性布局,就专门用来放头尾,由于又需要拓展我们自定义的adapter那么我们在内部引入了我们平时的adapter即可,那么我们的adapter的数目其实也就是 1+count+1个了,我们只需要在首位特殊处理即可。

 

public  abstract class ZxlRecyclerAdapter extends RecyclerView.Adapter {

    private List mList = new ArrayList<>();
    private Context mContext = null;
    private ZxlHeadWithFooterAdapter mZxlHeadWithFooterAdapter = null;

    public List getList() {
        return mList;
    }

    public Context getContext() {
        return mContext;
    }

    public void setZxlHeadWithFooterAdapter(ZxlHeadWithFooterAdapter zxlHeadWithFooterAdapter) {
        mZxlHeadWithFooterAdapter = zxlHeadWithFooterAdapter;
    }

    public ZxlHeadWithFooterAdapter getZxlHeadWithFooterAdapter() {
        return mZxlHeadWithFooterAdapter;
    }

    public ZxlRecyclerAdapter(List list, Context context) {
        mList = list;
        mContext = context;
    }


    @Override
    public int getItemCount() {
        return mList == null ? 0:mList.size();
    }


    public final void notifyItemChangedWrapper(int position) {
        if (mZxlHeadWithFooterAdapter == null) {
            notifyItemChanged(position);
        } else {
            mZxlHeadWithFooterAdapter.
                    notifyItemChanged(mZxlHeadWithFooterAdapter.getHeadCount() + position);
        }
    }

    public final void notifyItemRemovedWrapper(int position){
        if (mZxlHeadWithFooterAdapter == null){
            notifyItemRemoved(position);
        }else {
            mZxlHeadWithFooterAdapter.notifyItemRemoved(mZxlHeadWithFooterAdapter.getHeadCount() + position);
        }
    }

    public final void notifyItemInsertWrapper(int position){
        if (mZxlHeadWithFooterAdapter == null){
            notifyItemInserted(position);
        }else {
            mZxlHeadWithFooterAdapter.notifyItemInserted(mZxlHeadWithFooterAdapter.getHeadCount()+position);
        }
    }

    public final void notifyChangedWrapper(){
        if (mZxlHeadWithFooterAdapter == null){
            notifyDataSetChanged();
        }else {
            mZxlHeadWithFooterAdapter.notifyDataSetChanged();
        }
    }
}
public class ZxlHeadWithFooterAdapter extends RecyclerView.Adapter {
    private LinearLayout mHeaderLayout = null;
    private LinearLayout mFooterLayout = null;
    public static final int HEADER_VIEW = 0x00000111;
    public static final int FOOTER_VIEW = 0x00000333;
    private RecyclerView.Adapter mInnerAdapter;

    public ZxlHeadWithFooterAdapter(ZxlRecyclerAdapter innerAdapter,Context context) {
        mInnerAdapter = innerAdapter;
        innerAdapter.setZxlHeadWithFooterAdapter(this);

        // 初始化头尾部布局...
        if (mHeaderLayout == null) {
            mHeaderLayout = new LinearLayout(context);
            mHeaderLayout.setOrientation(LinearLayout.VERTICAL);
            mHeaderLayout.setLayoutParams(new RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
        }

        if (mFooterLayout == null) {
            mFooterLayout = new LinearLayout(context);
            mFooterLayout.setOrientation(LinearLayout.VERTICAL);
            mFooterLayout.setLayoutParams(new RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
        }
    }

    public RecyclerView.Adapter getInnerAdapter() {
        return mInnerAdapter;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        switch (i){
            case HEADER_VIEW:
                return new RecyclerView.ViewHolder(mHeaderLayout){

                };
            case FOOTER_VIEW:
                return new RecyclerView.ViewHolder(mFooterLayout) {

                };
                default:
                    return mInnerAdapter.onCreateViewHolder(viewGroup,i);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
        int headCounts = getHeadCount();
        if (i < headCounts){
            return;
        }else {
            int adjPosition = i - headCounts;
            int adapterCount = mInnerAdapter.getItemCount();
            if (adjPosition < adapterCount){
                mInnerAdapter.onBindViewHolder(viewHolder,adjPosition);
            }else {
                return;
            }
        }
    }

    @Override
    public int getItemViewType(int position) {
        int headCounts = getHeadCount();
        if (position < headCounts){
            return HEADER_VIEW;
        }else {
            int adjPosition = position - headCounts;
            int adapterCount = mInnerAdapter.getItemCount();
            if (adjPosition < adapterCount){
                return mInnerAdapter.getItemViewType(adjPosition);
            }else {
                return FOOTER_VIEW;
            }
        }
    }

    /**
     * 返回数量
     * @return
     */
    @Override
    public final int getItemCount() {
        return getHeadCount()+getTailCount()+mInnerAdapter.getItemCount();
    }


    public final int getHeadCount(){
        return 1;
    }


    public final int getTailCount(){
        return 1;
    }

    @Override
    public final void onAttachedToRecyclerView(RecyclerView recyclerView) {
        mInnerAdapter.onAttachedToRecyclerView(recyclerView);
    }

    /**
     * 添加尾部的方法等
     * @param header
     */

    public void addHeadView(View header){
        mHeaderLayout.addView(header);
    }

    public void removeHeadView(View header){
        mHeaderLayout.removeView(header);
    }

    public void addFootView(View footer){
        mFooterLayout.addView(footer);
    }

    public void removeFootView(View footer){
        mFooterLayout.removeView(footer);
    }
}

 

Android Recyclerview细节与记录_第3张图片

该效果对应的具体代码实现在  文下项目地址的 “仿钉钉首页" demo里面可查

项目地址

滑动相关demo

你可能感兴趣的:(常用控件)