1. 程式人生 > >Android開發筆記之自定義控制元件(物流時間軸的實現)

Android開發筆記之自定義控制元件(物流時間軸的實現)

最近修改專案遇到檢視物流這個需求,經過一個下午的時間的終於搞定,趁著這個時間點,趕快把這個功能抽取出來,方便大家以後開發的需要,幫助到更多的人
先看效果圖,如下
這裡寫圖片描述
看完之後,分析可知道,主要是兩部分,一個頭部和一個body.
那我們最主要的工作就是body內容的實現,頭部的實現簡單,這裡就不再詳細的說明
這裡我給大家提供一個github上的開源專案,不過這個實現起來,繪製的效果比較慢,不過也可以實現相同的效果
github作者的部落格地址

接下來我們就詳細的講解我的實現於哪裡

首先我們自定義個類繼承自LinearLayout控制元件,
繼承幾個構造方法

public class
UnderLineLinearLayout extends LinearLayout{
public UnderLineLinearLayout(Context context) {this(context, null);} public UnderLineLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public UnderLineLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super
(context,attrs,defStyleAttr);}

我們看時間軸,可以分析得到,原點分為綠色和灰色兩種狀態,字型也是如此
初始化一些引數和使用工具

  //=============================================================line gravity常量定義
    public static final int GRAVITY_LEFT = 2;
    public static final int GRAVITY_RIGHT = 4;
    public static final int GRAVITY_MIDDLE =0;
    public
static final int GRAVITY_TOP = 1; public static final int GRAVITY_BOTTOM = 3; //=============================================================元素定義 private Bitmap mIcon; //line location private int lineMarginSide; private int lineDynamicDimen; //line property private int lineStrokeWidth; private int lineColor; //point property private int pointSize; private int pointColor; //=============================================================paint private Paint linePaint; private Paint pointPaint; //=============================================================其他輔助引數 //第一個點的位置 private int firstX; private int firstY; //最後一個圖的位置 private int lastX; private int lastY; //預設垂直 private int curOrientation = VERTICAL; //line gravity(預設垂直的左邊) private int lineGravity = GRAVITY_LEFT; private Context mContext; //開關 private boolean drawLine = true; private int rootLeft; private int rootMiddle; private int rootRight; private int rootTop; private int rootBottom; //參照點 private int sideRelative;

整個的原始碼如下

public class UnderLineLinearLayout extends LinearLayout {
    //=============================================================line gravity常量定義
    public static final int GRAVITY_LEFT = 2;
    public static final int GRAVITY_RIGHT = 4;
    public static final int GRAVITY_MIDDLE =0;
    public static final int GRAVITY_TOP = 1;
    public static final int GRAVITY_BOTTOM = 3;
    //=============================================================元素定義
    private Bitmap mIcon;
    //line location
    private int lineMarginSide;
    private int lineDynamicDimen;
    //line property
    private int lineStrokeWidth;
    private int lineColor;
    //point property
    private int pointSize;
    private int pointColor;

    //=============================================================paint
    private Paint linePaint;
    private Paint pointPaint;
    //=============================================================其他輔助引數
    //第一個點的位置
    private int firstX;
    private int firstY;
    //最後一個圖的位置
    private int lastX;
    private int lastY;
    //預設垂直
    private int curOrientation = VERTICAL;

    //line gravity(預設垂直的左邊)
    private int lineGravity = GRAVITY_LEFT;

    private Context mContext;

    //開關
    private boolean drawLine = true;

    private int rootLeft;
    private int rootMiddle;
    private int rootRight;
    private int rootTop;
    private int rootBottom;
    //參照點
    private int sideRelative;

    public UnderLineLinearLayout(Context context) {
        this(context, null);
    }

    public UnderLineLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public UnderLineLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.UnderLineLinearLayout);
        lineMarginSide = attr.getDimensionPixelOffset(R.styleable.UnderLineLinearLayout_line_margin_side, 10);
        lineDynamicDimen = attr.getDimensionPixelOffset(R.styleable.UnderLineLinearLayout_line_dynamic_dimen, 0);
        lineStrokeWidth = attr.getDimensionPixelOffset(R.styleable.UnderLineLinearLayout_line_stroke_width, 2);
        lineColor = attr.getColor(R.styleable.UnderLineLinearLayout_line_v_color, 0xff3dd1a5);
        pointSize = attr.getDimensionPixelSize(R.styleable.UnderLineLinearLayout_point_size, 8);
        pointColor = attr.getColor(R.styleable.UnderLineLinearLayout_point_color, 0xff3dd1a5);
        lineGravity = attr.getInt(R.styleable.UnderLineLinearLayout_line_gravity, GRAVITY_LEFT);

        int iconRes = attr.getResourceId(R.styleable.UnderLineLinearLayout_icon_src, R.drawable.point);

        // mIcon = BitmapFactory.decodeResource(context.getResources(), iconRes);

        Drawable temp = context.getResources().getDrawable(iconRes);

        if (drawableToBitmap(temp)!=null) mIcon=drawableToBitmap(temp);

        // BitmapDrawable bd = (BitmapDrawable) temp;
        // Bitmap bm = bd.getBitmap();
        // if (bm != null) mIcon = bm;

        curOrientation = getOrientation();
        attr.recycle();
        setWillNotDraw(false);
        initView(context);
    }

    public static Bitmap drawableToBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(
            drawable.getIntrinsicWidth(),
            drawable.getIntrinsicHeight(),
            drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);
        return bitmap;
    }

    private void initView(Context context) {
        this.mContext = context;

        linePaint = new Paint();
        linePaint.setAntiAlias(true);
        linePaint.setDither(true);
        linePaint.setColor(lineColor);
        linePaint.setStrokeWidth(lineStrokeWidth);
        linePaint.setStyle(Paint.Style.FILL_AND_STROKE);

        pointPaint = new Paint();
        pointPaint.setAntiAlias(true);
        pointPaint.setDither(true);
        pointPaint.setColor(pointColor);
        pointPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        calculateSideRelative();
        if (drawLine) {
            drawTimeLine(canvas);
        }
    }

    private void calculateSideRelative() {
        rootLeft = getLeft();
        rootTop = getTop();
        rootRight = getRight();
        rootBottom = getBottom();
        if (curOrientation == VERTICAL) rootMiddle = (rootLeft + rootRight) >> 1;
        if (curOrientation == HORIZONTAL) rootMiddle = (rootTop + rootBottom) >> 1;

        boolean isCorrect=(lineGravity==GRAVITY_MIDDLE||(lineGravity+curOrientation)%2!=0);
        if (isCorrect){
            switch (lineGravity){
                case GRAVITY_TOP:
                    sideRelative=rootTop;
                    break;
                case GRAVITY_BOTTOM:
                    sideRelative=rootBottom;
                    break;
                case GRAVITY_LEFT:
                    sideRelative=rootLeft;
                    break;
                case GRAVITY_RIGHT:
                    sideRelative=rootRight;
                    break;
                case GRAVITY_MIDDLE:
                    sideRelative=rootMiddle;
                    break;
            }
        }else {
            sideRelative=0;
        }
    }

    private void drawTimeLine(Canvas canvas) {
        int childCount = getChildCount();

        if (childCount > 0) {
            //大於1,證明至少有2個,也就是第一個和第二個之間連成線,第一個和最後一個分別有點/icon
            if (childCount > 1) {
                switch (curOrientation) {
                    case VERTICAL:
                        drawFirstChildViewVertical(canvas);
                        drawLastChildViewVertical(canvas);
                        drawBetweenLineVertical(canvas,childCount);
                        break;
                    case HORIZONTAL:
                        drawFirstChildViewHorizontal(canvas);
                        drawLastChildViewHorizontal(canvas);
                        drawBetweenLineHorizontal(canvas);
                        break;
                    default:
                        break;
                }
            }
            else if (childCount == 1) {
                switch (curOrientation) {
                    case VERTICAL:
                        drawFirstChildViewVertical(canvas);
                        break;
                    case HORIZONTAL:
                        drawFirstChildViewHorizontal(canvas);
                        break;
                    default:
                        break;
                }
            }
        }
    }

    //=============================================================Vertical Draw
    private void drawFirstChildViewVertical(Canvas canvas) {

        if (getChildAt(0) != null) {
            int top = getChildAt(0).getTop();
            //記錄值
            lastX = (sideRelative>=rootMiddle?(sideRelative-lineMarginSide):(sideRelative+lineMarginSide)) - (mIcon
                .getWidth() >> 1);
            lastY = top + getChildAt(0).getPaddingTop() + lineDynamicDimen;
            //畫一個圖
            canvas.drawBitmap(mIcon, lastX, lastY, null);
        }
    }

    private void drawLastChildViewVertical(Canvas canvas) {
        if (getChildAt(getChildCount()-1) != null) {
            int top = getChildAt(getChildCount()-1).getTop();
            //記錄值
            firstX = sideRelative>=rootMiddle?(sideRelative-lineMarginSide):(sideRelative+lineMarginSide);
            firstY = top + getChildAt(getChildCount()-1).getPaddingTop() + lineDynamicDimen;
            //畫一個圓
            canvas.drawCircle(firstX, firstY, pointSize, pointPaint);
        }
    }

    private void drawBetweenLineVertical(Canvas canvas, int childCount) {
        //畫剩下的

        for (int i = 0; i < getChildCount() - 1; i++) {
            //畫了線,就畫圓
            if (getChildAt(i) != null && i != 0) {
                int top = getChildAt(i).getTop();
                int Y = top + getChildAt(i).getPaddingTop() + lineDynamicDimen;
                //記錄值
                canvas.drawCircle(firstX, Y, pointSize, pointPaint);
            }

            // if (i==0){
                canvas.drawLine(firstX, firstY, firstX, lastY+mIcon.getHeight(), linePaint);
            //
            // }else {
            //     canvas.drawLine(firstX, firstY, firstX, lastY, linePaint);
            // }
        }
    }

    //=============================================================Horizontal Draw
    private void drawFirstChildViewHorizontal(Canvas canvas) {
        if (getChildAt(0) != null) {
            int left = getChildAt(0).getLeft();
            //記錄值
            firstX = left + getChildAt(0).getPaddingLeft() + lineDynamicDimen;
            firstY = sideRelative>=rootMiddle?(sideRelative-lineMarginSide):(sideRelative+lineMarginSide);
            //畫一個圓
            canvas.drawCircle(firstX, firstY, pointSize, pointPaint);
        }
    }

    private void drawLastChildViewHorizontal(Canvas canvas) {
        if (getChildAt(getChildCount() - 1) != null) {
            int left = getChildAt(getChildCount() - 1).getLeft();
            //記錄值
            lastX = left + getChildAt(getChildCount() - 1).getPaddingLeft() + lineDynamicDimen;
            lastY = (sideRelative>=rootMiddle?(sideRelative-lineMarginSide):(sideRelative+lineMarginSide)) - (mIcon
                .getWidth() >> 1);
            //畫一個圖
            canvas.drawBitmap(mIcon, lastX, lastY, null);
        }
    }

    private void drawBetweenLineHorizontal(Canvas canvas) {
        //畫剩下的線
        canvas.drawLine(firstX, firstY, lastX, firstY, linePaint);
        for (int i = 0; i < getChildCount() - 1; i++) {
            //畫了線,就畫圓
            if (getChildAt(i) != null && i != 0) {
                int left = getChildAt(i).getLeft();
                //記錄值
                int x = left + getChildAt(i).getPaddingLeft() + lineDynamicDimen;
                canvas.drawCircle(x, firstY, pointSize, pointPaint);
            }
        }
    }

    //=============================================================Getter/Setter

    @Override
    public void setOrientation(int orientation) {
        super.setOrientation(orientation);
        this.curOrientation = orientation;
        invalidate();
    }

    public int getLineStrokeWidth() {
        return lineStrokeWidth;
    }

    public void setLineStrokeWidth(int lineStrokeWidth) {
        this.lineStrokeWidth = lineStrokeWidth;
        invalidate();
    }

    public boolean isDrawLine() {
        return drawLine;
    }

    public void setDrawLine(boolean drawLine) {
        this.drawLine = drawLine;
        invalidate();
    }

    public Paint getLinePaint() {
        return linePaint;
    }

    public void setLinePaint(Paint linePaint) {
        this.linePaint = linePaint;
        invalidate();
    }

    public int getPointSize() {
        return pointSize;
    }

    public void setPointSize(int pointSize) {
        this.pointSize = pointSize;
        invalidate();
    }

    public int getPointColor() {
        return pointColor;
    }

    public void setPointColor(int pointColor) {
        this.pointColor = pointColor;
        invalidate();
    }

    public Paint getPointPaint() {
        return pointPaint;
    }

    public void setPointPaint(Paint pointPaint) {
        this.pointPaint = pointPaint;
        invalidate();
    }

    public int getLineColor() {
        return lineColor;
    }

    public void setLineColor(int lineColor) {
        this.lineColor = lineColor;
        invalidate();
    }

    public int getLineMarginSide() {
        return lineMarginSide;
    }

    public void setLineMarginSide(int lineMarginSide) {
        this.lineMarginSide = lineMarginSide;
        invalidate();
    }

    public int getLineDynamicDimen() {
        return lineDynamicDimen;
    }

    public void setLineDynamicDimen(int lineDynamicDimen) {
        this.lineDynamicDimen = lineDynamicDimen;
        invalidate();
    }

    public Bitmap getIcon() {
        return mIcon;
    }

    public void setIcon(Bitmap icon) {
        mIcon = icon;
    }

    public void setIcon(int resId) {
        if (resId == 0) return;
        BitmapDrawable temp = (BitmapDrawable) mContext.getResources().getDrawable(resId);
        if (temp != null) mIcon = temp.getBitmap();
        invalidate();
    }

    public int getLineGravity() {
        return lineGravity;
    }

    public void setLineGravity(int lineGravity) {
        this.lineGravity = lineGravity;
        invalidate();
    }
}

同時還有一個style需要你新增

    <!-- 時間軸 -->
    <declare-styleable name="UnderLineLinearLayout">
        <!--時間軸偏移值-->
        <attr name="line_margin_side" format="dimension"/>
        <!--時間軸動態調整值-->
        <attr name="line_dynamic_dimen" format="dimension"/>
        <!--線寬-->
        <attr name="line_stroke_width" format="dimension"/>
        <!--線的顏色-->
        <attr name="line_v_color" format="color"/>
        <!--點的大小-->
        <attr name="point_size" format="dimension"/>
        <!--點的顏色-->
        <attr name="point_color" format="color"/>
        <!--圖示-->
        <attr name="icon_src" format="reference"/>
        <!--時間軸的gravity-->
        <!--the gravity of the timeline-->
        <attr name="line_gravity">
            <enum name="Left" value="2"/>
            <enum name="Right" value="4"/>
            <enum name="Middle" value="0"/>
            <enum name="Top" value="1"/>
            <enum name="Bottom" value="3"/>
        </attr>
    </declare-styleable>

中間又一個R.drawable.point,就是節點顏色的一個xml,你自己隨便定義一個就好

使用的話佈局檔案如下

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="none">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingLeft="45dp"
                android:paddingTop="20px"
                android:paddingBottom="20px"
                android:orientation="vertical"
                android:background="@color/white">

                <TextView
                    android:id="@+id/txt_logistics_status"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:textSize="@dimen/text_normal_medium"
                    android:textColor="@color/xml_color_text_normal_black"
                    android:text="物流狀態:    "/>

                <TextView
                    android:id="@+id/txt_logistics_company"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:layout_marginTop="10px"
                    android:gravity="center"
                    android:textSize="@dimen/text_normal_medium"
                    android:textColor="@color/xml_color_text_normal_gray"
                    android:text="承運公司:    "/>

                <TextView
                    android:id="@+id/txt_logistics_order_id"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:layout_marginTop="10px"
                    android:gravity="center"
                    android:textSize="@dimen/text_normal_medium"
                    android:textColor="@color/xml_color_text_normal_gray"
                    android:text="運單編號:    "/>

                <TextView
                    android:id="@+id/txt_logistics_phone"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:layout_marginTop="10px"
                    android:gravity="center"
                    android:textSize="@dimen/text_normal_medium"
                    android:textColor="@color/xml_color_text_normal_gray"
                    android:text="官方電話:    "/>
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="@color/xml_color_text_normal_gray_cd"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="@dimen/space_normal_dp"
                android:background="@color/xml_color_text_normal_gray_bg"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="@color/xml_color_text_normal_gray_cd"/>

  <UnderLineLinearLayout
                android:id="@+id/underline_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:background="@android:color/white"
                app:line_margin_side="35dp"
                app:point_size="@dimen/space_6"
                app:line_stroke_width="@dimen/space_3"
                app:point_color="@color/xml_color_text_normal_gray_bg"
                app:line_v_color="@color/xml_color_text_normal_gray_bg"
                app:line_dynamic_dimen="8dp"/>
        </LinearLayout>
    </ScrollView>
    //初始化物流時間軸
    private void initBody(List<ExpressInfoEntity.ResultBean.ListBean> list){
        for (int i = 0; i < list.size(); i++) {
            View v = LayoutInflater.from(this).inflate(R.layout.logicst_item, underlineLayout, false);
            ((TextView) v.findViewById(R.id.tx_action)).setText(list.get(i).getStatus());
            ((TextView) v.findViewById(R.id.tx_time)).setText(list.get(i).getTime());
            if (i==0){
                ((TextView) v.findViewById(R.id.tx_action)).setTextColor(ContextCompat.getColor(this,R.color.xml_color_text_normal_style));
                ((TextView) v.findViewById(R.id.tx_time)).setTextColor(ContextCompat.getColor(this,R.color.xml_color_text_normal_style));
            }
            underlineLayout.addView(v);
        }
    }