Android自定义View实现垂直时间轴布局
程序员文章站
2023-11-29 13:37:22
时间轴,顾名思义就是将发生的事件按照时间顺序罗列起来,给用户带来一种更加直观的体验。京东和淘宝的物流顺序就是一个时间轴,想必大家都不陌生,如下图:
分析
实现这...
时间轴,顾名思义就是将发生的事件按照时间顺序罗列起来,给用户带来一种更加直观的体验。京东和淘宝的物流顺序就是一个时间轴,想必大家都不陌生,如下图:
分析
实现这个最常用的一个方法就是用listview,我这里用继承linearlayout的方式来实现。首先定义了一些自定义属性:
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="timelinelayout"> <!--时间轴左偏移值--> <attr name="line_margin_left" format="dimension"/> <!--时间轴上偏移值--> <attr name="line_margin_top" format="dimension"/> <!--线宽--> <attr name="line_stroke_width" format="dimension"/> <!--线的颜色--> <attr name="line_color" format="color"/> <!--点的大小--> <attr name="point_size" format="dimension"/> <!--点的颜色--> <attr name="point_color" format="color"/> <!--图标--> <attr name="icon_src" format="reference"/> </declare-styleable> </resources>
timelinelayout.java
package com.jackie.timeline; import android.content.context; import android.content.res.typedarray; import android.graphics.bitmap; import android.graphics.canvas; import android.graphics.paint; import android.graphics.drawable.bitmapdrawable; import android.support.annotation.nullable; import android.util.attributeset; import android.view.view; import android.widget.linearlayout; /** * created by jackie on 2017/3/8. * 时间轴控件 */ public class timelinelayout extends linearlayout { private context mcontext; private int mlinemarginleft; private int mlinemargintop; private int mlinestrokewidth; private int mlinecolor;; private int mpointsize; private int mpointcolor; private bitmap micon; private paint mlinepaint; //线的画笔 private paint mpointpaint; //点的画笔 //第一个点的位置 private int mfirstx; private int mfirsty; //最后一个图标的位置 private int mlastx; private int mlasty; public timelinelayout(context context) { this(context, null); } public timelinelayout(context context, @nullable attributeset attrs) { this(context, attrs, 0); } public timelinelayout(context context, @nullable attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); typedarray ta = context.obtainstyledattributes(attrs, r.styleable.timelinelayout); mlinemarginleft = ta.getdimensionpixeloffset(r.styleable.timelinelayout_line_margin_left, 10); mlinemargintop = ta.getdimensionpixeloffset(r.styleable.timelinelayout_line_margin_top, 0); mlinestrokewidth = ta.getdimensionpixeloffset(r.styleable.timelinelayout_line_stroke_width, 2); mlinecolor = ta.getcolor(r.styleable.timelinelayout_line_color, 0xff3dd1a5); mpointsize = ta.getdimensionpixelsize(r.styleable.timelinelayout_point_size, 8); mpointcolor = ta.getdimensionpixeloffset(r.styleable.timelinelayout_point_color, 0xff3dd1a5); int iconres = ta.getresourceid(r.styleable.timelinelayout_icon_src, r.drawable.ic_ok); bitmapdrawable drawable = (bitmapdrawable) context.getresources().getdrawable(iconres); if (drawable != null) { micon = drawable.getbitmap(); } ta.recycle(); setwillnotdraw(false); initview(context); } private void initview(context context) { this.mcontext = context; mlinepaint = new paint(); mlinepaint.setantialias(true); mlinepaint.setdither(true); mlinepaint.setcolor(mlinecolor); mlinepaint.setstrokewidth(mlinestrokewidth); mlinepaint.setstyle(paint.style.fill_and_stroke); mpointpaint = new paint(); mpointpaint.setantialias(true); mpointpaint.setdither(true); mpointpaint.setcolor(mpointcolor); mpointpaint.setstyle(paint.style.fill); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); drawtimeline(canvas); } private void drawtimeline(canvas canvas) { int childcount = getchildcount(); if (childcount > 0) { if (childcount > 1) { //大于1,证明至少有2个,也就是第一个和第二个之间连成线,第一个和最后一个分别有点和icon drawfirstpoint(canvas); drawlasticon(canvas); drawbetweenline(canvas); } else if (childcount == 1) { drawfirstpoint(canvas); } } } private void drawfirstpoint(canvas canvas) { view child = getchildat(0); if (child != null) { int top = child.gettop(); mfirstx = mlinemarginleft; mfirsty = top + child.getpaddingtop() + mlinemargintop; //画圆 canvas.drawcircle(mfirstx, mfirsty, mpointsize, mpointpaint); } } private void drawlasticon(canvas canvas) { view child = getchildat(getchildcount() - 1); if (child != null) { int top = child.gettop(); mlastx = mlinemarginleft; mlasty = top + child.getpaddingtop() + mlinemargintop; //画图 canvas.drawbitmap(micon, mlastx - (micon.getwidth() >> 1), mlasty, null); } } private void drawbetweenline(canvas canvas) { //从开始的点到最后的图标之间,画一条线 canvas.drawline(mfirstx, mfirsty, mlastx, mlasty, mlinepaint); for (int i = 0; i < getchildcount() - 1; i++) { //画圆 int top = getchildat(i).gettop(); int y = top + getchildat(i).getpaddingtop() + mlinemargintop; canvas.drawcircle(mfirstx, y, mpointsize, mpointpaint); } } public int getlinemarginleft() { return mlinemarginleft; } public void setlinemarginleft(int linemarginleft) { this.mlinemarginleft = linemarginleft; invalidate(); } }
从上面的代码可以看出,分三步绘制,首先绘制开始的实心圆,然后绘制结束的图标,然后在开始和结束之间先绘制一条线,然后在线上在绘制每个步骤的实心圆。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <linearlayout android:layout_width="match_parent" android:layout_height="50dp" android:weightsum="2"> <button android:id="@+id/add_item" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="add"/> <button android:id="@+id/sub_item" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="sub"/> </linearlayout> <linearlayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightsum="2"> <button android:id="@+id/add_margin" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="+"/> <button android:id="@+id/sub_margin" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="-"/> </linearlayout> <textview android:id="@+id/current_margin" android:layout_width="match_parent" android:layout_height="40dp" android:gravity="center" android:text="current line margin left is 25dp"/> <scrollview android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none"> <com.jackie.timeline.timelinelayout android:id="@+id/timeline_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:line_margin_left="25dp" app:line_margin_top="8dp" android:orientation="vertical" android:background="@android:color/white"> </com.jackie.timeline.timelinelayout> </scrollview> </linearlayout>
mainactivity.java
package com.jackie.timeline; import android.os.bundle; import android.support.v7.app.appcompatactivity; import android.view.layoutinflater; import android.view.view; import android.widget.button; import android.widget.textview; public class mainactivity extends appcompatactivity implements view.onclicklistener { private button additembutton; private button subitembutton; private button addmarginbutton; private button submarginbutton; private textview mcurrentmargin; private timelinelayout mtimelinelayout; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initview(); } private void initview() { additembutton = (button) findviewbyid(r.id.add_item); subitembutton = (button) findviewbyid(r.id.sub_item); addmarginbutton= (button) findviewbyid(r.id.add_margin); submarginbutton= (button) findviewbyid(r.id.sub_margin); mcurrentmargin= (textview) findviewbyid(r.id.current_margin); mtimelinelayout = (timelinelayout) findviewbyid(r.id.timeline_layout); additembutton.setonclicklistener(this); subitembutton.setonclicklistener(this); addmarginbutton.setonclicklistener(this); submarginbutton.setonclicklistener(this); } private int index = 0; private void additem() { view view = layoutinflater.from(this).inflate(r.layout.item_timeline, mtimelinelayout, false); ((textview) view.findviewbyid(r.id.tv_action)).settext("步骤" + index); ((textview) view.findviewbyid(r.id.tv_action_time)).settext("2017年3月8日16:55:04"); ((textview) view.findviewbyid(r.id.tv_action_status)).settext("完成"); mtimelinelayout.addview(view); index++; } private void subitem() { if (mtimelinelayout.getchildcount() > 0) { mtimelinelayout.removeviews(mtimelinelayout.getchildcount() - 1, 1); index--; } } @override public void onclick(view v) { switch (v.getid()){ case r.id.add_item: additem(); break; case r.id.sub_item: subitem(); break; case r.id.add_margin: int currentmargin = uihelper.pxtodip(this, mtimelinelayout.getlinemarginleft()); mtimelinelayout.setlinemarginleft(uihelper.diptopx(this, ++currentmargin)); mcurrentmargin.settext("current line margin left is " + currentmargin + "dp"); break; case r.id.sub_margin: currentmargin = uihelper.pxtodip(this, mtimelinelayout.getlinemarginleft()); mtimelinelayout.setlinemarginleft(uihelper.diptopx(this, --currentmargin)); mcurrentmargin.settext("current line margin left is " + currentmargin + "dp"); break; default: break; } } }
item_timeline.xml
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingleft="65dp" android:paddingtop="20dp" android:paddingright="20dp" android:paddingbottom="20dp"> <textview android:id="@+id/tv_action" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textsize="14sp" android:textcolor="#1a1a1a" android:text="测试一"/> <textview android:id="@+id/tv_action_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textsize="12sp" android:textcolor="#8e8e8e" android:layout_below="@id/tv_action" android:layout_margintop="10dp" android:text="2017年3月8日16:49:12"/> <textview android:id="@+id/tv_action_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textsize="14sp" android:textcolor="#3dd1a5" android:layout_alignparentright="true" android:text="完成"/> </relativelayout>
附上像素工具转化的工具类:
package com.jackie.timeline; import android.content.context; /** * created by jackie on 2017/3/8. */ public final class uihelper { private uihelper() throws instantiationexception { throw new instantiationexception("this class is not for instantiation"); } /** * dip转px */ public static int diptopx(context context, float dip) { return (int) (dip * context.getresources().getdisplaymetrics().density + 0.5f); } /** * px转dip */ public static int pxtodip(context context, float pxvalue) { final float scale = context.getresources().getdisplaymetrics().density; return (int) (pxvalue / scale + 0.5f); } }
效果图如下:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。