Android使用自定义View实现横行时间轴效果

前言

本篇文章会说下如何使用并且要用麻烦的自定义 view 去实现时间轴效果,以及如何分析、实现自定义 view。 需要具备的知识:Paint、Canvas、自定义 view 的绘制流程。

欢迎留言交流。

一、已经有很多 RecycleView 实现时间轴的例子,为何还要费劲的使用自定义 view 去实现时间轴?

首先看下最终想要的效果:

根据上图可以总结出以下几点:

  1. 每个阶段要显示时间、阶段名、状态图标、中间有虚线;
  2. 文字上下交错显示;
  3. 相邻阶段的文字在垂直方向上是可以相交的;
  4. 时间轴的个数不确定,但是要铺满屏幕并且不可滑动; 如果只实现上两点的效果,使用 RecycleView 无疑是最好的选择,但是要同时实现以上整个效果目前想到的最好的办法就是使用自定义 view。

二、如何开始?

相信也有人跟我一样,对自定义的绘制过程 view、canvas、path、paint 的使用有了解,但是真的要去写自定义 view 确不知道从何开始,不知道第一步如何下手。我个人的总结就是:想要的太多,迟迟不动手,所以有想法一定要去动手试验! 不要想着写完第一次运行就是最终想展示的完美效果,而是要抱着整体拆分成不重复的小块,然后去绘制重复块,然后去一点点实现一步步完美的心态才能做出来。

所以首先要把想实现的 view 拆分成一个个小的可绘制的并且没有重复的块,以目前想实现的时间轴效果来说,最小可绘制无重复块也就是只包括一个时间结点的块如图:

它包括:

  • 垂直居中的一条虚线;
  • 一个表示状态的图标;
  • 一个显示时间的文本块;
  • 一个显示阶段名的文本块;

三、开始画

有了上面的分析,接下来就要开始画了。

1. 画中间的线

首先画虚线,如果虚线不知道怎么画,可以先画一条实线,然后再去找画虚线的方法。

使用 canvas 中画线的方法 drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) 根据参数得知需知道线的起点与终点坐标以及一个 paint 对象,因为是垂直居中且横穿整个控件的直线所以可以确定两个点的 y 坐标是一样的,也就是控件高的一半,起点的 x 坐标为0,终点的 x 坐标为控件的宽。也就是知道控件的宽和高之后就可以绘制出这条线。获取控件的宽高,可以在 onMeasure 方法中获取:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  mViewWidth = MeasureSpec.getSize(widthMeasureSpec) - dip2px(mContext, mSafeDistance * 2);
  mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
}

画线的代码(在 onDraw 方法中添加,下面其它的绘制方法同样是在 onDraw 方法中添加):

// 定义画笔,并设置相关属性
Paint mLinePaint = new Paint();
mLinePaint.setColor(Color.parseColor("#999999"));
mLinePaint.setStrokeWidth(1);
mLinePaint.setStyle(Paint.Style.STROKE);
// 画虚线
canvas.drawLine(0, mViewHeight / 2, mViewWidth, mViewHeight / 2, mLinePaint);

2. 画图标

canvas 画图标的方法:drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint),根据方法的参数去分析如何准备值,这里需要一个 bitmap 对象,起点坐标以及 paint。bitmap 对象可以将资源文件 drawable 转为 bitmap 格式;坐标就是控件的中心点。画图标的代码:

// 图标 x,y 坐标
Bitmap statusBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.ic_no_pass);
float bX = mViewWidth / 2;
// 垂直的中心点在图标的顶部,所以要减去 bitmap 高的一半
float bY = mViewHeight / 2 - statusBitmap.getHeight() / 2f;
Paint mBitmapPaint = new Paint();
mBitmapPaint.setFilterBitmap(true);
canvas.drawBitmap(statusBitmap, bX, bY, mBitmapPaint);

3. 画文本

canvas 画文本的方法:drawText(@NonNull String text, float x, float y, @NonNull Paint paint),依然是根据方法得知需要知道绘制的内容,开始的坐标点以及 paint。当文本在图标上方时,文本的 y 坐标需要使用图标的 y 坐标减去文本到图标的距离,x 坐标同图片的 x 坐标一样;当文本在图标下方时,文本的 y 坐标需要使用图标的 y 坐标加上文本到图标的距离。画文本的代码:

// 定义画笔
Paint mDatePaint = new Paint();
mDatePaint.setColor(Color.parseColor("#666666"));
mDatePaint.setTextSize(dip2px(mContext, 12));
mDatePaint.setStyle(Paint.Style.FILL);
mDatePaint.setTextAlign(Paint.Align.CENTER);
mDatePaint.setAntiAlias(true);

Paint mNamePain = new Paint();
mNamePain.setColor(Color.parseColor("#666666"));
mNamePain.setTextSize(dip2px(mContext, 13));
mNamePain.setStyle(Paint.Style.FILL);
mNamePain.setTextAlign(Paint.Align.CENTER);
mNamePain.setAntiAlias(true);

// 定义坐标变量
float dateX = bX + statusBitmap.getWidth() / 2f;
float dateY;
dateY = mViewHeight / 2 - dip2px(mContext, 19);

// 画文字,在图标上
canvas.drawText("有效时间", dateX, dateY, mNamePain);
canvas.drawText("09.27-09.29", dateX, dateY - dateTextHeight mDatePaint);

// 画文字,在图标下
dateY = mViewHeight / 2 + dip2px(mContext, 19);
canvas.drawText("09.27-09.29", dateX, dateY, mDatePaint);
canvas.drawText("有效时间", dateX, dateY + dateTextHeigh, mNamePain);

4. 由局部到整体

上面已经完成了只有一个时间点的绘制,接下来思考如果有多个时间点时如何绘制。只有一个时间点时计算坐标是以控件的宽高进行计算,那么当有两个时间点的时候需要首先把控件均分成两部分,然后在均分的部分中计算对应的坐标,完成绘制。当有三个时间点的时候需要均分为三部分,然后在各自的部分计算对应的坐标,完成绘制。所以得到不论时间点的个数有多少绘制的方法不会改变,需要改变的是绘制时候用到的点的坐标。其实已经可以看出,当多个点的时候需要循环一下,代码如下:

// 得到多个点时,其中每个部分的宽,itevW 也就等同与上面只有一个时间点时控件的宽
float itemW = mViewWidth / mDataList.size();
for (int i = 0; i < mDataList.size(); i++) {
  // 完成相关计算、绘制
}

5. 完善

到这里,整个分析及绘制就差不多要结束了。

绘制虚线的方法:mLinePaint.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0));

绘制虚线时遇到一个问题,在手机上不显示虚线效果,后来查到需要关闭 view 层的硬件加速:setLayerType(View.LAYER_TYPE_SOFTWARE, null);

绘制文本时的中心点计算需要注意下

paint、bitmap 等变量的初始化建议放到初始化方法中去做,不建议在 onDraw 方法中做。

四、总结

关于自定义 view 一定要多看,多尝试。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • Android实现列表时间轴

    本文实例为大家分享了Android列表时间轴展示的具体代码,供大家参考,具体内容如下 实现的效果图如下: 实现的方式是利用recycleview的ItemDecoration这个抽象类,就是我们经常用来画分割线的的这个类, 具体如下 public class DividerItemDecoration extends RecyclerView.ItemDecoration{ // 写右边字的画笔(具体信息) private Paint mPaint; // 写左边日期字的画笔( 时间 + 日期)

  • 教你3分钟了解Android 简易时间轴的实现方法

    一.有段时间没更了,因为一直在思索,应该写点什么,真的是无比纠结.这一回,就给大家分享一款简便好用的,小编自制的土晾时间轴. 附上XML预览图: 效果图 注:小黄鸭不是效果哈,是为了保护个人隐私P上去的: 1.新建一个自定义控件: public class WorkExcView extends LinearLayout { private TextView dataLeft; private TextView dataRight; private TextView company; priva

  • Android自定义View实现垂直时间轴布局

    时间轴,顾名思义就是将发生的事件按照时间顺序罗列起来,给用户带来一种更加直观的体验.京东和淘宝的物流顺序就是一个时间轴,想必大家都不陌生,如下图: 分析 实现这个最常用的一个方法就是用ListView,我这里用继承LinearLayout的方式来实现.首先定义了一些自定义属性: attrs.xml <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable

  • Android实现快递物流时间轴效果

    本文实例为大家分享了Android实现快递物流时间轴效果展示的具体代码,供大家参考,具体内容如下 首先,这篇参考了别人的代码.根据自己的项目需求简单改造了一下,效果图如下 xml:代码 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:lay

  • Android自定义指示器时间轴效果实例代码详解

    指示器时间轴在外卖.购物类的APP里会经常用到,效果大概就像下面这样,看了网上很多文章,大都是自己绘制,太麻烦,其实通过ListView就可以实现. 在Activity关联的布局文件activity_main.xml中放置一个ListView,代码如下.由于这个列表只是用于展示信息,并不需要用户去点击,所以将其clickable属性置为false:为了消除ListView点击产生的波纹效果,我们设置其listSelector属性的值为透明:我们不需要列表项之间的分割线,所以设置其divider属

  • Android自定义view仿淘宝快递物流信息时间轴

    学了Android有一段时间了,一直没有时间写博客,趁着周末有点空,就把自己做的一些东西写下来. 一方面锻炼一下自己的写文档的能力,另一方面分享代码的同时也希望能与大家交流一下技术,共同学习,共同进步. 废话不多少说,我们先来看看我们自定义view要实现的效果: 效果图 自定义属性 <resources> <declare-styleable name="TimeLineView"> <attr name="timelineRadius"

  • Android自定义时间轴的实现过程

    本文讲述Android自定义时间轴的实现过程,供大家参考,具体内容如下 相关视频链接: Android自定义控件系列 http://edu.csdn.net/course/detail/3719/65396 Android视频全系列 http://edu.csdn.net/course/detail/2741/43163 时间轴效果,实际上非常简单,就是listView中一个又一个的条目而已-.大家可以只关注一个条目. 首先展示一个条目的布局效果 <?xml version="1.0&qu

  • Android控件之使用ListView实现时间轴效果

     实现思路: 该View是通过ListView实现的,通过实体两个字段内容content和时间time来展示每个ListItem 时间轴是使用上面一条线(20dp)和中间一个圆(15dp)和下面一条线(40dp)组装成的 在ListView中,设置其分割线为空,并且没有点击效果 效果图: 步骤一:使用xml画出一个灰色的圆点(time_cycle.xml) <?xml version="1.0" encoding="utf-8"?> <shape

  • Android使用自定义View实现横行时间轴效果

    前言 本篇文章会说下如何使用并且要用麻烦的自定义 view 去实现时间轴效果,以及如何分析.实现自定义 view. 需要具备的知识:Paint.Canvas.自定义 view 的绘制流程. 欢迎留言交流. 一.已经有很多 RecycleView 实现时间轴的例子,为何还要费劲的使用自定义 view 去实现时间轴? 首先看下最终想要的效果: 根据上图可以总结出以下几点: 每个阶段要显示时间.阶段名.状态图标.中间有虚线: 文字上下交错显示: 相邻阶段的文字在垂直方向上是可以相交的: 时间轴的个数不

  • Android通过自定义view实现刮刮乐效果详解

    前言 已经有两个月没有更新博客了,其实这篇文章我早在两个月前就写好了,一直保存在草稿箱里没有发布出来.原因是有一些原理性的东西还没了解清楚,最近抽时间研究了一下混合模式,终于也理解了刮刮乐是怎么实现的,所以想继续分享一下自己的一些心得,先上效果图. 效果图: 实现原理 其实刮刮乐实现原理也不算很复杂,最关键的还是需要了解Paint的混合模式.因为刮刮乐是由两个bitmap组成的,一个是源图另一个是目标图,我们需要把目标图的颜色改成灰色,在源图上面盖上了一张灰色的目标图.当手指滑动屏幕时paint

  • Android自定义View实现比赛时间闪动效果

    本文实例为大家分享了Android实现比赛时间闪动效果的具体代码,供大家参考,具体内容如下 效果 代码 上代码 public class TwinkleTextView extends TextView implements Runnable { /** * 是否显示 */ private boolean flag = true; /** * 闪动内容 */ private String twinkleText = "'"; /** * 闪动时间 */ private int dela

  • Android通过自定义View实现随机验证码

    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章. 一.问题描述 熟悉web开发中童鞋们都知道为了防止恶意破解.恶意提交.刷票等我们在提交表单数据时,都会使用随机验证码功能.在Android应用中我们同样需要这一功能,该如何实现呢,下面我们就自定义一个随机验证码View控件实现这一需求,并且具备通用性,需要的时候在界面中直接加入这个View组件即可. 二.案例介绍 案例运行效

  • Android如何自定义View实现横向的双水波纹进度条

    目录 思路分析 功能实现 1.绘制圆角背景和圆角矩形边框 2.通过贝塞尔曲线实现双水波 3.设置动画使进度和水波纹变化 结语 网上垂直的水波纹进度条很多,但横向的很少,将垂直的水波纹改为水平的还遇到了些麻烦,现在完善后发布出来,希望遇到的人少躺点坑. 思路分析 整体效果可分为三个,绘制圆角背景和圆角矩形,绘制第一条和第二条水波浪,根据自定义进度变化效果. 功能实现 1.绘制圆角背景和圆角矩形边框 圆角矩形边框: private RectF rectBorder; if (rectBorder =

  • Android使用自定义View绘制渐隐渐现动画

    实现了一个有趣的小东西:使用自定义View绘图,一边画线,画出的线条渐渐变淡,直到消失.效果如下图所示: 用属性动画或者渐变填充(Shader)可以做到一笔一笔的变化,但要想一笔渐变(手指不抬起边画边渐隐),没在Android中找到现成的API可用.所以,自己做了一个. 基本的想法是这样的: 在View的onTouchEvent中记录触摸点,生成一条一条的线LineElement,放在一个List中.给每个LineElement配置一个Paint实例. 在onDraw中绘制线段. 变换LineE

随机推荐