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依赖包
dependencies {
     ..........
    api 'com.android.support:recyclerview-v7:28.0.0'
}
  • 在布局文件中使用
<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布局
<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
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
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
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,更多干货等你来拿哟.

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

更多精彩文章请关注

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容