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

  • Android开发中,时间轴的 UI需求非常常见,如下图:

  • 本文将结合 自定义View & RecyclerView的知识,手把手教你实现该常见 & 实用的自定义View:时间轴


目录

目录


1. 知识储备

本文采用 自定义View & RecyclerView 实现时间轴,所以必须先了解相关知识:

1.1 RecyclerView

  • RecyclerView 的基础使用:Android开发:ListView、AdapterView、RecyclerView全面解析

  • RecyclerView 的高级使用:教你玩转 Android RecyclerView:深入解析 RecyclerView.ItemDecoration类(含实例讲解)

1.2 自定义View

具体请看文章 Canvas类的最全面详解 - 自定义View应用系列


2. 具体实现

下面,我将手把手教你实现 时光轴的效果。

2.1 效果图

示意图

2.2 实现思路

示意图

2.3 实现步骤

  1. 导入 使用 RecyclerView的包
  2. 设置主布局 & RecyclerViewItem布局
  3. 设置RecyclerView的 Adapter
  4. 自定义RecyclerView.ItemDecoration
  5. 初始化 RecyclerView & 绑定数据

特别注意

  1. 步骤1、2、3、5都用到RecyclerView的基本知识,请看文章Android开发:ListView、AdapterView、RecyclerView全面解析
  2. 步骤 4 涉及到RecyclerView 高级使用 & 自定义View的知识,具体请看Canvas类的最全面详解 - 自定义View应用系列 & 教你玩转 Android RecyclerView:深入解析 RecyclerView.ItemDecoration类(含实例讲解)

2.4 步骤说明

** 步骤1:导入 使用 RecyclerView的包**

build.gradle

dependencies {...compile 'com.android.support:recyclerview-v7:23.2.0'
}

步骤2:设置主布局 & RecyclerViewItem布局
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="${relativePackage}.${activityClass}" ><android.support.v7.widget.RecyclerViewandroid:id="@+id/my_recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"android:scrollbars="horizontal"/>
</RelativeLayout>

list_cell.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="10sp"android:text="New Text"android:id="@+id/Itemtitle" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="New Text"android:textSize="10sp"android:id="@+id/Itemtext"android:layout_below="@+id/Itemtitle"/></LinearLayout>

步骤3:设置RecyclerView的 Adapter
MyAdapter.java


public class MyAdapter extends RecyclerView.Adapter {private LayoutInflater inflater;private ArrayList<HashMap<String, Object>> listItem;//构造函数,传入数据public MyAdapter(Context context, ArrayList<HashMap<String, Object>> listItem) {inflater = LayoutInflater.from(context);this.listItem = listItem;}//定义Viewholderclass Viewholder extends RecyclerView.ViewHolder  {private TextView Title, Text;public Viewholder(View root) {super(root);Title = (TextView) root.findViewById(R.id.Itemtitle);Text = (TextView) root.findViewById(R.id.Itemtext);}public TextView getTitle() {return Title;}public TextView getText() {return Text;}}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {return new Viewholder(inflater.inflate(R.layout.list_cell, null));}//在这里把ViewHolder绑定Item的布局@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {Viewholder vh = (Viewholder) holder;// 绑定数据到ViewHolder里面vh.Title.setText((String) listItem.get(position).get("ItemTitle"));vh.Text.setText((String) listItem.get(position).get("ItemText"));}//返回Item数目@Overridepublic int getItemCount() {return listItem.size();}}

步骤4:自定义RecyclerView.ItemDecoration

  • 此步骤就是该实例的实现关键,具体思路请看下图:

示意图

阅读前请先看文章:教你玩转 Android RecyclerView:深入解析 RecyclerView.ItemDecoration类(含实例讲解)

  • 具体代码实现

DividerItemDecoration.java


public class DividerItemDecoration extends RecyclerView.ItemDecoration {// 写右边字的画笔(具体信息)private Paint mPaint;// 写左边日期字的画笔( 时间 + 日期)private Paint mPaint1;private Paint mPaint2;// 左 上偏移长度private int itemView_leftinterval;private int itemView_topinterval;// 轴点半径private int circle_radius;// 图标private 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);// 赋值ItemView的左偏移长度为200itemView_leftinterval = 200;// 赋值ItemView的上偏移长度为50itemView_topinterval = 50;// 赋值轴点圆的半径为10circle_radius = 10;// 获取图标资源// mIcon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.logo);}// 重写getItemOffsets()方法// 作用:设置ItemView 左 & 上偏移长度@Overridepublic void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {super.getItemOffsets(outRect, view, parent, state);// 设置ItemView的左 & 上偏移长度分别为200 px & 50px,即此为onDraw()可绘制的区域outRect.set(itemView_leftinterval, itemView_topinterval, 0, 0);}// 重写onDraw()// 作用:在间隔区域里绘制时光轴线 & 时间文本@Overridepublic void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {super.onDraw(c, parent, state);// 获取RecyclerView的Child view的个数int childCount = parent.getChildCount();// 遍历每个Item,分别获取它们的位置信息,然后再绘制对应的分割线for (int i = 0; i < childCount; i++) {// 获取每个Item对象View child = parent.getChildAt(i);/*** 绘制轴点*/// 轴点 = 圆 = 圆心(x,y)float centerx = child.getLeft() - itemView_leftinterval / 3;float centery = child.getTop() - itemView_topinterval + (itemView_topinterval + child.getHeight()) / 2;// 绘制轴点圆c.drawCircle(centerx, centery, circle_radius, mPaint);// 通过Canvas绘制角标// c.drawBitmap(mIcon,centerx - circle_radius ,centery - circle_radius,mPaint);/*** 绘制上半轴线*/// 上端点坐标(x,y)float upLine_up_x = centerx;float upLine_up_y = child.getTop() - itemView_topinterval;// 下端点坐标(x,y)float upLine_bottom_x = centerx;float upLine_bottom_y = centery - circle_radius;//绘制上半部轴线c.drawLine(upLine_up_x, upLine_up_y, upLine_bottom_x, upLine_bottom_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 = child.getBottom();//绘制下半部轴线c.drawLine(bottomLine_up_x, bottom_up_y, bottomLine_bottom_x, bottomLine_bottom_y, mPaint);/*** 绘制左边时间文本*/// 获取每个Item的位置int index = parent.getChildAdapterPosition(child);// 设置文本起始坐标float Text_x = child.getLeft() - itemView_leftinterval * 5 / 6;float Text_y = upLine_bottom_y;// 根据Item位置设置时间文本switch (index) {case (0):// 设置日期绘制位置c.drawText("13:40", Text_x, Text_y, mPaint1);c.drawText("2017.4.03", Text_x + 5, Text_y + 20, mPaint2);break;case (1):// 设置日期绘制位置c.drawText("17:33", Text_x, Text_y, mPaint1);c.drawText("2017.4.03", Text_x + 5, Text_y + 20, mPaint2);break;case (2):// 设置日期绘制位置c.drawText("20:13", Text_x, Text_y, mPaint1);c.drawText("2017.4.03", Text_x + 5, Text_y + 20, mPaint2);break;case (3):// 设置日期绘制位置c.drawText("11:40", Text_x, Text_y, mPaint1);c.drawText("2017.4.04", Text_x + 5, Text_y + 20, mPaint2);break;case (4):// 设置日期绘制位置c.drawText("13:20", Text_x, Text_y, mPaint1);c.drawText("2017.4.04", Text_x + 5, Text_y + 20, mPaint2);break;case (5):// 设置日期绘制位置c.drawText("22:40", Text_x, Text_y, mPaint1);c.drawText("2017.4.04", Text_x + 5, Text_y + 20, mPaint2);break;default:c.drawText("已签收", Text_x, Text_y, mPaint1);}}}}

** 步骤5:初始化RecyclerView & 绑定数据 **

MainActivity.java


public class MainActivity extends AppCompatActivity  {private RecyclerView Rv;private ArrayList<HashMap<String,Object>> listItem;private MyAdapter myAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化显示的数据initData();// 绑定数据到RecyclerViewinitView();}// 初始化显示的数据public void initData(){listItem = 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", "发件人:谷歌 CEO Sundar Pichai");listItem.add(map1);map2.put("ItemTitle", "国际顺丰已收入");map2.put("ItemText", "等待中转");listItem.add(map2);map3.put("ItemTitle", "国际顺丰转件中");map3.put("ItemText", "下一站中国");listItem.add(map3);map4.put("ItemTitle", "中国顺丰已收入");map4.put("ItemText", "下一站广州华南理工大学");listItem.add(map4);map5.put("ItemTitle", "中国顺丰派件中");map5.put("ItemText", "等待派件");listItem.add(map5);map6.put("ItemTitle", "华南理工大学已签收");map6.put("ItemText", "收件人:Carson");listItem.add(map6);}// 绑定数据到RecyclerViewpublic void initView(){Rv = (RecyclerView) findViewById(R.id.my_recycler_view);//使用线性布局LinearLayoutManager layoutManager = new LinearLayoutManager(this);Rv.setLayoutManager(layoutManager);Rv.setHasFixedSize(true);//用自定义分割线类设置分割线Rv.addItemDecoration(new DividerItemDecoration(this));//为ListView绑定适配器myAdapter = new MyAdapter(this,listItem);Rv.setAdapter(myAdapter);}}

2.5 结果展示

示意图

2.6 源码地址

Carson_Ho的Github地址:自定义View实践 - 时间轴

希望大家动动手指给个 Star 呗, 嘻嘻!


3. 扩展使用

  • 此次的扩展使用是为了更加丰富UI效果:将轴点圆圈改成图标,如下图:

示意图

  • 代码实现

      private Bitmap mIcon;// 获取图标资源mIcon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.logo);// 在步骤4中,绘制轴点圆圈处 通过Canvas绘制该图c.drawBitmap(mIcon,centerx - circle_radius ,centery - circle_radius,mPaint);

4. 总结

  • 通过本文,相信你的自定义 View 库又多了一把杀器了!
  • 下一篇文章我将对讲解Android 自定义 View相关知识,有兴趣可以继续关注Carson_Ho的安卓开发笔记

请点赞!因为你的鼓励是我写作的最大动力!

Android 系统(201)---Android 自定义View实战系列 :时间轴相关推荐

  1. [自定义控件]android自定义view实战之太极图

    android自定义view实战之太极图 尊重原创,转载请注明出处: http://blog.csdn.net/qq137722697 自定义view是Android工程师进阶不可避免要接触的,我的学 ...

  2. 【Android自定义View实战】之自定义评价打分控件RatingBar,可以自定义星星大小和间距...

    [Android自定义View实战]之自定义评价打分控件RatingBar,可以自定义星星大小和间距

  3. android 行布局选择器,『自定义View实战』—— 银行种类选择器

    在工作中难免遇到自定义 View 的相关需求,本身这方面比较薄弱,因此做个记录,也是自己学习和成长的积累.自定义View实战 前言 年前的最后一个开发需求,将之前H5开卡界面转变成native.意思就 ...

  4. android 选择银行类型,『自定义View实战』—— 银行种类选择器

    在工作中难免遇到自定义 View 的相关需求,本身这方面比较薄弱,因此做个记录,也是自己学习和成长的积累.自定义View实战 前言 年前的最后一个开发需求,将之前H5开卡界面转变成native.意思就 ...

  5. Android进阶之自定义View实战(二)九宫格手势解锁实现

    一.引言 在上篇博客Android进阶之自定义View实战(一)仿iOS UISwitch控件实现中我们主要介绍了自定义View的最基本的实现方法.作为自定义View的入门篇,仅仅介绍了Canvas的 ...

  6. Android动画特效之自定义View

      Android动画特效之Animator属性动画实现_Angel-杭州的博客-CSDN博客   我在百忙之中抽出宝贵时间来实现Android动画特效,也就是Android Animator动画效果 ...

  7. Android实现雪花特效自定义view

    一.前言 这个冬天,老家一直没有下雨, 正好圣诞节,就想着制作一个下雪的特效. 圣诞祝福:平安夜,舞翩阡.雪花飘,飞满天.心与心,永相伴. 圣诞节是传统的宗教节日,对于基 督徒,那是庆祝耶稣的诞生,纪 ...

  8. Android 气泡动画(自定义View类)

    Android 气泡动画(自定义View类) 一.前言 二.代码 1. 随机移动的气泡 2.热水气泡 一.前言 最近有需求制作一个水壶的气泡动画,首先在网上查找了一番,找到了一个文章. https:/ ...

  9. Android系统架构-[Android取经之路]

    摘要:本节主要来讲解Android的系统架构 阅读本文大约需要花费10分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢 ...

最新文章

  1. 拼接召回在飞猪交通域的实践
  2. golang 中的 init 和 main函数
  3. 从open系统调用的源码看文件的打开过程
  4. Spring MVC:带有CNVR卷的REST应用程序。 1个
  5. 长生不死、名人复活?疯狂的AI时代,人类竟要靠IA实现“永生”
  6. html 选中变颜色变化,如何防止HTML中的选项中的颜色变化以及选中的元素在html中被选中并失去焦点?...
  7. BZOJ 1486 最小圈(二分+判负环)
  8. 思维导图做会议记录丨做年终终结都都都很合适
  9. 给群联PS3111/inic6081量产工具添加闪存颗粒支持
  10. canvas 踩坑 * 小球弹性碰撞逻辑解析
  11. 计算长方体、四棱锥的表面积和体积
  12. 关于区块链安全方面的文献
  13. 尚学堂怎么样?给你讲讲我的亲身经历
  14. 父亲发现高三女儿早恋 机智做法让网友惊呆
  15. 解决mac BigSur外接显示器发白、发黄、字体发虚 (OpenCore关闭SIP和Read-Only System)
  16. 管理者的人品的重要性
  17. Linux中write命令------实现用户间信息传递
  18. 2.flex 容器属性 flex-direction ,flex-wrap ,flex-flow
  19. 输出100-200之间所有的素数(素数:只能被1和自己本身整除的数)
  20. 【全排列代码】这可能是全网最详细的解析了

热门文章

  1. ubuntu12.04装机后设置
  2. 基于alsa的音量控制代码
  3. GitHub使用入门讲解--官方文档翻译让你最真实了解
  4. 计算n的阶乘以及n个阶乘相加
  5. mysql 常用命令集_Mysql 常用命令集
  6. php mysql.dll 下载_php_mysql.dll下载|
  7. 计算机应用基础的文档,计算机应用基础
  8. 【JAVA SE】第十三章 序列化与反序列化
  9. C++网易云课堂开发工程师--转换函数
  10. Mybatis框架简单使用