Android 自定义带节点的流程控件

以前常用自定义控件,然后到了这个公司之后发现大家都很少用,然后就慢慢的生疏了。但是呢自己以前自定义控件都是用各种view去拼。可是有的时候不是很好用。所以我还是鼓起勇气draw了一个。先来看看我们的需求吧,是这样的以个效果图:


是类似这样的一个流程节点,但是我觉得太丑了我后面改了一下,我们的界面总是这么不统一,每个界面的节点都不一样,在开始之前能先听我吐槽一下吗?算了你拒绝不了:


这也是一个


又是一个


还有一个

后面两个风格算统一的,只是显示的东西不一样而已,但是!我觉得这三类风格放在以前简直是灾难呀,效果图很是不好看,这是一个app耶风格能统一吗?

所以我依然决然的把图一和图4的风格合并了。其实也就是把中间的节点和线条换成了图4的,文字显示用图1的方式。

扫描二维码关注公众号,回复: 1588830 查看本文章

好了废话不多说,先上完整代码

先定义我们要用到的实体类,我叫它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);//当前走到哪一个节点了,就是已经完成的,正在进行的不算

具体的讲解我放在代码块的注释了,有不清楚和不对的地方可以留言

下面看看真正的效果图吧



猜你喜欢

转载自blog.csdn.net/xxx_19942/article/details/80675058