银行类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); |
参数说明:
TODO:
动画
可设置阶段
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。