当前位置: 移动技术网 > 移动技术>移动开发>Android > Android自定义View实现垂直时间轴布局

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

2019年07月24日  | 移动技术网移动技术  | 我要评论

时间轴,顾名思义就是将发生的事件按照时间顺序罗列起来,给用户带来一种更加直观的体验。京东和淘宝的物流顺序就是一个时间轴,想必大家都不陌生,如下图:

分析

实现这个最常用的一个方法就是用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); 
  } 
} 

效果图如下:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网