本篇文章已授權微信公眾號 hongyangAndroid(鴻洋)獨家釋出
本文主要是通過模仿魅族通訊錄,學習一下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的邊距
@Override
public 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
@Override
public 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