本文主要是通过模仿魅族通讯录,学习一下RecycleView的基本用法,水平有限,如有不当之处,欢迎批评指正,不胜感激!
先看通过RecycleView实现的一个效果图:
完整代码见github:仿魅族通讯录
Recycleview继承关系:
RecycleView的三个主要参与者:
1、LayoutManager
2、ItemAnimator
3、ItemDecoration
本文主要用到的是ItemAnimator和ItemDecoration。
Item动画 ItemAnimator
ItemAnimator是个抽象类,ItemAnimator子类用来管理ViewHolder的动画,官方已经实现了一个DefaultItemAnimator,它继承自SimpleItemAnimator,而SimpleItemAnimator继承自ItemAnimator,SimpleItemAnimator是一个包装类,用来记录视图范围,决定当前ViewHolder是否执行移动、变化、添加和删除动画,如果想自定义动画可以通过继承SimpleItemAnimator来实现,github上已经有很多优秀的开源动画了,如:
https://github.com/gabrielemariotti/RecyclerViewItemAnimators
这里只展示一种从左边进入的动画效果,其余大家可以下载下源码查看:
注:这里动画position=1是写死了的,主要是为了方便看效果~
ItemDecoration
ItemDecoration是RecycleView的一个静态内部类,通过对每一个ItemView的边界添加特殊绘制和布局,从而影响每一个ItemView的边界,如绘制分割线、绘制悬浮框等等,ItemDecoration中有三个方法:
1、getItemOffsets()
2、onDraw()
3、onDrawOver()
所有的ItemDecorations绘制都是顺序执行,即:
onDraw() < Item View < onDrawOver(),
onDraw()可以用来绘制divider,但在此之前必须在getItemOffsets设置了padding范围,否则onDraw()的绘制是在ItemView的下面导致不可见;onDrawOver()是绘制在最上层,所以可以用来绘制悬浮框等,下面来看各个方法:
1、getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
内部调用outRect.set(int left, int top, int right, int bottom)来改变ItemView的边界,类似于给ItemView设置Padding,默认getItemOffsets不会影响ItemView的边界,即默认内部调用的是outRect.set(0, 0, 0, 0),如果想得到当前正在修饰的ItemView的位置,可以通过parent.getChildAdapterPosition(view)来获取。
2、onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
在画布canvas上进行绘制,onDraw()方法是在ItemView被绘制之前执行的,因此onDraw()的绘制是在ItemView下方。
3、onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
在画布canvas上进行绘制,onDrawOver()方法是在ItemView被绘制之后执行的,因此onDrawOver()的绘制是在ItemView上方,可用来绘制本例中的悬浮框等。
下面介绍下实现本例魅族通讯录的主要思路:
1、自定义ItemDecoration来绘制悬浮框及ItemView之上的分类Tag:
//用来绘制每个ItemView的边距@Overridepublic void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { //............省略部分代码............ int position = parent.getChildAdapterPosition(view); if (position == 0) { //第一条数据有bar outRect.set(0, dividerHeight, 0, 0); } else if (position > 0) { if (TextUtils.isEmpty(mBeans.get(position).getIndexTag())) return; //与上一条数据中的tag不同时,该显示bar了 if (!mBeans.get(position).getIndexTag().equals(mBeans.get(position - 1).getIndexTag())) { outRect.set(0, dividerHeight, 0, 0); } } }//用来绘制最上面的悬浮框@Override public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) { int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition(); final int bottom = parent.getPaddingTop() + dividerHeight; mPaint.setColor(Color.WHITE); //绘制悬浮框的范围 canvas.drawRect(parent.getLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + dividerHeight, mPaint); //............省略部分代码............ mPaint.setTextSize(40); canvas.drawCircle(DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 2, 35, mPaint); mPaint.setColor(Color.WHITE); canvas.drawText(mBeans.get(position).getIndexTag(), DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 3, mPaint); }//按需绘制ItemView上面的分类Tag@Overridepublic void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { //............省略部分代码............ final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int position = params.getViewLayoutPosition(); if (position == 0) { //第一条数据有bar drawTitleBar(canvas, parent, child, mBeans.get(position), tagsStr.indexOf(mBeans.get(position).getIndexTag())); } else if (position > 0) { //与上一条数据中的tag不同时,该显示bar了 if (!mBeans.get(position).getIndexTag().equals(mBeans.get(position - 1).getIndexTag())) { drawTitleBar(canvas, parent, child, mBeans.get(position), tagsStr.indexOf(mBeans.get(position).getIndexTag())); } } } }
2、绘制右侧导航栏:
首先自定义SideBar(SideBar extends View ) 绘制最右侧字母:
@Override protected void onDraw(Canvas canvas) { //for循环绘制出所有的导航栏字母 for (int i = 0; i < indexStr.length(); i++) { String textTag = indexStr.substring(i, i + 1); float xPos = (mWidth - mPaint.measureText(textTag)) / 2; canvas.drawText(textTag, xPos, singleHeight * (i + 1) + DpUtil.dp2px(mContext, TOP_MARGIN), mPaint); } } @Override public boolean onTouchEvent(MotionEvent event) { //处理按下滑动事件 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //按下 mPaint.setColor(Color.BLACK); invalidate(); case MotionEvent.ACTION_MOVE: //滑动 event.getY()得到在父View中的Y坐标,通过和总高度的比例再乘以字符个数总长度得到按下的位置 int position = (int) ((event.getY() - getTop() - DpUtil.dp2px(mContext, 80)) / mHeight * indexStr.toCharArray().length); if (position >= 0 && position < indexStr.length()) { ((IndexBar) getParent()).setDrawData(event.getY(), String.valueOf(indexStr.toCharArray()[position]), position); if (listener != null) { listener.indexChanged(indexStr.substring(position, position + 1)); } } break; case MotionEvent.ACTION_UP: //抬起 ((IndexBar) getParent()).setTagStatus(false); mPaint.setColor(Color.GRAY); invalidate(); break; } return true; }
SideBar中绘制了导航字母并在onTouchEvent处理了滑动事件,当手指上下滑动时左侧有个圆跟着滑动,这里用的自定义IndexBar( IndexBar extends ViewGroup,IndexBar包含SideBar )来处理的,当SideBar滑动处于MOVE状态时通过((IndexBar) getParent()).setDrawData()把一系列位置参数传到IndexBar中去,:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childNum = getChildCount(); if (childNum <= 0) return; //得到SideBar View childView = getChildAt(0); childWidth = childView.getMeasuredWidth(); //把SideBar排列到最右侧 childView.layout((mWidth - childWidth), 0, mWidth, mHeight); }@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isShowTag) { //根据位置来不断变换Paint的颜色 ColorUtil.setPaintColor(mPaint, position); //绘制圆和文字 canvas.drawCircle((mWidth - childWidth) / 2, centerY, circleRadius, mPaint); mPaint.setColor(Color.WHITE); mPaint.setTextSize(80); canvas.drawText(tag, (mWidth - childWidth - mPaint.measureText(tag)) / 2, centerY - (mPaint.ascent() + mPaint.descent()) / 2, mPaint); } }
主要是在onLayout中把SideBar排列到最右侧,并在onDraw中根据SideBar传过来的一系列位置参数来不断改变圆的位置,这里要注意一下,自定义ViewGroup的onDraw()方法默认是不会调用的,如果想执行onDraw方法,可以通过下面两种方法:
1.设置透明背景:
在构造函数中:setBackgroundColor(Color.TRANSPARENT);
或者在xml中:android:background="@color/transparent"
2.或者可以在构造函数中添加setWillNotDraw(false);
本文例子中用到的三方库:
1、https://github.com/amulyakhare/TextDrawable
2、https://github.com/promeG/TinyPinyin
3、https://github.com/gabrielemariotti/RecyclerViewItemAnimators
共同学习,写下你的评论
评论加载中...
作者其他优质文章