Fork me on GitHub

Android自定义View:快递时间轴实现

前言

  • 在Android开发中,时间轴的 UI非常常见,如下图:
    TIM图片20190327232833.jpg

  • 储备知识:
    1.自定义view基础
    2.RecyclerView的使用
    3.自定义RecyclerView.ItemDecoration

具体实现

1.最终效果如下:
TIM截图20190327231820.png

2.实现思路

  • 使用RecyclerView,自定义RecyclerView.ItemDecoration
  • 复习ItemDecoration中getItemOffsets()方法,重写onDraw()方法
  • 实现RecyclerView.Adapter,绑定数据

3.详细设计

TIM截图20190327235039.png

TIM截图20190327235010.png

4.具体实现

  • 引入RecyclerView依赖包

    1
    2
    3
    4
    dependencies {
    ..........
    api 'com.android.support:recyclerview-v7:28.0.0'
    }
  • 在布局文件中使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="horizontal"
    />


    </RelativeLayout>
  • 设置item布局

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
    android:id="@+id/item_title"
    android:text="New Text"
    android:textSize="15sp"
    android:layout_marginLeft="30dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="New Text"
    android:layout_marginLeft="30dp"
    android:textSize="15sp"
    android:id="@+id/item_text"
    android:layout_below="@+id/item_title"
    />

    </LinearLayout>
  • 实现RecyclerView.Adapter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    public class MyAdapter extends RecyclerView.Adapter {
    private LayoutInflater inflater;
    private ArrayList<HashMap<String,Object>> listitem;

    //构造函数,传入数据
    public MyAdapter(Context context,ArrayList<HashMap<String, Object>> listitem) {
    this.inflater = LayoutInflater.from(context);
    this.listitem = listitem;
    }

    class ViewHolder extends RecyclerView.ViewHolder{
    private TextView title,text;

    public ViewHolder(@NonNull View itemView) {
    super(itemView);
    title = itemView.findViewById(R.id.item_title);
    text = itemView.findViewById(R.id.item_text);
    }

    public TextView getTitle() {
    return title;
    }

    public TextView getText() {
    return text;
    }


    }



    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    return new ViewHolder(inflater.inflate(R.layout.list_cell,null));
    //绑定item布局
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
    //绑定数据到ViewHolder
    ViewHolder vh = (ViewHolder) viewHolder;
    vh.title.setText((CharSequence) listitem.get(i).get("ItemTitle"));
    vh.text.setText((CharSequence) listitem.get(i).get("ItemText"));
    }

    @Override
    public int getItemCount() {
    return listitem.size();
    }
    }
  • 自定义RecyclerView.ItemDecoration

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    //轴点画笔
    private final Paint mPaint;
    //时分画笔
    private final Paint mPaint1;
    //年月画笔
    private final Paint mPaint2;
    //itemView 左 上 偏移量
    private int itemView_leftinterval;
    private int itemView_topintervarl;
    //轴点半径
    private int circle_radius;
    private final Bitmap mIcon;


    //在构造函数里初始化需要属性
    public DividerItemDecoration(Context context){
    mPaint = new Paint();
    mPaint.setColor(Color.RED);//设置画笔颜色为红色

    mPaint1 = new Paint();
    mPaint1.setColor(Color.BLUE);
    mPaint1.setTextSize(30);//设置绘制字体大小

    mPaint2 = new Paint();
    mPaint2.setColor(Color.BLUE);
    mPaint2.setTextSize(15);

    itemView_leftinterval = 200; //左偏移长度200
    itemView_topintervarl = 50; //上偏移长度50

    circle_radius = 10;//轴点半径为10
    mIcon = BitmapFactory.decodeResource(context.getResources(),R.mipmap.logo);

    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);

    //设置itemview的左上偏移量,即为onDraw可绘制的区域
    outRect.set(itemView_leftinterval,itemView_topintervarl,0,0);

    }

    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    super.onDraw(c, parent, state);

    //获取RecyclerView的Child的个数
    int childCount = parent.getChildCount();
    //遍历每个item,分别获取他们的位置信息,然后在绘制对应的分割线
    for (int i=0;i<childCount;i++){
    View view = parent.getChildAt(i);//获取每个item对象

    /**
    * 绘制轴点
    */
    // 轴点 = 圆 = 圆心(x,y)

    float centerX = view.getLeft() - itemView_leftinterval/3;
    float centerY = view.getTop() - itemView_topintervarl+(itemView_topintervarl+view.getHeight()/2);
    // 绘制轴点圆
    //c.drawCircle(centerX,centerY,circle_radius,mPaint);
    c.drawBitmap(mIcon,centerX-circle_radius,centerY-circle_radius,mPaint);

    /**
    * 绘制上半轴线
    */
    // 上端点坐标(x,y)
    float upLine_up_x = centerX;
    float upLine_up_y =view.getTop()-itemView_topintervarl;

    // 下端点坐标(x,y)
    float upLine_down_x = centerX;
    float upLine_down_y = centerY-circle_radius;

    c.drawLine(upLine_up_x,upLine_up_y,upLine_down_x,upLine_down_y,mPaint);//绘制下半轴线

    /**
    * 绘制下半轴线
    */
    // 上端点坐标(x,y)
    float bottomLine_up_x = centerX;
    float bottom_up_y = centerY + circle_radius;

    // 下端点坐标(x,y)
    float bottomLine_bottom_x = centerX;
    float bottomLine_bottom_y = view.getBottom();

    //绘制下半部轴线
    c.drawLine(bottomLine_up_x, bottom_up_y, bottomLine_bottom_x, bottomLine_bottom_y, mPaint);


    /**
    * 绘制左边时间文本
    */
    int index = parent.getChildAdapterPosition(view);
    //绘制时间文本起始位置
    float Text_x = view.getLeft()-itemView_leftinterval*5/6;
    float Text_y = upLine_down_y;

    //根据item位置设置时间

    switch (index){
    case 0:
    //设置绘制日期
    c.drawText("13:40",Text_x,Text_y,mPaint1);
    c.drawText("2018.4.03",Text_x+5,Text_y+20,mPaint2);
    break;
    case 1:
    //设置绘制日期
    c.drawText("13:40",Text_x,Text_y,mPaint1);
    c.drawText("2018.4.03",Text_x+5,Text_y+20,mPaint2);
    break;
    case 2:
    //设置绘制日期
    c.drawText("13:40",Text_x,Text_y,mPaint1);
    c.drawText("2018.4.03",Text_x+5,Text_y+20,mPaint2);
    break;
    case 3:
    //设置绘制日期
    c.drawText("13:40",Text_x,Text_y,mPaint1);
    c.drawText("2018.4.03",Text_x+5,Text_y+20,mPaint2);
    break;
    case 4:
    //设置绘制日期
    c.drawText("13:40",Text_x,Text_y,mPaint1);
    c.drawText("2018.4.03",Text_x+5,Text_y+20,mPaint2);
    break;
    case 5:
    //设置绘制日期
    c.drawText("13:40",Text_x,Text_y,mPaint1);
    c.drawText("2018.4.03",Text_x+5,Text_y+20,mPaint2);
    break;
    default:
    c.drawText("已签收",Text_x,Text_y,mPaint1);

    }





    }

    }

    }
  • 初始化数据,绑定RecyclerView

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    public class MainActivity extends AppCompatActivity {

    private ArrayList<HashMap<String, Object>> itemlist;
    private RecyclerView rl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initData();
    initView();
    }

    private void initData() {
    itemlist = new ArrayList<HashMap<String, Object>>();
    HashMap<String, Object> map1 = new HashMap<String, Object>();
    HashMap<String, Object> map2 = new HashMap<String, Object>();
    HashMap<String, Object> map3 = new HashMap<String, Object>();
    HashMap<String, Object> map4 = new HashMap<String, Object>();
    HashMap<String, Object> map5 = new HashMap<String, Object>();
    HashMap<String, Object> map6 = new HashMap<String, Object>();


    map1.put("ItemTitle", "中国广州公司已发出");
    map1.put("ItemText", "发件人:妙卡迪文化公司");
    itemlist.add(map1);

    map2.put("ItemTitle", "国际顺丰已收入");
    map2.put("ItemText", "等待中转");
    itemlist.add(map2);

    map3.put("ItemTitle", "国际顺丰转件中");
    map3.put("ItemText", "下一站中国");
    itemlist.add(map3);

    map4.put("ItemTitle", "中国顺丰已收入");
    map4.put("ItemText", "下一站江苏理工大学");
    itemlist.add(map4);

    map5.put("ItemTitle", "中国顺丰派件中");
    map5.put("ItemText", "等待派件");
    itemlist.add(map5);

    map6.put("ItemTitle", "江苏理工大学已签收");
    map6.put("ItemText", "收件人:darryrzhong");
    itemlist.add(map6);

    }

    private void initView() {
    rl = findViewById(R.id.my_recycler_view);
    LinearLayoutManager manager = new LinearLayoutManager(this);
    rl.setLayoutManager(manager);
    //当知道Adapter内Item的改变不会影响RecyclerView宽高的时候,可以设置为true让RecyclerView避免重新计算大小。
    rl.setHasFixedSize(true);
    rl.addItemDecoration(new DividerItemDecoration(this));//设置自定义分割线
    MyAdapter adapter = new MyAdapter(this,itemlist);
    rl.setAdapter(adapter);
    }
    }

至此,自定义RecyclerView就实现完成了.
TIM截图20190327231820.png

参考文章:

Android 自定义View实战系列 :时间轴

欢迎关注作者darryrzhong,更多干货等你来拿哟.

请赏个小红心!因为你的鼓励是我写作的最大动力!

更多精彩文章请关注

-------------本文结束 感谢阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!