Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義View系列(1)--仿支付寶中物流狀態效果

自定義View系列(1)--仿支付寶中物流狀態效果

編輯:關於Android編程

國際慣例,先上支付寶中的原效果圖:

\

 

再來一張自定義view的效果圖

\

 

看到兩個效果圖的對比,可能會有人問為啥物流狀態被選中時的背景沒有?其實是有的,只不過我把代碼注釋掉了,原因就是背景太難看了,畢竟水平有限,畫上去的背景和原效果差距甚遠,不好意思展示出來,所以就把字體稍微放大一些作為突出.還請見諒~~

先說一下這個自定義view支持的屬性哈

1,支持在代碼裡設置出發地和目的地(如效果圖中的武漢市和蘭州市)

2,支持在代碼裡和布局文件裡設置最新狀態時圓圈的顏色和線的顏色

3,支持最新狀態所在位置,比如從服務端返回的json中拿到當前的狀態為"派件中",那麼直接調用setNewestPointPosition(2)即可,

都是最基本且必須的屬性,當然還可以抽取出來其他屬性,比如未選中的圓圈和線的顏色以及字體大小和顏色,圓圈半徑,線的粗細,圓圈與線之間的padding值等.

下面上代碼:

 

package com.lanma.customviewproject.views;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import com.lanma.customviewproject.R;

/**
 * 作者 qiang_xi on 2016/8/17 10:17.
 * 狀態線:仿新版支付寶物流狀態效果
 */
public class StateLine extends View {
    private Paint mCirclePaint;//圓圈畫筆
    private Paint mLinePaint;//線畫筆
    private Paint mTextPaint;//文字畫筆
    private String[] data = {"已發貨", "運輸中", "派件中", "已簽收"};
    private int pointPosition;//最新的點的位置
    private String startPositionText;//出發地
    private String arrivePositionText;//目的地
    private int selectedCircleColor = Color.RED;
    private int selectedLineColor = Color.RED;
    private Drawable mTextBackground = getContext().getResources().getDrawable(R.drawable.bg_pop_dialog);//選中的文字背景

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

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

    public StateLine(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
        TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.StateLine);

        for (int i = 0; i < a.getIndexCount(); i++) {
           int attr =  a.getIndex(i);
            switch (attr) {
                case R.styleable.StateLine_selectedCircleColor:
                    selectedCircleColor = a.getColor(attr, Color.RED);
                    break;
                case R.styleable.StateLine_selectedLineColor:
                    selectedLineColor = a.getColor(attr, Color.RED);
                    break;
            }
        }
        a.recycle();
    }

    private void init() {
        //圓圈畫筆
        mCirclePaint = new Paint();
        mCirclePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mCirclePaint.setAntiAlias(true);
        //線畫筆
        mLinePaint = new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setStrokeWidth(2);
        //文本畫筆
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        int height;
        if (MeasureSpec.EXACTLY == widthMode) {
            width = widthSize;
        } else {
            width = dpToPx(200);
            if (MeasureSpec.AT_MOST == widthMode) {
                width = Math.min(width, widthSize);
            }
        }
        if (MeasureSpec.EXACTLY == heightMode) {
            height = heightSize;
        } else {
            height = dpToPx(45);
            if (MeasureSpec.AT_MOST == heightMode) {
                height = Math.min(height, heightSize);
            }
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int width = getWidth() / data.length;
        //繪制上部文字
        drawTopText(canvas, data.length, width);
        canvas.save();
        //把畫布向右移動 "已發貨"的文本寬度 一半的距離(讓第一個圓圈正對著該文本的中間)
        //同時向下移動30px,即在上部文字的下方30px處畫圓圈和線
        canvas.translate(getTextWidth(data[0]) / 2, 30);
        //繪制圓圈和線
        for (int i = 0; i < data.length; i++) {
            if (i < pointPosition) {
                mCirclePaint.setColor(selectedCircleColor);
                mLinePaint.setColor(selectedLineColor);
            } else if (i == pointPosition) {
                mCirclePaint.setColor(selectedCircleColor);
                mLinePaint.setColor(Color.GRAY);
            } else {
                mCirclePaint.setColor(Color.GRAY);
                mLinePaint.setColor(Color.GRAY);
            }
            canvas.drawCircle(5 + getPaddingLeft() + i * width, 5 + getPaddingTop(), 5, mCirclePaint);
            //最後一個點之後不再畫線
            if (i != data.length - 1) {
                canvas.drawLine(15 + getPaddingLeft() + i * width, 5 + getPaddingTop(),
                        getPaddingLeft() + (i + 1) * width - 5, 5 + getPaddingTop(), mLinePaint);
            }
        }
        //重置畫布狀態(恢復到上次save時的狀態,即沒有translate時的狀態)
        canvas.restore();
        canvas.save();
        //再向下平移55px,即在圓圈和線的下方55px處繪制底部文字
        canvas.translate(0, 55);
        //繪制底部文字
        drawBottomText(canvas, data.length, width);
        //再次把畫布的狀態重置
        canvas.restore();
    }

    /**
     * 繪制上部文字
     */
    private void drawTopText(Canvas canvas, int length, int width) {
        for (int i = 0; i < length; i++) {
            if (i == pointPosition) {
                mTextPaint.setColor(selectedCircleColor);
                mTextPaint.setTextSize(16);
                //下面兩行添加文字背景
//                mTextBackground.setBounds(getTextBound(i, width, data[i]));
//                mTextBackground.draw(canvas);
            } else {
                mTextPaint.setColor(Color.GRAY);
                mTextPaint.setTextSize(14);
            }
            canvas.drawText(data[i], 5 + getPaddingLeft() + i * width, getPaddingTop() + 20, mTextPaint);
        }
    }

    /**
     * 繪制底部文字
     */
    private void drawBottomText(Canvas canvas, int length, int width) {
        mTextPaint.setColor(Color.BLACK);
        if (!TextUtils.isEmpty(startPositionText)) {
            canvas.drawText(startPositionText, 5 + getPaddingLeft(), 5 + getPaddingTop(), mTextPaint);
        }
        if (!TextUtils.isEmpty(arrivePositionText)) {
            canvas.drawText(arrivePositionText, 5 + getPaddingLeft() + width * (length - 1), 5 + getPaddingTop(), mTextPaint);
        }
    }

    /**
     * 設置最新點的位置(從0開始)
     * 比如 pointPosition==2,則前3個點和2個線都是選中顏色,其他都是未選中顏色
     */
    public void setNewestPointPosition(int pointPosition) {
        if (data.length < pointPosition) {
            pointPosition = data.length - 1;
        }
        this.pointPosition = pointPosition;
        invalidate();
    }

    /**
     * 獲取最新點的位置(從0開始)
     */
    public int getNewestPointPosition() {
        return pointPosition;
    }

    /**
     * 設置出發地
     */
    public void setStartPositionText(String startPositionText) {
        this.startPositionText = startPositionText;
        invalidate();
    }

    /**
     * 獲取出發地
     */
    public String getStartPositionText() {
        return startPositionText;
    }

    /**
     * 設置目的地
     */
    public void setArrivePositionText(String arrivePositionText) {
        this.arrivePositionText = arrivePositionText;
        invalidate();
    }

    /**
     * 獲取目的地
     */
    public String getArrivePositionText() {
        return arrivePositionText;
    }

    /**
     * 設置選中的圓圈顏色
     */
    public void setSelectedCircleColor(int color) {
        this.selectedCircleColor = color;
        invalidate();
    }
    /**
     * 設置選中的線的顏色
     */
    public void setSelectedLineColor(int color) {
        this.selectedLineColor = color;
        invalidate();
    }

    private int getTextWidth(String text) {
        Rect textBound = new Rect();
        mTextPaint.getTextBounds(text, 0, text.length(), textBound);
        return textBound.width();
    }

    private Rect getTextBound(int i, int width, String text) {
        Rect textBound = new Rect();
        mTextPaint.getTextBounds(text, 0, text.length(), textBound);
        Rect rect = new Rect(getPaddingLeft() + i * width - 5, getPaddingTop() - 15,
                15 + getPaddingLeft() + i * width + textBound.width(), getPaddingTop() + 30 + textBound.height());
        return rect;
    }

    /**
     * dp轉px
     */
    public  int dpToPx(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                dp, getContext().getResources().getDisplayMetrics());
    }
}

 

 

代碼就這麼多,注釋也很詳細,有需要的直接拿走不謝.

下面再說一下如何繪制背景,如文章開頭所說,不是沒有背景,而是背景太難看了,不好意思展示出來.

其實一開始我也不是太清楚如何繪制背景,因為這個背景不是全局的,而是被選中文字的背景,並且這個背景還是跟著選中的文字走的,選中的文字在哪,背景就在哪,一開始有點思路,就是我知道如何做到選中的文字在哪,背景就在哪,但是卻不是太清楚如何繪制這個背景,不過當我研究了TextView繪制背景的源碼之後就知道如何繪制了(TextView中其實沒有繪制背景的代碼.代碼在他的父類View中有).

現在想來繪制背景和繪制圓圈其實本質都一樣,我目前知道的有兩種方式.

1,采用Drawable ,這個方式我是研究View源碼知道的,Drawable也有一個draw方法,用來繪制他自己的,但是需要一個繪制范圍,如draw方法的javadoc中所說,使用Drawable的draw方法之前,需要先調用setBound()方法,而setBound方法其實就是來確定Drawable的繪制范圍的.使用Drawable比較靈活,因為Drawable不僅可以是圖片,也可以是xml,你甚至也可以把顏色轉為Drawable使用.

2,使用Paint,這個也是一種方式,並且實現起來也很簡單,我之前把繪制背景想的太高深復雜了,導致一開始沒想到這種方式,其實不管使用paint還是Drawable,都需要一個繪制范圍,不然怎麼知道在哪繪制呢,所以難點不在於繪制,而是如何確定繪制的范圍.

繪制范圍的確定:

.繪制范圍的確定需要根據具體的控件樣式來確定,以本文的view來說,我要繪制被選中的文本的背景,我需要知道文本繪制的起點X坐標以及文本的寬度,這是用來確定文本背景的寬度,當然你可以為了好看再主動加點padding值,同樣的,我也需要知道文本繪制的起點左上角的Y坐標以及文本的高度,用來確定背景的高度,當然也可以加點padding值為了好看,根據以上的幾個值就可以設置繪制范圍了,其實就是一個Rect對象或RectF對象,把左,上,右,下的坐標設置進去即可,對於Drawable來說,調用setBound方法,把Rect放進去,然後再調用Drawable的draw方法就可以在指定范圍繪制背景了.

說了這麼多,本文的這個view的文字背景繪制起來其實還有點小問題,主要是因為在獲取文字寬高的時候,由於文字的大小會時刻的變,導致背景看起來很丑,所以就沒展示出來.

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved