Android 自定義物流狀態(tài)控件

導(dǎo)語(yǔ)

有些時(shí)候看著別人家的自定義控件很酷炫。

別人家的控件

淘寶

Paste_Image.png

美團(tuán)外賣(mài)

Paste_Image.png
自己家的控件
Paste_Image.png

做開(kāi)發(fā)的時(shí)候雷猪,難免產(chǎn)品會(huì)給出類(lèi)似的需求和設(shè)計(jì)次伶,看到這樣的物流狀態(tài)圖婉支,你第一時(shí)間會(huì)想到用什么方法去解決?當(dāng)然痢毒,解決的辦法N多種送矩,比如用ListView去實(shí)現(xiàn),或是用動(dòng)態(tài)布局去實(shí)現(xiàn)哪替,唯一需要解決的就是隱藏cell直接的間隔線(xiàn)栋荸,無(wú)縫連接每個(gè)cell之間的進(jìn)度線(xiàn),做一些布局上的巧妙處理凭舶∩慰椋看到類(lèi)似的物流狀態(tài)圖,我第一反應(yīng)總是想用自己的辦法去解決帅霜,腦子里浮現(xiàn)的是用一個(gè)自定義控件去搞定這件事情匆背,總覺(jué)得用ListView控件去實(shí)現(xiàn)這種數(shù)據(jù)量不大的界面表現(xiàn)有點(diǎn)大材小用。于是便有了下面的思路和解決辦法:

閱讀本文需要你了解這幾個(gè)知識(shí)點(diǎn):

1身冀、自定義View
2钝尸、手勢(shì)監(jiān)聽(tīng)
3、View滑動(dòng)
4搂根、適配器設(shè)計(jì)模式

思路整理
從界面呈現(xiàn)上

從整體來(lái)看珍促,界面上呈現(xiàn)的數(shù)據(jù)是一個(gè)數(shù)組或是List形式的數(shù)據(jù)封裝,然后根據(jù)數(shù)據(jù)的容量大小for循環(huán)動(dòng)態(tài)繪制剩愧,綁定相關(guān)數(shù)據(jù)到界面展示猪叙。

從一個(gè)cell局部來(lái)看,一個(gè)cell包含的內(nèi)容分6部分仁卷,分別是左上角的狀態(tài)提示點(diǎn)穴翩、左邊的狀態(tài)進(jìn)度線(xiàn)、cell的背景锦积、主文字區(qū)域芒帕、時(shí)間文字區(qū)域以及文字按鈕區(qū)域。

從其他來(lái)看充包,其實(shí)還包括每個(gè)cell之間的間隔距離副签、整個(gè)View和父控件的間隔距離遥椿、文字之間的距離、文字和cell間的距離等等淆储。

從邏輯上

根據(jù)for循環(huán)動(dòng)態(tài)填充界面元素冠场,最主要的邏輯是要計(jì)算每個(gè)元素的坐標(biāo)位置,然后根據(jù)坐標(biāo)位置繪制相關(guān)元素本砰。cell中某些元素的高度是給的定值碴裙,有些則是自適應(yīng)內(nèi)容高度,高度不定点额,這些高度值需要做累加處理舔株,才能準(zhǔn)確計(jì)算下個(gè)元素的坐標(biāo)位置。比如cell中的主文字高度是要根據(jù)文字字?jǐn)?shù)自適配的还棱,我們需要計(jì)算出文字的高度载慈。

左邊的物流狀態(tài)進(jìn)度線(xiàn)也會(huì)根據(jù)cell的高度繪制,其實(shí)這里我們可以換個(gè)角度去思考珍手,左邊的進(jìn)度線(xiàn)我們并不需要每一次for循環(huán)都去計(jì)算進(jìn)度線(xiàn)需要繪制的長(zhǎng)度或是計(jì)算繪制起點(diǎn)和終點(diǎn)的坐標(biāo)位置办铡,我們只需要得到物流狀態(tài)的第一個(gè)點(diǎn)和最后一個(gè)點(diǎn)的坐標(biāo)位置,然后用一條線(xiàn)連接即可琳要,這樣即可貫串中間的所有物流狀態(tài)點(diǎn)寡具。

從交互上

控件能承受的數(shù)據(jù)長(zhǎng)度是不定的,數(shù)據(jù)量偏大稚补,繪制肯定會(huì)超出屏幕童叠,這里需要做一個(gè)滑動(dòng);每個(gè)cell中的按鈕文字區(qū)域课幕,需要計(jì)算每個(gè)文字按鈕的有效觸摸區(qū)域厦坛,在觸摸事件結(jié)束的時(shí)候響應(yīng)監(jiān)聽(tīng)事件,模擬一個(gè)點(diǎn)擊事件撰豺。

從適配上

控件能接受的數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)肯定是多樣的粪般,這里需要為控件做一個(gè)數(shù)據(jù)適配器拼余,以適應(yīng)各種數(shù)據(jù)接口的數(shù)據(jù)源污桦。

運(yùn)行效果(刷新可看動(dòng)圖)
代碼實(shí)現(xiàn)
定義相關(guān)參數(shù)

<pre>
private int screenWidth;

private int screenHeight;

private int width, height; //當(dāng)前View寬高

private int viewWidth, viewHeight; //當(dāng)前View寬高

private int firstExpressCircleMarginLeft = DeviceUtils.dipToPx(getContext(), 16);
private int firstExpressCircleMarginTop = DeviceUtils.dipToPx(getContext(), 40);

private int expressCircleRadius = DeviceUtils.dipToPx(getContext(), 6);//物流狀態(tài)提示圈半徑
private int expressCircleCurrentRadius = DeviceUtils.dipToPx(getContext(), 3);//物流狀態(tài)提示圈半徑
private int expressCircleOuterRadius = DeviceUtils.dipToPx(getContext(), 8);//物流狀態(tài)提示圈外半徑

private int circleToTextMargin = DeviceUtils.dipToPx(getContext(), 12);//物流狀態(tài)提示圈到文字背景的距離

private int expressTextBackgroundWidth; //文字背景寬
private int expressTextMargin = DeviceUtils.dipToPx(getContext(), 8); //文字距離背景邊距

private int expressTextVecPadding = DeviceUtils.dipToPx(getContext(), 5); //每個(gè)物流信息豎直方向的間距

private int expressTextToTimeTextPadding = DeviceUtils.dipToPx(getContext(), 6); //物流文字距離時(shí)間文字的間距

private int expressButtonTextHeight; //按鈕文字高度

private boolean isTimeButtonVisible = false; //是否需要顯示時(shí)間和按鈕

private Paint expressCirclePaint; //物流狀態(tài)提示圈
private Paint expressTextBackgroundPaint; //文字背景
private TextPaint expressTextPaint; //文字
private TextPaint timeTextPaint; //時(shí)間文字
private TextPaint buttonTextPaint; //按鈕文字
private Paint bgPaint;
private Paint expressLinePaint;//物流線(xiàn)條
private int expressTextSize;//文字大小
private int expressTimeTextSize;//時(shí)間文字大小

private int contentDrawHeight; //根據(jù)內(nèi)容繪制的高度
</pre>

在res/values/attrs.xml中自定義控件相關(guān)屬性
<pre>
<declare-styleable name="ExpressView">
<attr name="firstExpressCircleMarginLeft" format="dimension">16</attr>
<attr name="firstExpressCircleMarginTop" format="dimension">16</attr>
<attr name="expressCircleRadius" format="dimension">6</attr>
<attr name="expressCircleOuterRadius" format="dimension">8</attr>
<attr name="circleToTextMargin" format="dimension">12</attr>
<attr name="expressTextMargin" format="dimension">8</attr>
<attr name="expressTextVecPadding" format="dimension">5</attr>
<attr name="expressTextSize" format="dimension">18</attr>
<attr name="expressTimeTextSize" format="dimension">14</attr>
<attr name="isTimeButtonVisible" format="boolean">false</attr>

<attr name="progressBarStyleHorizontal" format="reference" ></attr>
</declare-styleable>
</pre>

其中

firstExpressCircleMarginLeft 第一個(gè)物流狀態(tài)點(diǎn)距離父控件坐邊的間距
firstExpressCircleMarginTop 第一個(gè)物流狀態(tài)點(diǎn)距離父控件上邊的間距
expressCircleRadius 物流狀態(tài)點(diǎn)內(nèi)圈半徑
expressCircleOuterRadius 物流狀態(tài)點(diǎn)外圈半徑
circleToTextMargin 物流狀態(tài)提示圈到文字背景的距離
expressTextMargin 文字距離背景邊距
expressTextVecPadding 每個(gè)物流信息豎直方向的間距
expressTextSize 文字大小
expressTimeTextSize 時(shí)間文字大小
isTimeButtonVisible 是否顯示時(shí)間和文字按鈕

如果設(shè)置isTimeButtonVisible為false,界面顯示

Paste_Image.png

初始化屬性值
<pre>
private void initTypeArray(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ExpressView);
firstExpressCircleMarginLeft = (int) array.getDimension(R.styleable.ExpressView_firstExpressCircleMarginLeft, 16);
firstExpressCircleMarginTop = (int) array.getDimension(R.styleable.ExpressView_firstExpressCircleMarginTop, 16);
expressCircleRadius = (int) array.getDimension(R.styleable.ExpressView_expressCircleRadius, 6);
expressCircleOuterRadius = (int) array.getDimension(R.styleable.ExpressView_expressCircleOuterRadius, 8);
circleToTextMargin = (int) array.getDimension(R.styleable.ExpressView_circleToTextMargin, 12);
expressTextMargin = (int) array.getDimension(R.styleable.ExpressView_expressTextMargin, 8);
expressTextVecPadding = (int) array.getDimension(R.styleable.ExpressView_expressTextVecPadding, 5);
expressTextSize = (int) array.getDimension(R.styleable.ExpressView_expressTextSize, 16);
expressTimeTextSize = (int) array.getDimension(R.styleable.ExpressView_expressTimeTextSize, 16);
isTimeButtonVisible = array.getBoolean(R.styleable.ExpressView_isTimeButtonVisible, false);
array.recycle();

    buttonTopPositionMap = new HashMap<>();
    buttonTopPositionMap.put(0, new Point(0, 0)); //設(shè)置初始值
}

</pre>

初始化畫(huà)筆
<pre>
private void initPaint(Context context) {

    touchDistance = ViewConfiguration.get(context).getScaledTouchSlop();

    screenHeight = DeviceUtils.getScreenHeight(context);
    screenWidth = DeviceUtils.getScreenWidth(context);

    //物流狀態(tài)提示圈
    expressCirclePaint = new Paint();
    expressCirclePaint.setColor(Color.parseColor("#969696"));
    expressCirclePaint.setStyle(Paint.Style.FILL);
    expressCirclePaint.setAntiAlias(true);
    expressCirclePaint.setStrokeWidth(DeviceUtils.dipToPx(getContext(), 2));

    expressTextBackgroundPaint = new Paint();
    expressTextBackgroundPaint.setAntiAlias(true);
    expressTextBackgroundPaint.setColor(Color.WHITE);
    expressTextBackgroundPaint.setStyle(Paint.Style.FILL);
    expressCirclePaint.setStrokeWidth(DeviceUtils.dipToPx(getContext(), 2));

    expressTextPaint = new TextPaint();
    expressTextPaint.setAntiAlias(true);
    expressTextPaint.setColor(Color.BLACK);
    expressTextPaint.setTextSize(expressTextSize);
    expressTextPaint.setStyle(Paint.Style.FILL);

    timeTextPaint = new TextPaint();
    timeTextPaint.setAntiAlias(true);
    timeTextPaint.setColor(Color.parseColor("#969696"));
    timeTextPaint.setTextSize(expressTimeTextSize);
    timeTextPaint.setStyle(Paint.Style.FILL);

    buttonTextPaint = new TextPaint();
    buttonTextPaint.setAntiAlias(true);
    buttonTextPaint.setColor(Color.parseColor("#4682B4"));
    buttonTextPaint.setTextSize(expressTextSize);
    buttonTextPaint.setStyle(Paint.Style.FILL);


    expressLinePaint = new Paint();
    expressLinePaint.setAntiAlias(true);
    expressLinePaint.setColor(Color.parseColor("#969696"));
    expressLinePaint.setStyle(Paint.Style.FILL);
    expressLinePaint.setStrokeWidth(DeviceUtils.dipToPx(getContext(), 1));

    bgPaint = new Paint();
    bgPaint.setAntiAlias(true);
    bgPaint.setAlpha(30);
    bgPaint.setColor(Color.parseColor("#969696"));
    bgPaint.setStyle(Paint.Style.STROKE);

}

</pre>

這兩個(gè)方法的初始化都放在構(gòu)造方法里
<pre>
public ExpressView(Context context) {
super(context);
initPaint(context);
}

public ExpressView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initTypeArray(context, attrs);
    initPaint(context);
}

public ExpressView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initTypeArray(context, attrs);
    initPaint(context);
}

</pre>

重寫(xiě)onMeasure方法

<pre>
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
    int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
    int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
    int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

    Log.e("ExpressView", "屏幕寬高 " + screenWidth + "  " + screenHeight);

    if (modeWidth == MeasureSpec.EXACTLY) {
        viewWidth = sizeWidth;
        Log.e("ExpressView", "精確測(cè)量寬 " + viewWidth);
    } else {
        viewWidth = width;
        Log.e("ExpressView", "粗略測(cè)量寬 " + viewWidth);
    }

    if (modeHeight == MeasureSpec.EXACTLY) {
        viewHeight = sizeHeight;
        Log.e("ExpressView", "精確測(cè)量高 " + viewHeight);
    } else {
        viewHeight = height;
        Log.e("ExpressView", "粗略測(cè)量高 " + viewHeight);
    }


    viewWidth = viewWidth > screenWidth ? screenWidth : viewWidth;
    viewHeight = viewHeight > screenHeight ? screenHeight : viewHeight;

    setMeasuredDimension(viewWidth, viewHeight);

    expressTextBackgroundWidth = viewWidth - 2 * (firstExpressCircleMarginLeft - expressCircleRadius) - 2 * circleToTextMargin;
    Log.e("ExpressView", "View寬度 " + viewWidth + "繪制的文字背景寬度 " + expressTextBackgroundWidth);

}

</pre>

重寫(xiě)onDraw方法

這里主要講解下cell中文字的繪制以及文字高度的獲取
用StaticLayout即可解決文字自動(dòng)換行以及獲取文字的高度匙监,具體使用方法如下:
<pre>
StaticLayout layout = new StaticLayout(text, expressTextPaint, expressTextBackgroundWidth - 2 * expressTextMargin, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
int textHeight = layout.getHeight();//計(jì)算文字高度
layout.draw(canvas); //繪制到畫(huà)布
</pre>

StaticLayout中四個(gè)參數(shù)的意思:文字內(nèi)容凡橱、畫(huà)筆、文字寬度亭姥、對(duì)其方式稼钩、相對(duì)行間距、在基礎(chǔ)行距上添加值达罗、是否包含間距值坝撑。

重寫(xiě)onTouchEvent方法静秆,處理手勢(shì)監(jiān)聽(tīng)

定義一個(gè)HashMap集合,用于存儲(chǔ)文字按鈕坐標(biāo)值
<pre>
private Map<Integer, Point> buttonTopPositionMap; //用于存儲(chǔ)點(diǎn)擊按鈕左上角坐標(biāo)
private int firstButtonPositionY; //記錄第一個(gè)按鈕位置坐標(biāo)
</pre>

手勢(shì)監(jiān)聽(tīng)
<pre>
@Override
public boolean onTouchEvent(MotionEvent event) {
this.getParent().requestDisallowInterceptTouchEvent(true);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = (int) event.getY();
lastY = downY;
lastMoveY = downY;
isMoving = false;

            break;
        case MotionEvent.ACTION_MOVE:
            downY = (int) event.getY();
            int transY = downY - lastY;
            int transMoveY = downY - lastMoveY; //每次手指按下到滑動(dòng)停止的滑動(dòng)距離
            isMoving = Math.abs(transMoveY) > touchDistance ? true : false; //判定是否滑動(dòng)
            Log.e("ExpressViewTouch", "當(dāng)前滑動(dòng)距離 " + Math.abs(transMoveY) + " 是否滑動(dòng) " + isMoving);
            if (isMoving) {
                transDistance += transY;
                Log.e("ExpressViewOnScreen", "滑動(dòng)距離" + transDistance);
                scrollBy(0, -transY);
            }
            lastY = downY;
            break;
        case MotionEvent.ACTION_UP:
            if (isTimeButtonVisible) {
                Log.e("ExpressViewTouch", "累計(jì)滑動(dòng)總距離 " + transDistance);
                if (isMoving || (!isMoving && (buttonTopPositionMap.get(0).y == firstButtonPositionY))) {
                    Iterator<Map.Entry<Integer, Point>> it = buttonTopPositionMap.entrySet().iterator();
                    while (it.hasNext()) {
                        Map.Entry<Integer, Point> entry = it.next();
                        entry.getValue().y += transDistance;
                    }
                }
                Log.e("ExpressViewTouch", "手指離開(kāi)屏幕位置信息 " + JsonUtils.objectToString(buttonTopPositionMap, Map.class));
                onActionUpEvent(isMoving, event, buttonTopPositionMap);
                this.getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            this.getParent().requestDisallowInterceptTouchEvent(false);
            break;
    }
    return true;
}

</pre>

這里需要注意一點(diǎn)是巡李,每一次滑動(dòng)操作抚笔,文字按鈕的坐標(biāo)信息都會(huì)發(fā)生變動(dòng),需要隨滑動(dòng)過(guò)程隨時(shí)記錄最新的坐標(biāo)位置侨拦。

手指離開(kāi)屏幕需要檢驗(yàn)當(dāng)前點(diǎn)擊是否是一次有效點(diǎn)擊事件并處理點(diǎn)擊回調(diào)事件
<pre>
private void onActionUpEvent(boolean isMoving, MotionEvent event, Map<Integer, Point> maps) {
int touchX = (int) event.getX();
int touchY = (int) event.getY();

    Iterator<Map.Entry<Integer, Point>> it = maps.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<Integer, Point> entry = it.next();
        if (!isMoving) { //只有點(diǎn)擊事件才檢驗(yàn)點(diǎn)擊是否有效并響應(yīng)點(diǎn)擊事件
            if (isBooleanXY1(touchX, touchY, entry)) {
                onExpressItemButtonClickListener.onExpressItemButtonClick(entry.getKey(), 0);
            } else if (isBooleanXY2(touchX, touchY, entry)) {
                onExpressItemButtonClickListener.onExpressItemButtonClick(entry.getKey(), 1);
            }
        }
    }
}

</pre>

定義數(shù)據(jù)適配器

由于我們的物流狀態(tài)控件只需要四種數(shù)據(jù):主文字殊橙、時(shí)間文字、按鈕坐邊文字以及按鈕右邊文字狱从,所以要對(duì)數(shù)據(jù)源做適配處理膨蛮。

針對(duì)物流控件封裝好數(shù)據(jù)格式
<pre>
public class ExpressViewData {
private String content; //內(nèi)容
private String time; //時(shí)間
private String leftBtnText; //左按鈕文字
private String rightBtnText; //右按鈕文字

@Override
public String toString() {
    return "ExpressViewData{" +
            "content='" + content + '\'' +
            ", time='" + time + '\'' +
            ", leftBtnText='" + leftBtnText + '\'' +
            ", rightBtnText='" + rightBtnText + '\'' +
            '}';
}

public String getContent() {
    return content;
}

public void setContent(String content) {
    this.content = content;
}

public String getLeftBtnText() {
    return leftBtnText;
}

public void setLeftBtnText(String leftBtnText) {
    this.leftBtnText = leftBtnText;
}

public String getRightBtnText() {
    return rightBtnText;
}

public void setRightBtnText(String rightBtnText) {
    this.rightBtnText = rightBtnText;
}

public String getTime() {
    return time;
}

public void setTime(String time) {
    this.time = time;
}

}
</pre>

定義一個(gè)抽象適配器類(lèi)
<pre>
public abstract class ExpressViewAdapter<T> {

private OnDataChangedListener onDataChangedListener;

private List<T> dataList;

public ExpressViewAdapter(List<T> dataList) {
    this.dataList = dataList;
}

public int getCount(){
   return dataList == null ? 0 : dataList.size();
}

public T getItem(int position){
    return dataList == null ? null : dataList.get(position);
}

public abstract ExpressViewData bindData(ExpressView expressView, int position, T t);

public void notifyDataChanged(){
    onDataChangedListener.onDataChanged();
}

public interface OnDataChangedListener {
    void onDataChanged();
}

public void setOnDataChangedListener(OnDataChangedListener onDataChangedListener) {
    this.onDataChangedListener = onDataChangedListener;
}

}
</pre>

數(shù)據(jù)適配具體實(shí)現(xiàn)
<pre>
adapter = new ExpressViewAdapter<ExpressMessageBean>(list) {
@Override
public ExpressViewData bindData(ExpressView expressView, int position, ExpressMessageBean expressMessageBean) {
ExpressViewData data = new ExpressViewData();
data.setContent(expressMessageBean.getOpContent());
data.setTime(expressMessageBean.getCreateTimeFormat());
data.setLeftBtnText(expressMessageBean.getFlowStateBtLeft());
data.setRightBtnText(expressMessageBean.getFlowStateBtRight());
return data;
}
};
</pre>

控件中具體取值
<pre>
ExpressViewData expressViewData = mAdapter.bindData(this, i, mAdapter.getItem(i));
</pre>

適配器的使用
<pre>
expressView.setAdapter(adapter);
adapter.notifyDataChanged();
</pre>

通過(guò)適配器,即可把任意的數(shù)據(jù)源做適配展示到物流狀態(tài)控件上季研。

源碼

ExpressView
<pre>
/**

  • 物流狀態(tài)View
  • Created by licheng on 19/2/17.
    */

public class ExpressView extends View implements ExpressViewAdapter.OnDataChangedListener {

private int screenWidth;
private int screenHeight;
private int width, height; //當(dāng)前View寬高
private int viewWidth, viewHeight; //當(dāng)前View寬高
private int firstExpressCircleMarginLeft = DeviceUtils.dipToPx(getContext(), 16);
private int firstExpressCircleMarginTop = DeviceUtils.dipToPx(getContext(), 40);

private int expressCircleRadius = DeviceUtils.dipToPx(getContext(), 6);//物流狀態(tài)提示圈半徑
private int expressCircleCurrentRadius = DeviceUtils.dipToPx(getContext(), 3);//物流狀態(tài)提示圈半徑
private int expressCircleOuterRadius = DeviceUtils.dipToPx(getContext(), 8);//物流狀態(tài)提示圈外半徑

private int circleToTextMargin = DeviceUtils.dipToPx(getContext(), 12);//物流狀態(tài)提示圈到文字背景的距離

private int expressTextBackgroundWidth; //文字背景寬
private int expressTextMargin = DeviceUtils.dipToPx(getContext(), 8); //文字距離背景邊距

private int expressTextVecPadding = DeviceUtils.dipToPx(getContext(), 5); //每個(gè)物流信息豎直方向的間距

private int expressTextToTimeTextPadding = DeviceUtils.dipToPx(getContext(), 6); //物流文字距離時(shí)間文字的間距

private int expressButtonTextHeight; //按鈕文字高度

private boolean isTimeButtonVisible = false; //是否需要顯示時(shí)間和按鈕

private Paint expressCirclePaint; //物流狀態(tài)提示圈
private Paint expressTextBackgroundPaint; //文字背景
private TextPaint expressTextPaint; //文字
private TextPaint timeTextPaint; //時(shí)間文字
private TextPaint buttonTextPaint; //按鈕文字
private Paint bgPaint;
private Paint expressLinePaint;//物流線(xiàn)條
private int expressTextSize;//文字大小
private int expressTimeTextSize;//時(shí)間文字大小

private int contentDrawHeight; //根據(jù)內(nèi)容繪制的高度

private Map<Integer, Point> buttonTopPositionMap; //用于存儲(chǔ)點(diǎn)擊按鈕左上角坐標(biāo)
private int firstButtonPositionY; //記錄第一個(gè)按鈕位置坐標(biāo)

// private int[] location = new int[2];

private ExpressViewAdapter mAdapter;

public ExpressView(Context context) {
    super(context);
    initPaint(context);
}

public ExpressView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initTypeArray(context, attrs);
    initPaint(context);
}

public ExpressView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initTypeArray(context, attrs);
    initPaint(context);
}


private void initTypeArray(Context context, AttributeSet attrs) {
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ExpressView);
    firstExpressCircleMarginLeft = (int) array.getDimension(R.styleable.ExpressView_firstExpressCircleMarginLeft, 16);
    firstExpressCircleMarginTop = (int) array.getDimension(R.styleable.ExpressView_firstExpressCircleMarginTop, 16);
    expressCircleRadius = (int) array.getDimension(R.styleable.ExpressView_expressCircleRadius, 6);
    expressCircleOuterRadius = (int) array.getDimension(R.styleable.ExpressView_expressCircleOuterRadius, 8);
    circleToTextMargin = (int) array.getDimension(R.styleable.ExpressView_circleToTextMargin, 12);
    expressTextMargin = (int) array.getDimension(R.styleable.ExpressView_expressTextMargin, 8);
    expressTextVecPadding = (int) array.getDimension(R.styleable.ExpressView_expressTextVecPadding, 5);
    expressTextSize = (int) array.getDimension(R.styleable.ExpressView_expressTextSize, 16);
    expressTimeTextSize = (int) array.getDimension(R.styleable.ExpressView_expressTimeTextSize, 16);
    isTimeButtonVisible = array.getBoolean(R.styleable.ExpressView_isTimeButtonVisible, false);
    array.recycle();

    buttonTopPositionMap = new HashMap<>();
    buttonTopPositionMap.put(0, new Point(0, 0)); //設(shè)置初始值
}

private void initPaint(Context context) {

    touchDistance = ViewConfiguration.get(context).getScaledTouchSlop();

    screenHeight = DeviceUtils.getScreenHeight(context);
    screenWidth = DeviceUtils.getScreenWidth(context);

    //物流狀態(tài)提示圈
    expressCirclePaint = new Paint();
    expressCirclePaint.setColor(Color.parseColor("#969696"));
    expressCirclePaint.setStyle(Paint.Style.FILL);
    expressCirclePaint.setAntiAlias(true);
    expressCirclePaint.setStrokeWidth(DeviceUtils.dipToPx(getContext(), 2));

    expressTextBackgroundPaint = new Paint();
    expressTextBackgroundPaint.setAntiAlias(true);
    expressTextBackgroundPaint.setColor(Color.WHITE);
    expressTextBackgroundPaint.setStyle(Paint.Style.FILL);
    expressCirclePaint.setStrokeWidth(DeviceUtils.dipToPx(getContext(), 2));

    expressTextPaint = new TextPaint();
    expressTextPaint.setAntiAlias(true);
    expressTextPaint.setColor(Color.BLACK);
    expressTextPaint.setTextSize(expressTextSize);
    expressTextPaint.setStyle(Paint.Style.FILL);

    timeTextPaint = new TextPaint();
    timeTextPaint.setAntiAlias(true);
    timeTextPaint.setColor(Color.parseColor("#969696"));
    timeTextPaint.setTextSize(expressTimeTextSize);
    timeTextPaint.setStyle(Paint.Style.FILL);

    buttonTextPaint = new TextPaint();
    buttonTextPaint.setAntiAlias(true);
    buttonTextPaint.setColor(Color.parseColor("#4682B4"));
    buttonTextPaint.setTextSize(expressTextSize);
    buttonTextPaint.setStyle(Paint.Style.FILL);


    expressLinePaint = new Paint();
    expressLinePaint.setAntiAlias(true);
    expressLinePaint.setColor(Color.parseColor("#969696"));
    expressLinePaint.setStyle(Paint.Style.FILL);
    expressLinePaint.setStrokeWidth(DeviceUtils.dipToPx(getContext(), 1));

    bgPaint = new Paint();
    bgPaint.setAntiAlias(true);
    bgPaint.setAlpha(30);
    bgPaint.setColor(Color.parseColor("#969696"));
    bgPaint.setStyle(Paint.Style.STROKE);

}


@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    width = w;
    height = h;
    Log.e("ExpressView", "當(dāng)前View的width " + width + "  height " + height);

// getLocationOnScreen(location);
// Log.e("ExpressViewOnScreen", location[0] + " " + location[1]);

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
    int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
    int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
    int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

    Log.e("ExpressView", "屏幕寬高 " + screenWidth + "  " + screenHeight);

    if (modeWidth == MeasureSpec.EXACTLY) {
        viewWidth = sizeWidth;
        Log.e("ExpressView", "精確測(cè)量寬 " + viewWidth);
    } else {
        viewWidth = width;
        Log.e("ExpressView", "粗略測(cè)量寬 " + viewWidth);
    }

    if (modeHeight == MeasureSpec.EXACTLY) {
        viewHeight = sizeHeight;
        Log.e("ExpressView", "精確測(cè)量高 " + viewHeight);
    } else {
        viewHeight = height;
        Log.e("ExpressView", "粗略測(cè)量高 " + viewHeight);
    }


    viewWidth = viewWidth > screenWidth ? screenWidth : viewWidth;
    viewHeight = viewHeight > screenHeight ? screenHeight : viewHeight;

    setMeasuredDimension(viewWidth, viewHeight);

    expressTextBackgroundWidth = viewWidth - 2 * (firstExpressCircleMarginLeft - expressCircleRadius) - 2 * circleToTextMargin;
    Log.e("ExpressView", "View寬度 " + viewWidth + "繪制的文字背景寬度 " + expressTextBackgroundWidth);

}

@Override
protected void onDraw(Canvas canvas) {


    int expressTextBgHeightSum = 0;
    int firstCirclePoint = 0, lastCirclePoint = 0; //記錄第一個(gè)繪制和最后一個(gè)繪制的點(diǎn)位置

    if (isAdapterNull()) {

        for (int i = 0; i < mAdapter.getCount(); i++) {
            //物流文字高度測(cè)量

            ExpressViewData expressViewData = mAdapter.bindData(this, i, mAdapter.getItem(i));

            String text = expressViewData.getContent();
            StaticLayout layout = new StaticLayout(text, expressTextPaint, expressTextBackgroundWidth - 2 * expressTextMargin, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
            int textHeight = layout.getHeight();//計(jì)算文字高度
            int expressTextBgHeight = textHeight + 2 * expressTextMargin;

            //時(shí)間文字高度測(cè)量
            String timeText = "2017-02-16 23:44:31";
            StaticLayout timeLayout = new StaticLayout(timeText, timeTextPaint, (expressTextBackgroundWidth - 2 * expressTextMargin) / 2, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
            int timeTextHeight = timeLayout.getHeight();
            String timeShowText = expressViewData.getTime();

            //按鈕文字高度測(cè)量
            String buttonLeft = expressViewData.getLeftBtnText();
            String buttonRight = expressViewData.getRightBtnText();
            String buttoTxt = "立即購(gòu)買(mǎi)";

            StaticLayout buttonLayout = new StaticLayout(buttoTxt, buttonTextPaint, (expressTextBackgroundWidth - 2 * expressTextMargin) / 4, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
            int buttonTextHeight = buttonLayout.getHeight();
            int buttonTextWidth = buttonLayout.getWidth();

            expressButtonTextHeight = buttonTextHeight;
            timeTextHeight = Math.max(timeTextHeight, buttonTextHeight);


            if (!isTimeButtonVisible) {
                timeTextHeight = 0;
                expressTextToTimeTextPadding = 0;
            }


            //繪制提示圓圈
            canvas.save();
            if (i == 0) {
                expressCirclePaint.setColor(Color.parseColor("#D2691E"));
                canvas.drawCircle(firstExpressCircleMarginLeft,
                        firstExpressCircleMarginTop + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i,
                        expressCircleOuterRadius,
                        expressCirclePaint);
                expressCirclePaint.setColor(Color.parseColor("#ffffff"));
                canvas.drawCircle(firstExpressCircleMarginLeft,
                        firstExpressCircleMarginTop + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i,
                        expressCircleCurrentRadius,
                        expressCirclePaint);
            } else {
                expressCirclePaint.setColor(Color.parseColor("#969696"));
                canvas.drawCircle(firstExpressCircleMarginLeft,
                        firstExpressCircleMarginTop + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i,
                        expressCircleRadius,
                        expressCirclePaint);
            }
            canvas.restore();

            //獲取第一個(gè)提示點(diǎn)和最后一個(gè)提示點(diǎn)坐標(biāo)
            if (i == 0)
                firstCirclePoint = firstExpressCircleMarginTop + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i;
            if (i == mAdapter.getCount() - 1)
                lastCirclePoint = firstExpressCircleMarginTop + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i;

            //繪制文字背景
            canvas.save();
            if (i == 0) {
                canvas.translate(firstExpressCircleMarginLeft + circleToTextMargin + expressCircleRadius,
                        firstExpressCircleMarginTop + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i);
                expressTextBackgroundPaint.setColor(Color.parseColor("#D2691E"));
                canvas.drawRect(0, 0, expressTextBackgroundWidth, expressTextBgHeight + expressTextToTimeTextPadding + timeTextHeight, expressTextBackgroundPaint);
                expressTextBackgroundPaint.setColor(Color.parseColor("#ffffff"));
                canvas.drawRect(2, 2, expressTextBackgroundWidth - 2, expressTextBgHeight + expressTextToTimeTextPadding + timeTextHeight - 2, expressTextBackgroundPaint);
            } else {
                canvas.translate(firstExpressCircleMarginLeft + circleToTextMargin + expressCircleRadius,
                        firstExpressCircleMarginTop + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i);
                expressTextBackgroundPaint.setColor(Color.parseColor("#ffffff"));
                canvas.drawRect(0, 0, expressTextBackgroundWidth, expressTextBgHeight + expressTextToTimeTextPadding + timeTextHeight, expressTextBackgroundPaint);
            }

            if (i == mAdapter.getCount() - 1) { //記錄最后一個(gè)文字背景的坐標(biāo)位置
                contentDrawHeight = firstExpressCircleMarginTop + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i + expressTextBgHeight + expressTextToTimeTextPadding + timeTextHeight + expressTextVecPadding;
                Log.e("ExpressView", "最后一個(gè)文字背景坐標(biāo) " + contentDrawHeight);
            }

            canvas.restore();

            //繪制物流文字
            drawLeftButton(canvas, layout, firstExpressCircleMarginLeft + circleToTextMargin + expressCircleRadius + expressTextMargin, firstExpressCircleMarginTop + expressTextMargin + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i);

            if (isTimeButtonVisible) {
                //繪制時(shí)間文字
                if (!StringUtils.isBlank(timeShowText)) {
                    StaticLayout tl = new StaticLayout(timeShowText, timeTextPaint, (expressTextBackgroundWidth - 2 * expressTextMargin) / 2, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
                    drawLeftButton(canvas, tl, firstExpressCircleMarginLeft + circleToTextMargin + expressCircleRadius + expressTextMargin, firstExpressCircleMarginTop + expressTextMargin + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i + textHeight + expressTextToTimeTextPadding);
                }

                //繪制左邊按鈕
                if (!StringUtils.isBlank(buttonLeft)) {
                    StaticLayout sll = new StaticLayout(buttonLeft, buttonTextPaint, (expressTextBackgroundWidth - 2 * expressTextMargin) / 4, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
                    drawLeftButton(canvas, sll, firstExpressCircleMarginLeft + circleToTextMargin + expressCircleRadius + (expressTextBackgroundWidth - 2 * expressTextMargin) * 8 / 15, firstExpressCircleMarginTop + expressTextMargin + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i + textHeight + expressTextToTimeTextPadding);
                }

                //繪制右邊按鈕
                if (!StringUtils.isBlank(buttonRight)) {
                    StaticLayout sll = new StaticLayout(buttonRight, buttonTextPaint, (expressTextBackgroundWidth - 2 * expressTextMargin) / 4, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
                    drawRightButton(canvas, expressTextBgHeightSum, i, textHeight, sll, buttonTextWidth);
                }
            }

            if (i == 0)
                firstButtonPositionY = firstExpressCircleMarginTop + expressTextMargin + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i + textHeight + expressTextToTimeTextPadding; //記錄第一按鈕位置坐標(biāo)

            //存儲(chǔ)左邊按鈕坐標(biāo)
            Point point = new Point();
            point.set(firstExpressCircleMarginLeft + circleToTextMargin + expressCircleRadius + (expressTextBackgroundWidth - 2 * expressTextMargin) * 8 / 15, firstExpressCircleMarginTop + expressTextMargin + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i + textHeight + expressTextToTimeTextPadding);
            buttonTopPositionMap.put(i, point);

            expressTextBgHeightSum += (expressTextBgHeight + timeTextHeight);

        }

    }


    if (isAdapterNull()) {
        canvas.save();
        canvas.drawLine(firstExpressCircleMarginLeft, firstCirclePoint + expressCircleOuterRadius, firstExpressCircleMarginLeft, lastCirclePoint, expressLinePaint);
        canvas.restore();
    }

    Log.e("ExpressView", "按鈕位置信息 " + JsonUtils.objectToString(buttonTopPositionMap, Map.class));
}

//繪制左邊邊按鈕
private void drawLeftButton(Canvas canvas, StaticLayout buttonLayout, int dx, int dy) {
    canvas.save();
    canvas.translate(dx, dy);
    buttonLayout.draw(canvas);
    canvas.restore();
}

//繪制右邊按鈕
private void drawRightButton(Canvas canvas, int expressTextBgHeightSum, int i, int textHeight, StaticLayout buttonLayout, int buttonTextWidth) {
    drawLeftButton(canvas, buttonLayout, firstExpressCircleMarginLeft + circleToTextMargin + expressCircleRadius + (expressTextBackgroundWidth - 2 * expressTextMargin) * 8 / 15 + buttonTextWidth, firstExpressCircleMarginTop + expressTextMargin + expressTextBgHeightSum + (expressTextVecPadding + expressTextToTimeTextPadding) * i + textHeight + expressTextToTimeTextPadding);
}

/**
 * 判斷適配器是否為null
 *
 * @return
 */
private boolean isAdapterNull() {
    return null != mAdapter;
}

int downY = 0;
int lastY = 0;
int lastMoveY = 0;
int transDistance; //累計(jì)滑動(dòng)距離
int touchDistance; //系統(tǒng)判定滑動(dòng)的最小距離
boolean isMoving = false; //是否滑動(dòng)

@Override
public boolean onTouchEvent(MotionEvent event) {

    this.getParent().requestDisallowInterceptTouchEvent(true);
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downY = (int) event.getY();
            lastY = downY;
            lastMoveY = downY;
            isMoving = false;

            break;
        case MotionEvent.ACTION_MOVE:
            downY = (int) event.getY();
            int transY = downY - lastY;
            int transMoveY = downY - lastMoveY; //每次手指按下到滑動(dòng)停止的滑動(dòng)距離
            isMoving = Math.abs(transMoveY) > touchDistance ? true : false; //判定是否滑動(dòng)
            Log.e("ExpressViewTouch", "當(dāng)前滑動(dòng)距離 " + Math.abs(transMoveY) + " 是否滑動(dòng) " + isMoving);
            if (isMoving) {
                transDistance += transY;
                Log.e("ExpressViewOnScreen", "滑動(dòng)距離" + transDistance);
                scrollBy(0, -transY);

// if (Math.abs(transDistance) <= contentDrawHeight - screenHeight + location[1] && Math.abs(transDistance) > 0) {
// scrollBy(0, -transY);
// } else if (Math.abs(transDistance) == 0) {
// Log.e("ExpressViewOnScreen", "底部");
// } else {
// Log.e("ExpressViewOnScreen", "頂部");
// }
}
lastY = downY;
break;
case MotionEvent.ACTION_UP:
if (isTimeButtonVisible) {
Log.e("ExpressViewTouch", "累計(jì)滑動(dòng)總距離 " + transDistance);
if (isMoving || (!isMoving && (buttonTopPositionMap.get(0).y == firstButtonPositionY))) {
Iterator<Map.Entry<Integer, Point>> it = buttonTopPositionMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Point> entry = it.next();
entry.getValue().y += transDistance;
}
}
Log.e("ExpressViewTouch", "手指離開(kāi)屏幕位置信息 " + JsonUtils.objectToString(buttonTopPositionMap, Map.class));
onActionUpEvent(isMoving, event, buttonTopPositionMap);
this.getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_CANCEL:
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return true;
}

private void onActionUpEvent(boolean isMoving, MotionEvent event, Map<Integer, Point> maps) {
    int touchX = (int) event.getX();
    int touchY = (int) event.getY();

    Iterator<Map.Entry<Integer, Point>> it = maps.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<Integer, Point> entry = it.next();
        if (!isMoving) { //只有點(diǎn)擊事件才檢驗(yàn)點(diǎn)擊是否有效并響應(yīng)點(diǎn)擊事件
            if (isBooleanXY1(touchX, touchY, entry)) {
                onExpressItemButtonClickListener.onExpressItemButtonClick(entry.getKey(), 0);
            } else if (isBooleanXY2(touchX, touchY, entry)) {
                onExpressItemButtonClickListener.onExpressItemButtonClick(entry.getKey(), 1);
            }
        }
    }
}

public void setTimeButtonVisible(boolean timeButtonVisible) {
    isTimeButtonVisible = timeButtonVisible;
}

private boolean isBooleanXY1(int touchX, int touchY, Map.Entry<Integer, Point> entry) {
    return isBooleanX1(touchX, entry) && isBooleanY(touchY, entry);
}

private boolean isBooleanY(int touchY, Map.Entry<Integer, Point> entry) {
    return touchY > entry.getValue().y && touchY < entry.getValue().y + expressButtonTextHeight;
}

private boolean isBooleanX1(int touchX, Map.Entry<Integer, Point> entry) {
    return touchX > entry.getValue().x && touchX < entry.getValue().x + (expressTextBackgroundWidth - 2 * expressTextMargin) / 4;
}

private boolean isBooleanXY2(int touchX, int touchY, Map.Entry<Integer, Point> entry) {
    return isBooleanX2(touchX, entry) && isBooleanY(touchY, entry);
}

private boolean isBooleanX2(int touchX, Map.Entry<Integer, Point> entry) {
    return touchX > entry.getValue().x + (expressTextBackgroundWidth - 2 * expressTextMargin) / 4 && touchX < entry.getValue().x + 2 * ((expressTextBackgroundWidth - 2 * expressTextMargin) / 4);
}


@Override
public void onDataChanged() {
    invalidate();
}

public interface OnExpressItemButtonClickListener {
    void onExpressItemButtonClick(int position, int status);
}

private OnExpressItemButtonClickListener onExpressItemButtonClickListener;


public void setOnExpressItemButtonClickListener(OnExpressItemButtonClickListener onExpressItemButtonClickListener) {
    this.onExpressItemButtonClickListener = onExpressItemButtonClickListener;
}

public void setAdapter(ExpressViewAdapter adapter){
    mAdapter = adapter;
    mAdapter.setOnDataChangedListener(this);
}

}

</pre>

ExpressViewAdapter
<pre>
/**

  • Created by licheng on 19/3/17.
    */

public abstract class ExpressViewAdapter<T> {

private OnDataChangedListener onDataChangedListener;

private List<T> dataList;

public ExpressViewAdapter(List<T> dataList) {
    this.dataList = dataList;
}

public int getCount(){
   return dataList == null ? 0 : dataList.size();
}

public T getItem(int position){
    return dataList == null ? null : dataList.get(position);
}

public abstract ExpressViewData bindData(ExpressView expressView, int position, T t);

public void notifyDataChanged(){
    onDataChangedListener.onDataChanged();
}

public interface OnDataChangedListener {
    void onDataChanged();
}

public void setOnDataChangedListener(OnDataChangedListener onDataChangedListener) {
    this.onDataChangedListener = onDataChangedListener;
}

}
</pre>

ExpressViewData
<pre>
/**

  • 物流控件數(shù)據(jù)接口
  • Created by licheng on 19/3/17.
    */

public class ExpressViewData {
private String content; //內(nèi)容
private String time; //時(shí)間
private String leftBtnText; //左按鈕文字
private String rightBtnText; //右按鈕文字

@Override
public String toString() {
    return "ExpressViewData{" +
            "content='" + content + '\'' +
            ", time='" + time + '\'' +
            ", leftBtnText='" + leftBtnText + '\'' +
            ", rightBtnText='" + rightBtnText + '\'' +
            '}';
}

public String getContent() {
    return content;
}

public void setContent(String content) {
    this.content = content;
}

public String getLeftBtnText() {
    return leftBtnText;
}

public void setLeftBtnText(String leftBtnText) {
    this.leftBtnText = leftBtnText;
}

public String getRightBtnText() {
    return rightBtnText;
}

public void setRightBtnText(String rightBtnText) {
    this.rightBtnText = rightBtnText;
}

public String getTime() {
    return time;
}

public void setTime(String time) {
    this.time = time;
}

}
</pre>

布局文件中的使用
<pre>
ExpressView
android:id="@+id/expressview"
android:layout_width="match_parent"
android:layout_height="match_parent"
express:circleToTextMargin="12dp"
express:expressCircleOuterRadius="8dp"
express:expressCircleRadius="6dp"
express:expressTextMargin="12dp"
express:expressTextSize="14sp"
express:expressTextVecPadding="5dp"
express:expressTimeTextSize="10sp"
express:firstExpressCircleMarginLeft="16dp"
express:firstExpressCircleMarginTop="16dp"
express:isTimeButtonVisible="true" />
</pre>

客戶(hù)端代碼
<pre>
//數(shù)據(jù)源
final List<ExpressMessageBean> list = new ArrayList<>();
ExpressMessageBean bean = new ExpressMessageBean();
bean.setFlowState(1);
bean.setFlowStateBtRight("購(gòu)買(mǎi)流程");
bean.setCreateTime(1487259871184l);
bean.setCreateTimeFormat(TimeUtils.millis2String(1487259871184l));
bean.setOpContent("您已付款0.1200元敞葛,購(gòu)買(mǎi) 地下城與勇士/廣東區(qū)/廣東1區(qū)帳號(hào),請(qǐng)聯(lián)系賣(mài)家卡羅特將密保手機(jī)綁定您的手機(jī)號(hào) 18827065959");
list.add(bean);
bean = new ExpressMessageBean();
bean.setFlowState(2);
bean.setCreateTime(1487259991260l);
bean.setCreateTimeFormat(TimeUtils.millis2String(1487259991260l));
bean.setOpContent("天空套 0.1200 1個(gè)-申請(qǐng)退款");
list.add(bean);
bean = new ExpressMessageBean();
bean.setFlowState(3);
bean.setCreateTime(1487259871184l);
bean.setCreateTimeFormat(TimeUtils.millis2String(1487259871184l));
bean.setOpContent("您已付款0.1200元与涡,購(gòu)買(mǎi) 地下城與勇士/廣東區(qū)/廣東1區(qū)帳號(hào)制肮,請(qǐng)聯(lián)系賣(mài)家卡羅特將密保手機(jī)綁定您的手機(jī)號(hào) 18827065959");
list.add(bean);
bean = new ExpressMessageBean();
bean.setFlowState(4);
bean.setFlowStateBtLeft("同意退款"); //設(shè)置左右按鈕文字
bean.setFlowStateBtRight("拒絕退款");
bean.setCreateTime(1487259991260l);
bean.setCreateTimeFormat(TimeUtils.millis2String(1487259991260l));
bean.setOpContent("天空套 0.1200 1個(gè)-申請(qǐng)退款");
list.add(bean);
bean = new ExpressMessageBean();
bean.setFlowState(5);
bean.setCreateTime(1487259871184l);
bean.setCreateTimeFormat(TimeUtils.millis2String(1487259871184l));
bean.setOpContent("您已付款0.1200元,購(gòu)買(mǎi) 地下城與勇士/廣東區(qū)/廣東1區(qū)帳號(hào)递沪,請(qǐng)聯(lián)系賣(mài)家卡羅特將密保手機(jī)綁定您的手機(jī)號(hào) 18827065959");
list.add(bean);
bean = new ExpressMessageBean();
bean.setFlowState(6);
bean.setCreateTime(1487259991260l);
bean.setCreateTimeFormat(TimeUtils.millis2String(1487259991260l));
bean.setOpContent("天空套 0.1200 1個(gè)-申請(qǐng)退款");
list.add(bean);
bean = new ExpressMessageBean();
bean.setFlowState(7);
bean.setCreateTime(1487259871184l);
bean.setCreateTimeFormat(TimeUtils.millis2String(1487259871184l));
bean.setOpContent("您已付款0.1200元豺鼻,購(gòu)買(mǎi) 地下城與勇士/廣東區(qū)/廣東1區(qū)帳號(hào),請(qǐng)聯(lián)系賣(mài)家卡羅特將密保手機(jī)綁定您的手機(jī)號(hào) 18827065959");
list.add(bean);
bean = new ExpressMessageBean();
bean.setFlowState(1);
bean.setFlowStateBtRight("購(gòu)買(mǎi)流程"); //設(shè)置右按鈕文字
bean.setCreateTime(1487259991260l);
bean.setCreateTimeFormat(TimeUtils.millis2String(1487259991260l));
bean.setOpContent("天空套 0.1200 1個(gè)-申請(qǐng)退款");
list.add(bean);
//數(shù)據(jù)源適配
adapter = new ExpressViewAdapter<ExpressMessageBean>(list) {
@Override
public ExpressViewData bindData(ExpressView expressView, int position, ExpressMessageBean expressMessageBean) {
ExpressViewData data = new ExpressViewData();
data.setContent(expressMessageBean.getOpContent());
data.setTime(expressMessageBean.getCreateTimeFormat());
data.setLeftBtnText(expressMessageBean.getFlowStateBtLeft());
data.setRightBtnText(expressMessageBean.getFlowStateBtRight());
return data;
}
};

    expressView.setAdapter(adapter);
    adapter.notifyDataChanged();

    //延遲4秒添加2條數(shù)據(jù)
    expressView.postDelayed(new Runnable() {
        @Override
        public void run() {
            ExpressMessageBean bean = new ExpressMessageBean();
            bean.setFlowState(1);
            bean.setFlowStateBtRight("購(gòu)買(mǎi)流程"); //設(shè)置右按鈕文字
            bean.setCreateTime(1487259991260l);
            bean.setCreateTimeFormat(TimeUtils.millis2String(1487259991260l));
            bean.setOpContent("天空套 0.1200 1個(gè)-申請(qǐng)退款");
            list.add(bean);
            bean = new ExpressMessageBean();
            bean.setFlowState(1);
            bean.setFlowStateBtRight("購(gòu)買(mǎi)流程"); //設(shè)置右按鈕文字
            bean.setCreateTime(1487259991260l);
            bean.setCreateTimeFormat(TimeUtils.millis2String(1487259991260l));
            bean.setOpContent("天空套 0.1200 1個(gè)-申請(qǐng)退款");
            list.add(bean);
            adapter.notifyDataChanged();
        }
    }, 4000);

    //處理點(diǎn)擊事件
    expressView.setOnExpressItemButtonClickListener(new ExpressView.OnExpressItemButtonClickListener() {
        @Override
        public void onExpressItemButtonClick(int position, int status) {
            switch (list.get(position).getFlowState()){
                case 1:
                    if(status == 1){ //購(gòu)買(mǎi)流程
                        ToastUtil.ToastBottow(TestActivity.this, list.get(position).getFlowStateBtRight());
                    }
                    break;
                case 4:
                    if(status == 0) { //同意退款
                        ToastUtil.ToastBottow(TestActivity.this, list.get(position).getFlowStateBtLeft());
                    } else if(status == 1){ //拒絕退款
                        ToastUtil.ToastBottow(TestActivity.this, list.get(position).getFlowStateBtRight());
                    }
                    break;
                default:
                    break;
            }
        }
    });

</pre>

目前控件存在的問(wèn)題

1款慨、沒(méi)有處理滑動(dòng)沖突
2儒飒、沒(méi)有處理滑動(dòng)到頂部和到底部停止滑動(dòng)的邏輯
3、沒(méi)有實(shí)現(xiàn)彈性滑動(dòng)的效果

源碼地址:
https://github.com/xiaomanzijia/ExpressView

本文中物流狀態(tài)控件的實(shí)現(xiàn)僅供參考學(xué)習(xí)檩奠,用于實(shí)際項(xiàng)目還需優(yōu)化桩了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市埠戳,隨后出現(xiàn)的幾起案子井誉,更是在濱河造成了極大的恐慌,老刑警劉巖整胃,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颗圣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡屁使,警方通過(guò)查閱死者的電腦和手機(jī)在岂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蛮寂,“玉大人蔽午,你說(shuō)我怎么就攤上這事〕晏#” “怎么了及老?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵抽莱,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我骄恶,道長(zhǎng)岸蜗,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任叠蝇,我火速辦了婚禮璃岳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悔捶。我一直安慰自己铃慷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蜕该。 她就那樣靜靜地躺著犁柜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪堂淡。 梳的紋絲不亂的頭發(fā)上馋缅,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音绢淀,去河邊找鬼萤悴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛皆的,可吹牛的內(nèi)容都是我干的覆履。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼费薄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼硝全!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起楞抡,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤伟众,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后召廷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凳厢,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年柱恤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了数初。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片找爱。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梗顺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出车摄,到底是詐尸還是另有隱情寺谤,我是刑警寧澤仑鸥,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站变屁,受9級(jí)特大地震影響眼俊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粟关,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一疮胖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闷板,春花似錦澎灸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至县遣,卻和暖如春糜颠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萧求。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工其兴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夸政。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓忌警,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親秒梳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子法绵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,152評(píng)論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,762評(píng)論 22 665
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件酪碘、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,105評(píng)論 4 62
  • 感賞自己終于改完了今天下午的學(xué)生考卷朋譬,洗了澡可以舒舒服服地躺在床上寫(xiě)感賞了。感賞今天是忙碌充實(shí)又愉快的一天兴垦,上午接...
    旦子閱讀 254評(píng)論 0 1
  • 據(jù)說(shuō)俺老哥在省內(nèi)攝影圈名氣越來(lái)越大了徙赢,又據(jù)說(shuō)俺老哥斬獲過(guò)若干國(guó)內(nèi)省內(nèi)金獎(jiǎng)、大獎(jiǎng)探越,還據(jù)說(shuō)俺老哥頻頻受邀到一些“高大...
    智型騎士閱讀 296評(píng)論 0 0