前言
上一篇文章中詳細介紹了 ItemDecoration 這個了類,了解了 RecycleView 實作分割線的原理。
下面我們來進入實戰篇,首先實作一個比較常見的時光軸(物流詳情)的效果。
一、 效果分析
首先我來分析一下常見的一種物流詳情效果圖:

分析可知,正常的 item 布局實作無法滿足此需求,因為我們在 layout 中不知道 item 的高度為多少,中間那條豎線不容易實作。
有了上節對 ItemDecoration 的了解,我們可以将左邊布局(藍色框以左) 當做分割線來處理。
其中需要注意的有:
- 紅色框内表示整個 item 内容
- 藍色框内 item 在 布局中 固定的内容
- 綠色框内小圖示的繪制
- 黃色框内時間的繪制
- 灰色框内豎線的繪制
二、功能實作
既然知道了思路,根據上節的步驟按部就班的就好。
1.首先模拟下資料結構,定義好 dataBean,如下:
public class LogisticsInfoBean implements Serializable {
/**
* 物流資訊
*/
private String message;
/**
* 目前物流狀态
*/
private LogisticsStatus status;
/**
* 日期
*/
private String date;
/**
* 時間
*/
private String time;
}
public enum LogisticsStatus {
/**
* 一般提示語
*/
TIPS,
/**
* 已下單
*/
ORDERED,
/**
* 備貨中
*/
STOCK_UP ,
/**
* 已發貨
*/
DELIVERED,
/**
* 運輸中
*/
TRANSPORTING,
/**
* 已收貨
*/
RECEIVING
}
2.設定
getItemOffsets()
因為我們作為分割線的部分是藍色框以左,是以我們需要設定 item 左邊 left 的邊距,假設預留 120 像素的邊距。
/**
* 設定 item lef他方向的偏移量
*/
private int leftOffset = 120;
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//設定item 左邊流出 leftOffset 的邊距
outRect.left = leftOffset;
}
- 通過
方法繪制左邊分割線内的内容onDraw()
繪制是主要的部分,而
onDraw()
方法 作用于 RecycleView, 是以我們需要周遊 item ,計算繪制内容的坐标,
然後通過 canvas 的 draw 方法進行繪制。
/**
* 畫的小圓點的半徑
*/
private int circleRadius = 10;
/**
* 畫的小圖示的寬度
*/
private int iconWidth = 50;
private Context context;
private int padding = 24;
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
Log.d(TAG,"----onDraw---");
canvas.save();
//先畫分割線整體背景色
canvas.drawColor(context.getResources().getColor(R.color.white));
final int childCount = parent.getChildCount();
//周遊
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
//1.先畫 1 px 的圖示上半部分的豎線
int startX = leftOffset-30;
int startY = child.getTop();
int lineStopY = startY+padding;
paint.setColor(context.getResources().getColor(R.color.gray_deep));
canvas.drawLine(startX,startY,startX,lineStopY,paint);
//2.畫圖形
int positon = parent.getChildAdapterPosition(child);
LogisticsInfoBean bean = dataBeanList.get(positon);
Rect dst = new Rect(startX-iconWidth/2,lineStopY,startX+iconWidth/2,lineStopY+iconWidth);
//根據不同狀态畫不同圖形
switch (bean.getStatus()){
case TIPS:
canvas.drawCircle(startX,lineStopY+circleRadius,circleRadius,paint);
break;
case ORDERED:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_order),null,dst,null);
break;
case STOCK_UP:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_stockup),null,dst,null);
break;
case DELIVERED:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_diliver),null,dst,null);
break;
case TRANSPORTING:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_transporting),null,dst,null);
break;
case RECEIVING:
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_receive),null,dst,null);
break;
default:
canvas.drawCircle(startX,lineStopY+circleRadius,circleRadius,paint);
break;
}
//畫下半部分豎線
if (positon != dataBeanList.size() -1){
if (bean.getStatus() == LogisticsStatus.TIPS){
canvas.drawLine(startX,lineStopY+2*circleRadius,startX,child.getBottom(),paint);
}else {
canvas.drawLine(startX,lineStopY+iconWidth,startX,child.getBottom(),paint);
}
}
//3.畫日期
canvas.drawText(bean.getDate(),startX-iconWidth/2-10,lineStopY+iconWidth/2,paint);
canvas.drawText(bean.getTime(),startX-iconWidth/2-10,lineStopY+iconWidth/2+20,paint);
}
canvas.restore();
}
4.設定資料源
讓我們來造點假資料
@Override
protected void initData() {
dataBeanList.add(new LogisticsInfoBean("[收貨位址,xxxxxxx]",LogisticsStatus.RECEIVING,"02-11","10:00"));
dataBeanList.add(new LogisticsInfoBean("小主,運輸中x1",LogisticsStatus.TRANSPORTING,"02-10","12:00"));
dataBeanList.add(new LogisticsInfoBean("小主,\n運輸中x2",LogisticsStatus.TRANSPORTING,"02-10","12:10"));
dataBeanList.add(new LogisticsInfoBean("小主,\n\n運輸中x3",LogisticsStatus.TRANSPORTING,"02-10","12:20"));
dataBeanList.add(new LogisticsInfoBean("小主,\n運輸中x4",LogisticsStatus.TRANSPORTING,"02-10","12:30"));
dataBeanList.add(new LogisticsInfoBean("小主,已發貨",LogisticsStatus.DELIVERED,"02-10","10:00"));
dataBeanList.add(new LogisticsInfoBean("小主,備貨中",LogisticsStatus.STOCK_UP,"02-09","12:00"));
dataBeanList.add(new LogisticsInfoBean("訂單支付成功,系統正在處理",LogisticsStatus.ORDERED,"02-09","10:10"));
dataBeanList.add(new LogisticsInfoBean("訂單建立成功,等待支付",LogisticsStatus.TIPS,"02-09","10:00"));
adapter.notifyDataSetChanged();
}
其他一些設定這裡就不在說了,然後運作看一下效果:
是以,實作起來也很簡單有沒有。
完整代碼看這裡:demo 傳送門
總結
關于分割線的實作,不管是什麼樣式的,按照之前說的步驟一步步來就好:
- 通過
方法設定 item 的偏移量**getItemOffset()
- 在
或onDraw()
方法中完成繪制**onDrawOver()
- 周遊 item,計算分割線的位置**
- 通過
方法完成繪制**draw()
其中需要注意的是各個繪制内容坐标的計算。
參考
Android 自定義View實戰系列 :時間軸
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration