欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-11-29 13:37:22
时间轴,顾名思义就是将发生的事件按照时间顺序罗列起来,给用户带来一种更加直观的体验。京东和淘宝的物流顺序就是一个时间轴,想必大家都不陌生,如下图: 分析 实现这...

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

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

分析

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

效果图如下:

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

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