以前常用自定义控件,然后到了这个公司之后发现大家都很少用,然后就慢慢的生疏了。但是呢自己以前自定义控件都是用各种view去拼。可是有的时候不是很好用。所以我还是鼓起勇气draw了一个。先来看看我们的需求吧,是这样的以个效果图:
是类似这样的一个流程节点,但是我觉得太丑了我后面改了一下,我们的界面总是这么不统一,每个界面的节点都不一样,在开始之前能先听我吐槽一下吗?算了你拒绝不了:
这也是一个
又是一个
还有一个
后面两个风格算统一的,只是显示的东西不一样而已,但是!我觉得这三类风格放在以前简直是灾难呀,效果图很是不好看,这是一个app耶风格能统一吗?
所以我依然决然的把图一和图4的风格合并了。其实也就是把中间的节点和线条换成了图4的,文字显示用图1的方式。
好了废话不多说,先上完整代码
先定义我们要用到的实体类,我叫它FLowChart.java
public class FlowChart { private String name; private String topName; private String bottomName; private String time; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTopName() { return topName; } public void setTopName(String topName) { this.topName = topName; } public String getBottomName() { return bottomName; } public void setBottomName(String bottomName) { this.bottomName = bottomName; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } }稍稍解释一下name不做显示,就是代码的节点的名字,topName就是节点上方的文字,bottomName是节点下方的文字,time 是bottomName下方的文字,不要问我为什么这儿么取名,我就是实在不想取名了。可以自己调整名字,但是结构就是这么个结构
FLowLineView.java 这个就是我们在布局的时候要用的控件了
public class FLowLineView extends View { /** * 圆的直径 */ private int mRoundSize = 20; /** * 光晕透明度 */ private int haloAlpha = 50; /** * 光晕的宽度 */ private int haloWidth = 5; /** * 节点画笔 */ private Paint mPaint; /** * 文字描述的画笔 */ private TextPaint tp; /** * 流程线的画笔 */ private Paint mLinePaint; /** * 当前已完成的最新节点 */ private int tag; /** * 需要显示的流程节点集合 */ private List<FlowChart> mFlowCharts = new ArrayList<>(); /** * 每一个节点item(包含文字)所要占用的最小宽度即最长文字的宽度 */ private int mItemMaxWidth; /** * 当前控件宽度 */ private int mCurrViewWidth; /** * 当前控件高度 */ private int mCurrViewHeight=0; /** * 存放每个item最长的文字宽度 */ private List<Integer> mItemMaxTextViewWidthList = new ArrayList<>(); /** * 布局是否超出屏幕 */ private boolean full; /** * 流程线的高度 */ private int lineHeight = 5; /** * 文字大小 */ private int textSize = 33; /** * 已完成节点颜色 */ private int doneColor; /** * 进行中节点颜色 */ private int doingColor; /** * 未开始节点颜色 */ private int todoColor; /** * 行间距 */ private int rowRpacing = 30; private boolean doubleBottom; public FLowLineView(Context context) { super(context, null, 0); } public FLowLineView(Context context, @Nullable AttributeSet attrs) { super (context, attrs, 0); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FLowLineView);//下面是在读取布局里面设置的属性值 doneColor = typedArray.getColor(R.styleable.FLowLineView_doneColor, Color.parseColor("#5DBF19")); doingColor = typedArray.getColor(R.styleable.FLowLineView_doingColor, Color.parseColor("#FD6067")); todoColor = typedArray.getColor(R.styleable.FLowLineView_todongColor, Color.parseColor("#c1c1c1")); textSize = typedArray.getDimensionPixelSize(R.styleable.FLowLineView_textSize, textSize); lineHeight = typedArray.getDimensionPixelSize(R.styleable.FLowLineView_lineHeight, lineHeight); haloWidth = typedArray.getDimensionPixelSize(R.styleable.FLowLineView_haloWidth, haloWidth); haloAlpha = typedArray.getInt(R.styleable.FLowLineView_haloAlpha, haloAlpha); rowRpacing = typedArray.getDimensionPixelSize(R.styleable.FLowLineView_rowRpacing, rowRpacing); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); DisplayMetrics dm = getResources().getDisplayMetrics(); //圆的半径 int radius = mRoundSize / 2; int startY = rowRpacing+textSize/2; mPaint = new Paint(); mLinePaint = new Paint(); tp = new TextPaint();//以上三行可以放在初始化的地方没这样子写其实很不优雅,但是我现在不想改 for (int a = 0; a < mFlowCharts.size(); a++) { tp.setTextSize(textSize); tp.setTypeface(Typeface.SANS_SERIF); //文字字体加粗 tp.setFakeBoldText(false); //笔宽5像素 mLinePaint.setStrokeWidth(lineHeight); //下面开始设置画笔的颜色这些 if (a <= tag) { mPaint.setColor(doneColor); tp.setColor(doneColor); mLinePaint.setColor(doneColor); } else if (a == tag + 1) { mPaint.setColor(doingColor); tp.setColor(doingColor); mLinePaint.setColor(todoColor); } else { mPaint.setColor(todoColor); tp.setColor(todoColor); mLinePaint.setColor(todoColor); } // 计算文字的宽度 int topTextWidth = getTextWidth(tp, StringUtils.cleanString(mFlowCharts.get(a).getTopName())); int bottomTextWidth = getTextWidth(tp, StringUtils.cleanString(mFlowCharts.get(a).getBottomName())); int timeTextX = getTextWidth(tp, StringUtils.cleanString(mFlowCharts.get(a).getTime())); int maxTextWidth; int topX; int bottomX; int timeX; int roundCenterX; if (full) { maxTextWidth = mItemMaxWidth; } else { maxTextWidth = Math.max(topTextWidth, bottomTextWidth); if (doubleBottom) { maxTextWidth = Math.max(maxTextWidth, timeTextX); } maxTextWidth += 20; } roundCenterX = mItemMaxWidth * a + maxTextWidth / 2; topX = roundCenterX - topTextWidth / 2; bottomX = roundCenterX - bottomTextWidth / 2; timeX = roundCenterX - timeTextX / 2; // 绘制文字 canvas.drawText(StringUtils.cleanString(mFlowCharts.get(a).getTopName()), topX, startY, tp); canvas.drawText(StringUtils.cleanString(mFlowCharts.get(a).getBottomName()), bottomX, startY+rowRpacing*2+mRoundSize+haloWidth*2, tp); if (doubleBottom) {//不显示最后一行文字 canvas.drawText(StringUtils.cleanString(mFlowCharts.get(a).getTime()), timeX, startY + rowRpacing * 3 + textSize / 2 + mRoundSize + haloWidth * 2, tp); } if (a < mFlowCharts.size() - 1) { // 绘制线 canvas.drawLine(roundCenterX , startY+rowRpacing, roundCenterX + mItemMaxWidth, startY+rowRpacing, mLinePaint); } // 画圆 RectF rf2 = new RectF(roundCenterX - radius, startY+rowRpacing - radius, roundCenterX + 10, startY+rowRpacing + radius); canvas.drawOval(rf2, mPaint); if (a == tag + 1) { mPaint.setAlpha(haloAlpha); RectF haloRectF = new RectF(roundCenterX - (radius + haloWidth), startY+rowRpacing - radius - haloWidth, roundCenterX + (radius + haloWidth), startY+rowRpacing + radius + haloWidth); canvas.drawOval(haloRectF, mPaint); } mPaint.reset(); tp.reset(); } } /** * 计算文字宽度 */ public static int getTextWidth(Paint paint, String str) { int iRet = 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++) { iRet += (int) Math.ceil(widths[j]); } } return iRet; } public void setFlowCharts(List<FlowChart> flowCharts) { if (flowCharts == null) { flowCharts = new ArrayList<>(); } mFlowCharts = flowCharts; DisplayMetrics dm = getResources().getDisplayMetrics(); int width = dm.widthPixels; if (width < getViewMinWidth()) { width = getViewMinWidth() + 20 * (mFlowCharts.size() + 1); mItemMaxWidth = width / mFlowCharts.size(); full = true; } else { mItemMaxWidth = (width - 20 - getFlowMinWidth(mFlowCharts.get(0)) / 2 - getFlowMinWidth(mFlowCharts.get(mFlowCharts.size() - 1)) / 2) / (mFlowCharts.size() - 1); full = false; } mCurrViewWidth = width; if (doubleBottom) {//不现实最后一行文字就不参与计算 mCurrViewHeight = rowRpacing + textSize / 2 + rowRpacing * 3 + textSize / 2 + mRoundSize + haloWidth * 2+rowRpacing; }else { mCurrViewHeight = rowRpacing + textSize / 2 +rowRpacing*2+mRoundSize+haloWidth*2+rowRpacing; } } public void setTag(int tag) { this.tag = tag; } /** * 获取所有文字占用的最小宽度 * * @return */ public int getViewMinWidth() { int minWidth; if (mFlowCharts != null && !mFlowCharts.isEmpty()) { for (FlowChart chart : mFlowCharts) { // 计算文字的宽度 mItemMaxTextViewWidthList.add(getFlowMinWidth(chart)); } } else { } int max = Collections.max(mItemMaxTextViewWidthList); minWidth = (max+20) * mFlowCharts.size(); return minWidth; } /** * 计算每个item的最长文字占用宽度作为item的最小宽度 * * @param chart * @return */ public int getFlowMinWidth(FlowChart chart) { tp = new TextPaint(); tp.setTextSize(textSize); tp.setTypeface(Typeface.SANS_SERIF); //文字字体加粗 tp.setFakeBoldText(false); int topTextWidth = getTextWidth(tp, StringUtils.cleanString(chart.getTopName())); int bottomTextWidth = getTextWidth(tp, StringUtils.cleanString(chart.getBottomName())); int timeTextX = getTextWidth(tp, StringUtils.cleanString(chart.getTime())); int maxTextWidth = Math.max(topTextWidth, bottomTextWidth); if (doubleBottom) { maxTextWidth = Math.max(maxTextWidth, timeTextX); } return maxTextWidth; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(mCurrViewWidth, mCurrViewHeight); } public void setDoingColor(int doingColor) { this.doingColor = doingColor; } public void setDoneColor(int doneColor) { this.doneColor = doneColor; } public void setTodoColor(int todoColor) { this.todoColor = todoColor; } public void setHaloAlpha(int haloAlpha) { this.haloAlpha = haloAlpha; } public void setHaloWidth(int haloWidth) { this.haloWidth = haloWidth; } public void setRoundSize(int roundSize) { mRoundSize = roundSize; } public void setLineHeight(int lineHeight) { this.lineHeight = lineHeight; } public void setTextSize(int textSize) { this.textSize = textSize; } public void setDoubleBottom(boolean doubleBottom) { this.doubleBottom = doubleBottom; }}
下面是用到的方法
/** * 判断是否字段的值是否为空或null或"null"字符串 * * @param str * @return */ public static String cleanString(String str) { if (TextUtils.isEmpty(str) || "null".equalsIgnoreCase(str)) { return ""; } else { return str; } }
attrs.xml 这个就是我们定义的属性哟:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="FLowLineView"> <!--文字大小--> <attr name="textSize" format="dimension"/> <!--节点远点直径--> <attr name="mRoundSize" format="dimension"/> <!--进行中节点光晕透明度--> <attr name="haloAlpha" format="integer"/> <!--光晕宽度--> <attr name="haloWidth" format="dimension"/> <!--流程线条高--> <attr name="lineHeight" format="dimension"/> <!--已完成节点颜色--> <attr name="doneColor" format="color"/> <!--进行中节点颜色--> <attr name="doingColor" format="color"/> <!--未开始节点颜色--> <attr name="todongColor" format="color"/> <!--显示内容的行间距--> <attr name="rowRpacing" format="dimension"/> </declare-styleable> </resources>上面整个自定义控件就算完成了,接下来就是使用了
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.sigel.test.activity.MainActivity"> <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <com.core.sigel.sibase.view.FLowLineView android:id="@+id/view" android:layout_width="match_parent" android:layout_height="wrap_content"/> </HorizontalScrollView> </RelativeLayout>
必须要在外面包一个HorizontalScrollView哟,因为流程可能很长,不加这个就显示不全。不要问我为什么不直接做成可滑动了,因为我还没研究,俗称我还不会,没时间去看,而且这样很简单呀哈哈哈哈。
具体使用
FLowLineView mFLowLineView = findViewById(R.id.view); List<FlowChart> charts = new ArrayList<>(); FlowChart chart = new FlowChart(); chart.setTopName("节点"); chart.setName("节点"); chart.setBottomName("张月明1000000000"); chart.setTime("2018-09-09 00:00:00"); charts.add(chart); charts.add(chart); charts.add(chart); charts.add(chart); mFLowLineView.setDoubleBottom(true);//是否是小时两个下方文字,就是想要想图一那么显示就要是true就是显示了三行文字,false就是只显示两行文字,上面一行下面一行 mFLowLineView.setTextSize(40);//文字大小 mFLowLineView.setFlowCharts(charts); mFLowLineView.setTag(1);//当前走到哪一个节点了,就是已经完成的,正在进行的不算
具体的讲解我放在代码块的注释了,有不清楚和不对的地方可以留言
下面看看真正的效果图吧