美文网首页RecyclerViewrecyclerView干货区
Android——RecyclerView入门学习之ItemDe

Android——RecyclerView入门学习之ItemDe

作者: 英勇青铜5 | 来源:发表于2016-10-16 19:04 被阅读8953次

    学习资料:

    Piasy大神的每篇博客质量都很高,强烈推荐

    网上有很多关于RecyclerView学习博客,之前看了几篇,但基本侧重点都是RecyclerView.Adapter。关于RecyclerView的侧滑删除,之前有过简单学习ItemTouchHleper实现RecyclerView侧滑删除,但对RecyclerView了解远远不够。除了Adapter外,RecyclerView还有很多其他强大的地方需要学习

    天才木木同学收集整理的的Android开发之一些好用的RecyclerView轮子非常好


    学习计划:


    1. ItemDecoration 条目装饰<p>

    是一个抽象类,顾名思义,就是用来装饰RecyclerView的子item的,通过名字就可以知道,功能并不仅仅是添加间距绘制分割线,是用来装饰item的。源码中的描述:

    An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.

    基本的功能是可以用来给RecyclerView的子item设置四边边距,以及上下左右绘制分割线。当然功能不止这些

    ItemDecoration一个有6个抽象方法,有3个还废弃了,也就剩下3个需要学习

    • getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) 设置四边边距
    • onDraw(Canvas c, RecyclerView parent, State state) 绘制装饰
    • onDrawOver(Canvas c, RecyclerView parent, State state) 绘制蒙层

    1.1 使用RecyclerView展示50条字符串数据 <p>

    直接使用RecyclerView展示50条纯字符串数据,代码:

    public class MainActivity extends AppCompatActivity {
        private RecyclerView rv;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            init();
        }
    
        private void init() {
            rv = (RecyclerView) findViewById(R.id.rv_main_activity);
            //设置布局管理器
            LinearLayoutManager manager = new LinearLayoutManager(this);
            manager.setOrientation(LinearLayoutManager.VERTICAL);
            rv.setLayoutManager(manager);
            //设置ItemDecoration
    
            //适配器
            RecyclerViewAdapter adapter = new RecyclerViewAdapter(rv, R.layout.item_layout);
            rv.setAdapter(adapter);
            //添加数据
            addData(adapter);
        }
    
        /**
         * 添加数据
         */
        private void addData(RecyclerViewAdapter adapter) {
            List<String> listData = new ArrayList<>();
            for (int i = 0; i < 50; i++) {
                listData.add("英勇青铜5---->"+i);
            }
            adapter.setData(listData);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (null != rv) {
                rv.setAdapter(null);
            }
        }
    }
    
    

    代码中没有为RecyclerView设置ItemDecorationLayoutManagerLineatLayoutManager


    子item布局文件:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/tv_item_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorAccent"
            android:textAllCaps="false"
            android:textColor="@android:color/white"
            android:textSize="20sp" />
    </LinearLayout>
    

    布局也特别简单,给TextView设置了背景色,字体是白色

    运行效果:

    不设置ItemDecroation

    item间就没有间距,也没有任何的分割线,TextView背景色导致整个RecyclerView看起来都设置了背景色

    下面为每个item底部添加间距


    1.2 getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) 设置四边偏移量

    自定义一个RVItemDecoration继承ItemDecroation,重写getItemOffsets()

    代码:

    public class RVItemDecoration extends RecyclerView.ItemDecoration {
    
        private static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;//水平方向
        private static final int VERTICAL = LinearLayoutManager.VERTICAL;//垂直方向
        private int orientation;//方向
        private final int decoration;//边距大小 px
    
        public RVItemDecoration(@LinearLayoutCompat.OrientationMode int orientation int orientation, int decoration) {
            this.orientation = orientation;
            this.decoration = decoration;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            final int lastPosition = state.getItemCount() - 1;//整个RecyclerView最后一个item的position
            final int current = parent.getChildLayoutPosition(view);//获取当前要进行布局的item的position
            Log.e("0000", "0000---->" + current);
            Log.e("0000", "0000state.getItemCount()---->" + state.getItemCount());
            Log.e("0000", "0000getTargetScrollPosition---->" + state.getTargetScrollPosition());
            Log.e("0000", "0000state---->" + state.toString());
            if (current == -1) return;//holder出现异常时,可能为-1
            if (layoutManager instanceof LinearLayoutManager && !(layoutManager instanceof GridLayoutManager)) {//LinearLayoutManager
                if (orientation == LinearLayoutManager.VERTICAL) {//垂直
                    outRect.set(0, 0, 0, decoration);
                    if (current == lastPosition) {//判断是否为最后一个item
                        outRect.set(0, 0, 0, 0);
                    } else {
                        outRect.set(0, 0, 0, decoration);
                    }
                } else {//水平
                    if (current == lastPosition) {//判断是否为最后一个item
                        outRect.set(0, 0, 0, 0);
                    } else {
                       outRect.set(0, 0,decoration,  0);
                    }
                }
            }
        }
    }
    

    Acivity中,初始化RecyclerView的时候使用:

    //设置ItemDecoration
    rv.addItemDecoration(new RVItemDecoration(LinearLayoutManager.VERTICAL,30));
    

    运行后效果

    添加底部间距

    由于是入门学习,暂时也只是针对对LinearLayoutManager做了一点简单处理,最后1个item不再添加底部间距。实际开发的时候考虑的就要比这复杂的多。LinearLayoutManager大部分时候考虑itemposition就可以,但GridLayoutManagerStaggeredGridLayoutManager需要考虑行和列,情况就比较复杂。


    方法中有4个参数

    • Rect outRect:可以简单理解为item四边边距奉封装在这个对象中,用来设置Itempadding
    • View view: childView,就是item,可以理解为item的根View,并不是item中的控件
    • RecyclerView parent:就是RecyclerView自身
    • RecyclerView.State state : RecyclerView的状态,但并不包含滑动状态

    1.2.1 RecyclerView.State <p>

    这个类是RecyclerView的一个静态内部类,源码中的解释:

    Contains useful information about the current RecyclerView state like target scroll position or view focus. State object can also keep arbitrary data, identified by resource ids.

    个人理解:
    这个State封装着RecyclerView当前的状态,例如滑动目标的Position或者子控件的焦点。State对象也可以对任意的数据通过资源id进行保存或者识别

    State中有3个用于标记当前所处步骤的常量值:

    • STEP_START :布局开始
    • STEP_LAYOUT :布局中
    • STEP_ANIMATIONS :处于动画中

    RecyclerView的工作流程肯定也会是measure,layout,draw。3个值在RecyclerViewonMeasure()有使用,感觉是用来标识RecyclerView在测量过程中所处于的不同时机。目前并不清楚具体的影响,RecyclerView工作流程需要以后再进行深入学习

    方法 作用
    getItemCount() 得到整个RecyclerView中,目前的item的数量
    isMeasuring() 是否正在测量
    isPreLayout() 是否准备进行布局
    get(int resourceId) 根据资源id获取item中的控件,建议使用R.id.*
    put(int resourceId, Object data) 添加一个指定id映射的资源对象,建议使用R.id.*来避免冲突
    remove(int resourceId) 根据使用R.id.*指定id来删除存入的控件对象
    getTargetScrollPosition() 返回已经可见的滑动目标在Adapter的索引值,滑动目标由SmoothScroller来指定
    hasTargetScrollPosition() 判断是否已经滑动到目标
    willRunPredictiveAnimations() 判断是否进行预测模式的动画在布局过程中
    willRunSimpleAnimations() 判断是否进行简单模式的动画在布局过程中

    getItemCount()并不是完全等于getAdapter.getItemCount(),在源码的注释中,关于postion的计算,建议使用State.getItemCount()而非立即直接通过Adapter

    State有些方法和属性涉及到其他的类,有些涉及RecyclerView的工作过程,目前我的学习程度也不是很了解,暂时并不打算继续深挖学习下去,总觉得理解有错误,知道的同学请指出


    1.3 onDraw(Canvas c, RecyclerView parent, State state)绘制装饰 <p>

    这个用于绘制divider,绘制在item的下一层,也就是说item会盖在divider所在层的上面

    使用重写了onDrawer()方法和onDrawOver()ItemDecoration后,对RecyclerView在绘制item时有些影响,主要是由于绘制顺序:

    mItemDecoration.onDraw()-->item.onDraw()--->mItemDecoration.onDrawOver()
    

    onDraw()方法可以为divier设置绘制范围,并且绘制范围可以超出在 getItemOffsets 中设置的范围,但由于是在item下面一层进行绘制,会存在overdraw


    简单使用,完整代码

    public class RVItemDecoration extends RecyclerView.ItemDecoration {
        private final int orientation;//方向
        private final int decoration;//边距大小 px
        private final int lineSize ;//分割线厚度
        private final ColorDrawable mDivider;
    
        public RVItemDecoration(@LinearLayoutCompat.OrientationMode int orientation, int decoration, @ColorInt int color, int lineSize) {
            mDivider = new ColorDrawable(color);
            this.orientation = orientation;
            this.decoration = decoration;
            this.lineSize = lineSize;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            final int lastPosition = state.getItemCount() -1;//整个RecyclerView最后一个item的position
            final int current = parent.getChildLayoutPosition(view);//获取当前要进行布局的item的position
            if (current == -1) return;
            if (layoutManager instanceof LinearLayoutManager && !(layoutManager instanceof GridLayoutManager)) {//LinearLayoutManager
                if (orientation == LinearLayoutManager.VERTICAL) {//垂直
                   if (current == lastPosition) {//判断是否为最后一个item
                        outRect.set(0, 0, 0, 0);
                    } else {
                        outRect.set(0, 0, 0, decoration);
                    }
                } else {//水平
                    if (current == lastPosition) {//判断是否为最后一个item
                        outRect.set(0, 0, 0, 0);
                    } else {
                        outRect.set(0, 0, decoration, 0);
                    }
                }
            }
        }
    
      /**
         * 绘制装饰
         */
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);
            if (orientation == LinearLayoutManager.VERTICAL) {//垂直
                drawHorizontalLines(c, parent);
            } else {//水平
                drawVerticalLines(c, parent);
            }
        }
    
        /**
         * 绘制垂直布局 水平分割线
         */
        private void drawHorizontalLines(Canvas c, RecyclerView parent) {
             //  final int itemCount = parent.getChildCount()-1;//出现问题的地方  下面有解释
            final int itemCount = parent.getChildCount();
            Log.e("item","---->"+itemCount);
            final int left = parent.getPaddingLeft();
            final int right = parent.getWidth() - parent.getPaddingRight();
            for (int i = 0; i < itemCount; i++) {
                final View child = parent.getChildAt(i);
                if (child == null) return;
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                final int top = child.getBottom() + params.bottomMargin;
                final int bottom = top +lineSize;
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    
        /**
         * 绘制水平布局 竖直的分割线
         */
        private void drawVerticalLines(Canvas c, RecyclerView parent) {
            final int itemCount = parent.getChildCount();
            final int top = parent.getPaddingTop();
            for (int i = 0; i < itemCount; i++) {
                final View child = parent.getChildAt(i);
                if (child == null) return;
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                final int bottom = child.getHeight() - parent.getPaddingBottom();
                final int left = child.getRight() + params.rightMargin;
                final int right = left +lineSize;
                if (mDivider == null) return;
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    }
    

    运行后的效果:

    绘制底部分割线
    同样这里也只是考虑了最简单的LinerLayoutManager一种情况。使用这个方法时,注意绘制范围,尽量避免overdraw

    当间距小于分割线的宽度时,分割线绘制的厚度会保持与间距一样


    1.3 onDrawOver(Canvas c, RecyclerView parent, State state) 绘制蒙层<p>

    这个方法是在itemonDraw()方法之后进行回调,也就绘制在了最上层

    简单使用,绘制一个颜色红黄渐变的圆

     @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        //画笔
        final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //圆心 x 坐标
        final float x = parent.getWidth() / 2;
        ////圆心 y 坐标
        final float y = 100;
        //半径
        final float radius = 100;
        //渐变着色器 坐标随意设置的
        final LinearGradient shader = new LinearGradient(x-50, 0, x+100, 200, Color.RED, Color.YELLOW, Shader.TileMode.REPEAT);
        paint.setShader(shader);
        //绘制圆
        c.drawCircle(x, y, radius, paint);
    }
    
    绘制一个圆

    只要手指在RecyclerView上进行滑动,onDrawOver()方法就会被回调。但onDrawOver()每回调一次,会将上次的绘制清除,只有最后一次的绘制会被保留。也就是说绘制的蒙层在屏幕只会有一个


    2. 遇到的问题<p>

    在绘制底部分割线的时候,遇到一个问题:

    遇到的问题

    当快速滑动时,底部会闪动,造成体验不好,如果分割线比较窄,不是很明显,分割线宽的时候就很明显

    已解决 ,原因分析在下面


    2.1 补充,问题修复 <p>

    问题原因:
    问题出在drawHorizontalLines()方法中final int itemCount = parent.getChildCount()-1这行代码,之所以减一考虑的是为了使最后一个item下,不用再绘制分割线。

    RecyclerView.getChildCount()方法的返回值并不是recyclerViewAdapter中所有的item的数量,而是当前屏幕中出现在RecyclerViewitem的数量,一个item只要露出一点点,就算出现,就会被包含在内。

    -1就会导致RecycelrView统计已经出现的item时的数量少一个,就会导致滑动过程中,屏幕中最后一个item的底部分割线不进行绘制,造成闪屏


    解决办法:

    不减1,就OK,修改为:

    final int itemCount = parent.getChildCount();
    

    注意:
    ViewGroupgetChildCount()方法的返回值itemCount便是 getChildAt(int index)这个方法index的区间上限 ,[0,itemCount)。例如:

    position示例
    当前屏幕显示的是25--到-->42parent.getChildCount()的返回结果itemCount便是18。凡是在屏幕上第一个出现的itemindex便是0,哪怕只是漏出一点点。在parent.getChildAt(int index)中,index的取值范围便是0<= index < 18

    2016.10.17 13:48


    3.0 补充 官方推出DividerItemDecoration <p>

    2016.10.20
    Android support libraries更新了25.0.0,新增了BottomNavigationView,并增加了一个官方版的DividerItemDecoration,可以学习下代码,有一些不错的细节优化

    以上信息从drakeet 博客得知,果然关注大神,能够多了解信息


    3. 最后 <p>

    作为一个青铜5的选手,也是热爱LOL的,也有着一颗王者心,可RNG,EDG全输了,止步8强,郁闷

    本人很菜,有错误请指出

    一个完整的练习:TitleItemDecoration

    慕课有一个不错的视屏不一样的RecyclerView优雅实现复杂列表布局

    共勉 :)

    相关文章

      网友评论

      • 心若冰清_:mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.HORIZONTAL));
        这个在添加后,完全没有效果啊,我尝试将item 的颜色改变了,还是不行
        不知道 你有什么方法吗?谢谢
        心若冰清_:@心若冰清_ 仅仅是加了几步,默认的
        心若冰清_:@英勇青铜5 方向我设置的就是水平的分割线,采用默认的itemDecoration,其他的都没有设置啊
        英勇青铜5: @心若冰清_ 额。。。你看看方向,还有大小设置的是不是有问题
      • rynfar:这配色辣眼睛
        英勇青铜5: @rynfar 😂😂😂😂
      • 523bf0910bc4:“Rect outRect:可以简单理解为item四边边距奉封装在这个对象中,用来设置Item的padding。”
        这句话感觉有问题,outRect的作用应该是用于减少item在RecyclerView中的可用空间,结合view的measue源码就看出来了。
        英勇青铜5:多谢指出问题
      • 853b8d58e9f8:android studio 怎么继承不了 那个RecylerView 类呢 因为Sdk版本问题?
        853b8d58e9f8:@英勇青铜5 👍👍嗯,很有道理
        英勇青铜5:@Snowluliang 估计是没有引用support design包。我个人感觉,根据你说的自己情况,你先不要学recyclerview,先把listview多用几次,不然还是不容易理解adapter,viewholder的作用。等对listview熟悉常用的方法以及一些简单的优化有些了解后再来看recyclerview就比较容易接受了
      • kidz:正需要
        英勇青铜5:@kidz :smile::smile:希望有帮助
      • sun_goden:最近也在用recyclerview,写的很好
        英勇青铜5:@android_songjp :smile::smile:我也在学。

      本文标题:Android——RecyclerView入门学习之ItemDe

      本文链接:https://www.haomeiwen.com/subject/qdqayttx.html