Android自定義View實現動態垂直時間軸佈局

轉載自:https://blog.csdn.net/shineflowers/article/details/60878859

時間軸

時間軸,顧名思義就是將發生的事件按照時間順序羅列起來,給用戶帶來一種更加直觀的體驗。京東和淘寶的物流順序就是一個時間軸,想必大家都不陌生,如下圖:


分析

實現這個最常用的一個方法就是用ListView,我這裏用繼承LinearLayout的方式來實現。首先定義了一些自定義屬性:

attrs.xml

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="TimelineLayout">  
  4.         <!--時間軸左偏移值-->  
  5.         <attr name="line_margin_left" format="dimension"/>  
  6.         <!--時間軸上偏移值-->  
  7.         <attr name="line_margin_top" format="dimension"/>  
  8.         <!--線寬-->  
  9.         <attr name="line_stroke_width" format="dimension"/>  
  10.         <!--線的顏色-->  
  11.         <attr name="line_color" format="color"/>  
  12.         <!--點的大小-->  
  13.         <attr name="point_size" format="dimension"/>  
  14.         <!--點的顏色-->  
  15.         <attr name="point_color" format="color"/>  
  16.         <!--圖標-->  
  17.         <attr name="icon_src" format="reference"/>  
  18.     </declare-styleable>  
  19. </resources>  
TimelineLayout.java
[java] view plain copy
  1. package com.jackie.timeline;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.graphics.Bitmap;  
  6. import android.graphics.Canvas;  
  7. import android.graphics.Paint;  
  8. import android.graphics.drawable.BitmapDrawable;  
  9. import android.support.annotation.Nullable;  
  10. import android.util.AttributeSet;  
  11. import android.view.View;  
  12. import android.widget.LinearLayout;  
  13.   
  14. /** 
  15.  * Created by Jackie on 2017/3/8. 
  16.  * 時間軸控件 
  17.  */  
  18.   
  19. public class TimelineLayout extends LinearLayout {  
  20.     private Context mContext;  
  21.   
  22.     private int mLineMarginLeft;  
  23.     private int mLineMarginTop;  
  24.     private int mLineStrokeWidth;  
  25.     private int mLineColor;;  
  26.     private int mPointSize;  
  27.     private int mPointColor;  
  28.     private Bitmap mIcon;  
  29.   
  30.     private Paint mLinePaint;  //線的畫筆  
  31.     private Paint mPointPaint;  //點的畫筆  
  32.       
  33.   
  34.     //第一個點的位置  
  35.     private int mFirstX;  
  36.     private int mFirstY;  
  37.     //最後一個圖標的位置  
  38.     private int mLastX;  
  39.     private int mLastY;  
  40.   
  41.     public TimelineLayout(Context context) {  
  42.         this(context, null);  
  43.     }  
  44.   
  45.     public TimelineLayout(Context context, @Nullable AttributeSet attrs) {  
  46.         this(context, attrs, 0);  
  47.     }  
  48.   
  49.     public TimelineLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {  
  50.         super(context, attrs, defStyleAttr);  
  51.         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TimelineLayout);  
  52.         mLineMarginLeft = ta.getDimensionPixelOffset(R.styleable.TimelineLayout_line_margin_left, 10);  
  53.         mLineMarginTop = ta.getDimensionPixelOffset(R.styleable.TimelineLayout_line_margin_top, 0);  
  54.         mLineStrokeWidth = ta.getDimensionPixelOffset(R.styleable.TimelineLayout_line_stroke_width, 2);  
  55.         mLineColor = ta.getColor(R.styleable.TimelineLayout_line_color, 0xff3dd1a5);  
  56.         mPointSize = ta.getDimensionPixelSize(R.styleable.TimelineLayout_point_size, 8);  
  57.         mPointColor = ta.getDimensionPixelOffset(R.styleable.TimelineLayout_point_color, 0xff3dd1a5);  
  58.   
  59.         int iconRes = ta.getResourceId(R.styleable.TimelineLayout_icon_src, R.drawable.ic_ok);  
  60.         BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(iconRes);  
  61.         if (drawable != null) {  
  62.             mIcon = drawable.getBitmap();  
  63.         }  
  64.   
  65.         ta.recycle();  
  66.   
  67.         setWillNotDraw(false);  
  68.         initView(context);  
  69.     }  
  70.   
  71.     private void initView(Context context) {  
  72.         this.mContext = context;  
  73.   
  74.         mLinePaint = new Paint();  
  75.         mLinePaint.setAntiAlias(true);  
  76.         mLinePaint.setDither(true);  
  77.         mLinePaint.setColor(mLineColor);  
  78.         mLinePaint.setStrokeWidth(mLineStrokeWidth);  
  79.         mLinePaint.setStyle(Paint.Style.FILL_AND_STROKE);  
  80.   
  81.         mPointPaint = new Paint();  
  82.         mPointPaint.setAntiAlias(true);  
  83.         mPointPaint.setDither(true);  
  84.         mPointPaint.setColor(mPointColor);  
  85.         mPointPaint.setStyle(Paint.Style.FILL);  
  86.     }  
  87.   
  88.     @Override  
  89.     protected void onDraw(Canvas canvas) {  
  90.         super.onDraw(canvas);  
  91.           
  92.         drawTimeline(canvas);  
  93.     }  
  94.   
  95.     private void drawTimeline(Canvas canvas) {  
  96.         int childCount = getChildCount();  
  97.   
  98.         if (childCount > 0) {  
  99.             if (childCount > 1) {  
  100.                 //大於1,證明至少有2個,也就是第一個和第二個之間連成線,第一個和最後一個分別有點和icon  
  101.                 drawFirstPoint(canvas);  
  102.                 drawLastIcon(canvas);  
  103.                 drawBetweenLine(canvas);  
  104.             } else if (childCount == 1) {  
  105.                 drawFirstPoint(canvas);  
  106.             }  
  107.         }  
  108.     }  
  109.   
  110.     private void drawFirstPoint(Canvas canvas) {  
  111.         View child = getChildAt(0);  
  112.         if (child != null) {  
  113.             int top = child.getTop();  
  114.             mFirstX = mLineMarginLeft;  
  115.             mFirstY = top + child.getPaddingTop() + mLineMarginTop;  
  116.   
  117.             //畫圓  
  118.             canvas.drawCircle(mFirstX, mFirstY, mPointSize, mPointPaint);  
  119.         }  
  120.     }  
  121.   
  122.     private void drawLastIcon(Canvas canvas) {  
  123.         View child = getChildAt(getChildCount() - 1);  
  124.         if (child != null) {  
  125.             int top = child.getTop();  
  126.             mLastX = mLineMarginLeft;  
  127.             mLastY = top + child.getPaddingTop() + mLineMarginTop;  
  128.   
  129.             //畫圖  
  130.             canvas.drawBitmap(mIcon, mLastX - (mIcon.getWidth() >> 1), mLastY, null);  
  131.         }  
  132.     }  
  133.   
  134.     private void drawBetweenLine(Canvas canvas) {  
  135.         //從開始的點到最後的圖標之間,畫一條線  
  136.         canvas.drawLine(mFirstX, mFirstY, mLastX, mLastY, mLinePaint);  
  137.         for (int i = 0; i < getChildCount() - 1; i++) {  
  138.             //畫圓  
  139.             int top = getChildAt(i).getTop();  
  140.             int y = top + getChildAt(i).getPaddingTop() + mLineMarginTop;  
  141.             canvas.drawCircle(mFirstX, y, mPointSize, mPointPaint);  
  142.         }  
  143.     }  
  144.   
  145.     public int getLineMarginLeft() {  
  146.         return mLineMarginLeft;  
  147.     }  
  148.   
  149.     public void setLineMarginLeft(int lineMarginLeft) {  
  150.         this.mLineMarginLeft = lineMarginLeft;  
  151.         invalidate();  
  152.     }  
  153. }  
從上面的代碼可以看出,分三步繪製,首先繪製開始的實心圓,然後繪製結束的圖標,然後在開始和結束之間先繪製一條線,然後在線上在繪製每個步驟的實心圓。

activity_main.xml

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical">  
  7.   
  8.     <LinearLayout  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="50dp"  
  11.         android:weightSum="2">  
  12.   
  13.         <Button  
  14.             android:id="@+id/add_item"  
  15.             android:layout_width="0dp"  
  16.             android:layout_height="match_parent"  
  17.             android:layout_weight="1"  
  18.             android:text="add"/>  
  19.   
  20.         <Button  
  21.             android:id="@+id/sub_item"  
  22.             android:layout_width="0dp"  
  23.             android:layout_height="match_parent"  
  24.             android:layout_weight="1"  
  25.             android:text="sub"/>  
  26.     </LinearLayout>  
  27.   
  28.     <LinearLayout  
  29.         android:layout_width="match_parent"  
  30.         android:layout_height="wrap_content"  
  31.         android:orientation="horizontal"  
  32.         android:weightSum="2">  
  33.   
  34.         <Button  
  35.             android:id="@+id/add_margin"  
  36.             android:layout_width="0dp"  
  37.             android:layout_weight="1"  
  38.             android:layout_height="wrap_content"  
  39.             android:text="+"/>  
  40.   
  41.         <Button  
  42.             android:id="@+id/sub_margin"  
  43.             android:layout_width="0dp"  
  44.             android:layout_weight="1"  
  45.             android:layout_height="wrap_content"  
  46.             android:text="-"/>  
  47.     </LinearLayout>  
  48.   
  49.     <TextView  
  50.         android:id="@+id/current_margin"  
  51.         android:layout_width="match_parent"  
  52.         android:layout_height="40dp"  
  53.         android:gravity="center"  
  54.         android:text="current line margin left is 25dp"/>  
  55.   
  56.     <ScrollView  
  57.         android:layout_width="match_parent"  
  58.         android:layout_height="wrap_content"  
  59.         android:scrollbars="none">  
  60.   
  61.         <com.jackie.timeline.TimelineLayout  
  62.             android:id="@+id/timeline_layout"  
  63.             android:layout_width="match_parent"  
  64.             android:layout_height="wrap_content"  
  65.             app:line_margin_left="25dp"  
  66.             app:line_margin_top="8dp"  
  67.             android:orientation="vertical"  
  68.             android:background="@android:color/white">  
  69.         </com.jackie.timeline.TimelineLayout>  
  70.     </ScrollView>  
  71. </LinearLayout>  
MainActivity.java
[java] view plain copy
  1. package com.jackie.timeline;  
  2.   
  3. import android.os.Bundle;  
  4. import android.support.v7.app.AppCompatActivity;  
  5. import android.view.LayoutInflater;  
  6. import android.view.View;  
  7. import android.widget.Button;  
  8. import android.widget.TextView;  
  9.   
  10. public class MainActivity extends AppCompatActivity implements View.OnClickListener {  
  11.     private Button addItemButton;  
  12.     private Button subItemButton;  
  13.     private Button addMarginButton;  
  14.     private Button subMarginButton;  
  15.     private TextView mCurrentMargin;  
  16.   
  17.     private TimelineLayout mTimelineLayout;  
  18.   
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.activity_main);  
  23.   
  24.         initView();  
  25.     }  
  26.   
  27.     private void initView() {  
  28.         addItemButton = (Button) findViewById(R.id.add_item);  
  29.         subItemButton = (Button) findViewById(R.id.sub_item);  
  30.         addMarginButton= (Button) findViewById(R.id.add_margin);  
  31.         subMarginButton= (Button) findViewById(R.id.sub_margin);  
  32.         mCurrentMargin= (TextView) findViewById(R.id.current_margin);  
  33.         mTimelineLayout = (TimelineLayout) findViewById(R.id.timeline_layout);  
  34.   
  35.         addItemButton.setOnClickListener(this);  
  36.         subItemButton.setOnClickListener(this);  
  37.         addMarginButton.setOnClickListener(this);  
  38.         subMarginButton.setOnClickListener(this);  
  39.     }  
  40.   
  41.     private int index = 0;  
  42.     private void addItem() {  
  43.         View view = LayoutInflater.from(this).inflate(R.layout.item_timeline, mTimelineLayout, false);  
  44.         ((TextView) view.findViewById(R.id.tv_action)).setText("步驟" + index);  
  45.         ((TextView) view.findViewById(R.id.tv_action_time)).setText("2017年3月8日16:55:04");  
  46.         ((TextView) view.findViewById(R.id.tv_action_status)).setText("完成");  
  47.         mTimelineLayout.addView(view);  
  48.         index++;  
  49.     }  
  50.   
  51.     private void subItem() {  
  52.         if (mTimelineLayout.getChildCount() > 0) {  
  53.             mTimelineLayout.removeViews(mTimelineLayout.getChildCount() - 11);  
  54.             index--;  
  55.         }  
  56.     }  
  57.   
  58.     @Override  
  59.     public void onClick(View v) {  
  60.         switch (v.getId()){  
  61.             case R.id.add_item:  
  62.                 addItem();  
  63.                 break;  
  64.             case R.id.sub_item:  
  65.                 subItem();  
  66.                 break;  
  67.             case R.id.add_margin:  
  68.                 int currentMargin = UIHelper.pxToDip(this, mTimelineLayout.getLineMarginLeft());  
  69.                 mTimelineLayout.setLineMarginLeft(UIHelper.dipToPx(this, ++currentMargin));  
  70.                 mCurrentMargin.setText("current line margin left is " + currentMargin + "dp");  
  71.                 break;  
  72.             case R.id.sub_margin:  
  73.                 currentMargin = UIHelper.pxToDip(this, mTimelineLayout.getLineMarginLeft());  
  74.                 mTimelineLayout.setLineMarginLeft(UIHelper.dipToPx(this, --currentMargin));  
  75.                 mCurrentMargin.setText("current line margin left is " + currentMargin + "dp");  
  76.                 break;  
  77.             default:  
  78.                 break;  
  79.         }  
  80.     }  
  81. }  

item_timeline.xml

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="wrap_content"  
  6.     android:paddingLeft="65dp"  
  7.     android:paddingTop="20dp"  
  8.     android:paddingRight="20dp"  
  9.     android:paddingBottom="20dp">  
  10.   
  11.     <TextView  
  12.         android:id="@+id/tv_action"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:textSize="14sp"  
  16.         android:textColor="#1a1a1a"  
  17.         android:text="測試一"/>  
  18.   
  19.     <TextView  
  20.         android:id="@+id/tv_action_time"  
  21.         android:layout_width="wrap_content"  
  22.         android:layout_height="wrap_content"  
  23.         android:textSize="12sp"  
  24.         android:textColor="#8e8e8e"  
  25.         android:layout_below="@id/tv_action"  
  26.         android:layout_marginTop="10dp"  
  27.         android:text="2017年3月8日16:49:12"/>  
  28.   
  29.     <TextView  
  30.         android:id="@+id/tv_action_status"  
  31.         android:layout_width="wrap_content"  
  32.         android:layout_height="wrap_content"  
  33.         android:textSize="14sp"  
  34.         android:textColor="#3dd1a5"  
  35.         android:layout_alignParentRight="true"  
  36.         android:text="完成"/>  
  37.   
  38. </RelativeLayout>  
附上像素工具轉化的工具類:
[java] view plain copy
  1. package com.jackie.timeline;  
  2.   
  3. import android.content.Context;  
  4.   
  5. /** 
  6.  * Created by Jackie on 2017/3/8. 
  7.  */  
  8. public final class UIHelper {  
  9.   
  10.     private UIHelper() throws InstantiationException {  
  11.         throw new InstantiationException("This class is not for instantiation");  
  12.     }  
  13.   
  14.     /** 
  15.      * dip轉px 
  16.      */  
  17.     public static int dipToPx(Context context, float dip) {  
  18.         return (int) (dip * context.getResources().getDisplayMetrics().density + 0.5f);  
  19.     }  
  20.   
  21.     /** 
  22.      * px轉dip 
  23.      */  
  24.     public static int pxToDip(Context context, float pxValue) {  
  25.         final float scale = context.getResources().getDisplayMetrics().density;  
  26.         return (int) (pxValue / scale + 0.5f);  
  27.     }  
  28. }  
效果圖如下:

    


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章