UIandroid自定义

仿支付宝大额充值到账流程-VerticalStepView

2018-04-05  本文已影响951人  Jaesoon

银行类App,最近需要开发一个大额充值的功能。中间涉及到一个解释大额充值到账流程的功能。好像没有合适的控件,于是自己撸了一个StepView。废话不多说,show you my code。嗯,规矩我懂,先给你看看图。


demo.png

Java Code:

package xin.smartlink.view;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;

import java.util.List;

/**
 * Created by Jaesoon on 2018/4/4.
 */
public class VerticalStepView extends View {
    private ItemDrawable[] itemDrawables;
    int textSize = 15;
    int descriptionTextSize = 12;
    int drawableSize = 30;
    int drawableMarginText = 10;
    int spacing = 30;
    private int mTextColor = Color.BLACK;
    private int mDescriptionTextColor = Color.GRAY;
    private int mProgressLineColor = Color.GRAY;
    private TextPaint mPaint;
    private List<Item> contentItems;

    public VerticalStepView(Context context) {
        super(context);
    }

    public VerticalStepView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public VerticalStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VerticalStepView);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);
            if (attr == R.styleable.VerticalStepView_textSize) {
                textSize = a.getDimensionPixelSize(attr, textSize);
            } else if (attr == R.styleable.VerticalStepView_drawableSize) {
                drawableSize = a.getDimensionPixelSize(attr, drawableSize);
            } else if (attr == R.styleable.VerticalStepView_spacing) {
                spacing = a.getDimensionPixelSize(attr, spacing);
            } else if (attr == R.styleable.VerticalStepView_drawableMarginText) {
                drawableMarginText = a.getDimensionPixelSize(attr, drawableMarginText);
            } else if (attr == R.styleable.VerticalStepView_textColor) {
                mTextColor = a.getColor(attr, mTextColor);
            } else if (attr == R.styleable.VerticalStepView_progressLineColor) {
                mProgressLineColor = a.getColor(attr, mProgressLineColor);
            } else if (attr == R.styleable.VerticalStepView_descriptionTextColor) {
                mDescriptionTextColor = a.getColor(attr, mDescriptionTextColor);
            } else if (attr == R.styleable.VerticalStepView_descriptionTextSize) {
                descriptionTextSize = a.getDimensionPixelSize(attr, descriptionTextSize);
            }
        }
        a.recycle();
        final Resources res = getResources();
        mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mPaint.density = res.getDisplayMetrics().density;
        mPaint.setColor(mTextColor);
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(textSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        layoutItem();
        if (itemDrawables != null) {
            int height = getWantHeight();
            if (height > getMeasuredHeight()) {
                requestLayout();
                invalidate();
                return;
            }
            for (int i = 0; i < itemDrawables.length; i++) {
                ItemDrawable textDrawable = itemDrawables[i];
                textDrawable.draw(canvas);
                if (i > 0) {
                    textDrawable.getBounds();
                    drawConnectLine(canvas, textDrawable.getBounds().left + drawableSize / 2, itemDrawables[i - 1].getBounds().top + drawableSize + drawableSize / 10, textDrawable.getBounds().top - drawableSize / 10);
                }
            }
        }
    }

    private int getWantHeight() {
        int height = 0;
        if (itemDrawables != null) {
            for (ItemDrawable itemDrawable : itemDrawables) {
                height += itemDrawable.getIntrinsicHeight();
                height += spacing;
            }
            height -= spacing;
            height += getPaddingTop() + getPaddingBottom();
        }
        return height;
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取宽度的测试模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//获取宽度的测试值
        int width;
        //如果宽度的测试模式等于EXACTLY,就直接赋值
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = 800;//使用我们自己在代码中定义的宽度
            //如果宽度的测试模式等于AT_MOST,取测量值和计算值的最小值
            if (widthMode == MeasureSpec.AT_MOST) {
                width = Math.min(width, widthSize);
            }
        }
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获取高度的测试模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//获取高度的测试值
        int height = 0;
        //如果高度的测试模式等于EXACTLY,就直接赋值
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            //计算出整个View的高度
            if (itemDrawables != null) {
                height = getWantHeight();
            } else {
                height = 200;
            }
            //如果高度的测试模式等于AT_MOST,取测量值和计算值的最小值
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(height, heightSize);
            }
        }
        setMeasuredDimension(width, height);//来存储测量的宽,高值
    }

    private void drawConnectLine(Canvas canvas, int startX, int startY, int endY) {
        Paint p = new Paint();
        p.setAntiAlias(true);
        p.setColor(mProgressLineColor);
        p.setStyle(Paint.Style.STROKE);
        p.setStrokeWidth(3.0f);
        DashPathEffect dashPathEffect = new DashPathEffect(new float[]{10, 10}, 0);
        p.setPathEffect(dashPathEffect);

        Path path = new Path();
        path.moveTo(startX, startY);
        path.lineTo(startX, endY);
        canvas.drawPath(path, p);
    }

    private void layoutItem() {
        if (contentItems == null || contentItems.isEmpty()) {
            return;
        }
        int left = getPaddingLeft(), top = getPaddingTop();
        itemDrawables = new ItemDrawable[contentItems.size()];
        for (int i = 0; i < contentItems.size(); i++) {
            ItemDrawable itemDrawable = new ItemDrawable();
            itemDrawables[i] = itemDrawable;
            itemDrawable.setmTextColor(mTextColor);
            itemDrawable.setmDescriptionTextColor(mDescriptionTextColor);
            itemDrawable.setContent(contentItems.get(i).getIcon(),
                    contentItems.get(i).getContent(), contentItems.get(i).getDescription(), mPaint, textSize, descriptionTextSize,
                    getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                    drawableSize,
                    drawableMarginText
            );
            itemDrawable.setBounds(new Rect(left, top, left + itemDrawable.getIntrinsicWidth(), top + itemDrawable.getIntrinsicHeight()));
            top += itemDrawable.getIntrinsicHeight();
            top += getSpacing();
        }
    }

    public int getTextSize() {
        return textSize;
    }

    public void setTextSize(int textSize) {
        this.textSize = textSize;
    }

    public int getDrawableSize() {
        return drawableSize;
    }

    public void setDrawableSize(int drawableSize) {
        this.drawableSize = drawableSize;
    }

    public int getSpacing() {
        return spacing;
    }

    public void setSpacing(int spacing) {
        this.spacing = spacing;
    }

    public List<Item> getContentItems() {
        return contentItems;
    }

    public void setContentItems(List<Item> contentItems) {
        this.contentItems = contentItems;
        invalidate();
    }

    private class ItemDrawable {
        private TextPaint mPaint;
        private String content = "";
        private String[] arrContent;
        private String description = "";
        private String[] arrDescription;
        private Rect bounds = new Rect();
        private Drawable icon;
        private int intrinsicWidth;
        private int intrinsicHeight;
        private int iconSize;
        private int iconMarginTextWidth;
        private int fontHeight;
        private int descriptionFontHeight;
        private int textSize;
        private int descriptionTextSize;
        private int mTextColor = Color.BLACK;
        private int mDescriptionTextColor = Color.GRAY;

        public int getmTextColor() {
            return mTextColor;
        }

        public void setmTextColor(int mTextColor) {
            this.mTextColor = mTextColor;
        }

        public int getmDescriptionTextColor() {
            return mDescriptionTextColor;
        }

        public void setmDescriptionTextColor(int mDescriptionTextColor) {
            this.mDescriptionTextColor = mDescriptionTextColor;
        }

        public String getContent() {
            return content;
        }

        /**
         * @param icon                左侧的图标
         * @param content             内容
         * @param width               总宽度
         * @param iconMarginTextWidth 文本与icon的距离
         * @param iconSize            icon宽度
         * @param p                   画笔
         */
        public void setContent(Drawable icon, String content, String description, TextPaint p, int textSize, int descriptionTextSize, int width, int iconSize, int iconMarginTextWidth) {
            this.mPaint = p;
            this.icon = icon;
            this.content = content;
            this.description = description;
            this.textSize = textSize;
            this.descriptionTextSize = descriptionTextSize;
            this.iconSize = iconSize;
            this.iconMarginTextWidth = iconMarginTextWidth;

            intrinsicWidth = width;
            intrinsicHeight = 0;

            Paint.FontMetricsInt fontMetrics = p.getFontMetricsInt();
            if (!TextUtils.isEmpty(description)) {
                p.setTextSize(descriptionTextSize);
                arrDescription = autoSplit(description, p, width - iconMarginTextWidth - iconSize);
                descriptionFontHeight = fontMetrics.bottom - fontMetrics.top;
                intrinsicHeight += arrDescription.length * descriptionFontHeight;
            }
            p.setTextSize(textSize);
            arrContent = autoSplit(content, p, width - iconMarginTextWidth - iconSize);
            fontMetrics = p.getFontMetricsInt();
            fontHeight = fontMetrics.bottom - fontMetrics.top;
            intrinsicHeight += arrContent.length * fontHeight;
            intrinsicHeight = Math.max(intrinsicHeight, iconSize);
        }

        public Rect getBounds() {
            return bounds;
        }

        public void setBounds(Rect bounds) {
            this.bounds = bounds;
            int top;
            top = fontHeight > iconSize ? bounds.top + (fontHeight - iconSize) / 2 : bounds.top;
            icon.setBounds(bounds.left,
                    top,
                    bounds.left + iconSize,
                    top + iconSize);
        }

        public void draw(Canvas canvas) {
            int left, top;
            left = bounds.left;
            //绘制Icon
            icon.draw(canvas);

            //绘制文字
            left += iconSize + iconMarginTextWidth;
            top = fontHeight > iconSize ? bounds.top : bounds.top + (iconSize - fontHeight) / 2;
            mPaint.setTextSize(textSize);
            mPaint.setColor(mTextColor);
            Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
            Rect targetRect = new Rect(bounds.left, top, bounds.right, top + fontHeight);
            int baseline = targetRect.top + (targetRect.bottom - targetRect.top - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
            for (String line : arrContent) {
                if (!TextUtils.isEmpty(line)) {
                    canvas.drawText(line, left, baseline, mPaint);
                    baseline += fontHeight;
                }
            }
            mPaint.setTextSize(descriptionTextSize);
            mPaint.setColor(mDescriptionTextColor);
            baseline -= fontHeight;
            baseline += descriptionFontHeight;
            if (arrDescription != null)
                for (String line : arrDescription) {
                    if (!TextUtils.isEmpty(line)) {
                        canvas.drawText(line, left, baseline, mPaint);
                        baseline += descriptionFontHeight;
                    }
                }
        }


        /**
         * 自动分割文本
         *
         * @param content 需要分割的文本
         * @param p       画笔,用来根据字体测量文本的宽度
         * @param width   最大的可显示像素(一般为控件的宽度)
         * @return 一个字符串数组,保存每行的文本
         */
        public String[] autoSplit(String content, TextPaint p, float width) {

            if (width <= 0) {
                return null;
            }
            int length = content.length();
            Rect bounds = new Rect();
            p.getTextBounds(content, 0, content.length(), bounds);
            float textWidth = bounds.width();
            if (textWidth <= width) {
                return new String[]{content};
            }

            int start = 0, end = 1, i = 0;
            int lines = (int) Math.ceil(textWidth / width); //计算行数
            String[] lineTexts = new String[lines];
            while (start < length && end < length) {
                if (p.measureText(content, start, end) >= width) {
                    lineTexts[i++] = content.substring(start, end - 1);
                    start = end - 1;
                    end--;
                }
                end++;
            }
            if (i < lineTexts.length)
                lineTexts[i++] = content.substring(start, end);
            return lineTexts;
        }

        public int getIntrinsicWidth() {
            return intrinsicWidth;
        }

        public void setIntrinsicWidth(int intrinsicWidth) {
            this.intrinsicWidth = intrinsicWidth;
        }

        public int getIntrinsicHeight() {
            return intrinsicHeight;
        }

        public void setIntrinsicHeight(int intrinsicHeight) {
            this.intrinsicHeight = intrinsicHeight;
        }

        public int getTextWidth(Paint paint, String str) {
            int w = 0;
            if (str != null && str.length() > 0) {
                int len = str.length();
                float[] widths = new float[len];
                paint.getTextWidths(str, widths);
                for (int j = 0; j < len; j++) {
                    w += (int) Math.ceil(widths[j]);
                }
            }
            return w;
        }
    }


    /**
     * 每一步对应的内容
     */
    public static class Item {
        /**
         * 内容
         */
        private String content;
        /**
         * 描述
         */
        private String description;
        /**
         * 左侧的图标
         */
        private Drawable icon;

        /**
         * 内容
         */
        public String getContent() {
            return content;
        }

        /**
         * 内容
         */
        public void setContent(String content) {
            this.content = content;
        }

        /**
         * 描述
         */
        public String getDescription() {
            return description;
        }

        /**
         * 描述
         */
        public void setDescription(String description) {
            this.description = description;
        }

        /**
         * 左侧的图标
         */
        public Drawable getIcon() {
            return icon;
        }

        /**
         * 左侧的图标
         */
        public void setIcon(Drawable icon) {
            this.icon = icon;
        }
    }
}

xml code:

    <declare-styleable name="VerticalStepView">
        <attr name="drawableSize" format="dimension" />
        <attr name="drawableMarginText" format="dimension" />
        <attr name="textSize" format="dimension" />
        <attr name="descriptionTextSize" format="dimension" />
        <attr name="textColor" format="color" />
        <attr name="progressLineColor" format="color" />
        <attr name="descriptionTextColor" format="color" />
        <attr name="spacing" format="dimension" />
    </declare-styleable>

图片资源:


icon_large_amount_recharge_bank.png icon_large_amount_recharge_finish.png icon_large_amount_recharge_start.png

Maybe you would ask how to use it?
1. 首先在layout文件中编写如下xml

<xin.smartlink.view.VerticalStepView
        android:id="@+id/vertical_step_view"
        android:layout_width="280dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="20dp"
        android:background="@color/white"
        android:padding="10dp"
        app:descriptionTextColor="@color/gray"
        app:descriptionTextSize="15sp"
        app:drawableMarginText="8dp"
        app:drawableSize="25dp"
        app:progressLineColor="#61a5db"
        app:spacing="25dp"
        app:textColor="@color/db_text_color_general"
        app:textSize="16sp" />

2. 在Java文件中,设置steps(内容、图片和解释(可选))

private VerticalStepView vertical_step_view;

vertical_step_view = (VerticalStepView) findViewById(R.id.vertical_step_view);

VerticalStepView.Item item = new VerticalStepView.Item();
item.setContent(String.format("通过银行APP或网银转账至%s大额充值专用账户", getString(R.string.custom_bank_name)));
item.setIcon(getResources().getDrawable(R.drawable.icon_large_amount_recharge_start));
items.add(item);

item = new VerticalStepView.Item();
item.setContent("核实转账,等待银行处理");
item.setIcon(getResources().getDrawable(R.drawable.icon_large_amount_recharge_bank));
items.add(item);

item = new VerticalStepView.Item();
item.setContent("充值到账");
item.setIcon(getResources().getDrawable(R.drawable.icon_large_amount_recharge_finish));
item.setDescription("完成银行转账后,一般2个工作日内充值到账,具体以银行时间为准");
items.add(item);

vertical_step_view.setContentItems(items);

参数说明:

参数 说明
drawableSize 左侧的icon大小(dp、sp、px)
drawableMarginText 左侧的icon与文字内容的间隔(dp、sp、px)
textSize 内容栏文字大小(dp、sp、px)
descriptionTextSize 解释栏文字大小(dp、sp、px)
textColor 内容栏文字颜色(color,reference)
descriptionTextColor 解释栏文字颜色(color,reference)
progressLineColor 进度连接线颜色(color,reference)
spacing 内容栏之间的间隔(dp、sp、px)

TODO:

  1. 动画
  2. 可设置阶段

欢迎转载,请留下作者信息
Author:Jaesoon
E-mail:jayyuz@163.com
简书主页:https://www.jianshu.com/u/561d203e9590

上一篇 下一篇

猜你喜欢

热点阅读