作者:saka 地址:https:///post/5b38df5a51882574a36fc14e 声明:本文来自 saka 投稿,转发等请联系原作者授权
RecyclerView是安卓开发中常用的列表控件,当初google设计它的目的就是用来取代listview和gridview。这篇文章要讲的主角是recyclerview的一个附属品-ItemDecoration。讲解的也都是基础内容,主要有三个部分:
为每个item实现索引
为特定的item实现不同的分隔线
覆盖在item上的一个可移动的icon
最终的效果如下(忽略毫无设计的ui):
github地址:https%3A%2F%2Fgithub.com%2Frangaofei%2FCustomViewDemo
简介 ItemDecoration是一个非常简单的抽象类,它没有抽象方法,除去废弃的api,共有三个方法可以重写,分别是
//获取当前view的位置信息 public void getItemOffsets (Rect outRect, View view, RecyclerView parent, State state) //在item背后draw public void onDraw (Canvas c, RecyclerView parent, State state) //在item上边draw public void onDrawOver (Canvas c, RecyclerView parent, State state)
这三个方法可以说是三个非常重要的方法,他们之间有着强烈的因果关系。 首先需要注意的是三个方法的调用顺序:
首先调用的是getItemOffsets会被多次调用,在layoutmanager每次测量可摆放的view的时候回调用一次,在当前状态下需要摆放多少个view这个方法就会回调多少次。
其次会调用ondraw方法,ItemDecoration的ondraw方法是在recyclerview的ondraw方法中调用的,注意这时候传入的canvas是recyclerview的canvas,要时刻注意这点,它是和recyclerview的边界是一致的。这个时候绘制的内容相当于背景,会被item覆盖。
最后调用的是ondrawover方法,ItemDecoration的ondrawover方法是在recyclerview的draw方法中调用的,同样传入的是recyclerview的canvas,这时候onlayout已经调用,所以此时绘制的内容会覆盖item。
理解了以上三个方法,我们就可以随意定制itemdecoration了。
为每个item实现索引 这个要引出的知识点是关于getItemOffsets的详细用法,在这里会传过来四个参数(所有的方法中的state暂不讨论,它和layoutmanager关系比较密切),主要关注前三个参数:
outRect,核心参数,这个rect相当于item摆放的时候设置的margin,rect的left相当于item的marginleft,rect的right相当于item的marginright。
view,当前绘制的view,可以用来获取它在adapter中的位置
parent,recyclerview,没什么好说的。
上一段简单的代码,来实现为所有的左侧添加一个空白
if (parent.getLayoutManager() instanceof LinearLayoutManager) { this .layoutOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation(); }switch (this .layoutOrientation) { case LinearLayoutManager.VERTICAL: outRect.set(40 , 0 , 0 , 2 );//bottom正常 break ; case LinearLayoutManager.HORIZONTAL://比较懒,没写 break ; default : break ; }
这样,就在所有的item左侧流出来一段空白,接下来就可以在这段空白上来绘制文字: 首先在ondraw方法中分发事件:
@Override public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state) { Log.d("---" , "onDraw" ); switch (this .layoutOrientation) { case LinearLayoutManager.VERTICAL: drawVertical(c, parent); break ; case LinearLayoutManager.HORIZONTAL: drawHorizontal(c, parent); break ; default : break ; } }
然后看一下具体的绘制文字流程:
c.save();final int left;final int right;if (parent.getClipToPadding()) { left = parent.getPaddingLeft(); right = parent.getWidth() - parent.getPaddingRight(); c.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); } else { left = 0 ; right = parent.getWidth(); }for (int i = 0 ; i < parent.getChildCount(); i++) { View view = parent.getChildAt(i); int position = parent.getChildAdapterPosition(view); String text = String.valueOf(position+1 ); float w = textPaint.measureText(text); c.drawText(text, 20 - w / 2 , view.getBottom() - view.getHeight() / 2 + textPaint.getFontMetri().descent, textPaint); } c.restore();
这里要注意两点,paren.getChildCount是获取的当前显示的view的数量,并不会获取不显示的view的数量。假如recyclerview里共有30条数据,而当前屏幕内显示的只有5条,这paren.getChildCount的值是5,不是30。 int position = parent.getChildAdapterPosition(view) 这段代码是获取的当前这个view在30条数据中的位置。
c.drawText(text, 20 - w / 2 , view.getBottom() - view.getHeight() / 2 + textPaint.getFontMetri().descent, textPaint);
这段代码绘制了文字,文字的位置就是20 - w / 2, view.getBottom() - view.getHeight() / 2 + textPaint.getFontMetri().descent这个坐标,假如对绘制文字时坐标的获取不太熟悉的话,需要搜索一下资料。
为特定的item实现不同的分隔线 这个非常简单,只需要在getitemoffset中判断即可:
switch (this .layoutOrientation) { case LinearLayoutManager.VERTICAL: if (parent.getChildAdapterPosition(view) % 3 == 0 ) { outRect.set(40 , 0 , 0 , 100 ); } else { outRect.set(40 , 0 , 0 , 2 ); } break ; case LinearLayoutManager.HORIZONTAL: break ; default : break ; }
通过上面代码,就为所有的索引能被3整除的位置添加了一个高度为100的分隔线。看一下效果。
覆盖在item上的一个可移动的icon 这个方法需要在ondrawover中实现,icon会覆盖在item上边,并随屏幕比例来回移动,废话不多,直接上代码:
private void drawOverVertical (Canvas c, RecyclerView parent) { c.save(); final int left; final int right; if (parent.getClipToPadding()) { left = parent.getPaddingLeft(); right = parent.getWidth() - parent.getPaddingRight(); c.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); } else { left = 0 ; right = parent.getWidth(); } for (int i = 0 ; i < parent.getChildCount(); i++) { View view = parent.getChildAt(i); int top = view.getTop(); int totalHeight = parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(); float percentT = (float ) top / totalHeight; drawable.setBounds((int ) (view.getLeft() + 10 + view.getWidth() * percentT), view.getTop(), (int ) (view.getLeft() + 58 + view.getWidth() * percentT), view.getTop() + 48 ); drawable.draw(c); } }
上面代码中测量了当前view的top位置相对于recyclerview的高度的比例,在item上实现随位置滚动而左右滑动的一个icon。
一篇非常简单的文章,希望能帮助到一些朋友。 另外分享一个自己正在编写的基于注解的库:https%3A%2F%2Fgithub.com%2Frangaofei%2FTimeLine
欢迎提意见