Android自定义View-销售计划完成率看板

话不多说,先上效果图:
<img src="https://img-blog.csdnimg.cn/20200321112309804.gif" width="320" height="640"/>

要点

  1. 支持滚动查看
  2. 支持两种显示模式切换

思路

根据效果图可整理思路
1.因为要实现左侧x轴固定,右侧可滑动,所以可将整个View看成左右两部分,分别为左侧自定义LeftView,及右侧的水平滚动视图ScrollView.然后再ScrollView中加入自定义RightView.这样即可实现左侧固定,右侧水平滑动的效果.


在这里插入图片描述

2.所以我们的View应该继承LinearLayout,用于放置左侧和右侧两个视图
3.由效果图可知,切换显示模式只需改变bar的宽度,及绘制的起始坐标.

代码实现

1.继承自LinearLayout并设置为水平方向排列.

public class PlanCompleteBarView extends LinearLayout {

   public PlanCompleteBarView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        setOrientation(HORIZONTAL);     
}

2.添加基本结构

    private void show() {
        removeAllViews();
        init();
        LayoutParams layoutParams = new LayoutParams(dp2px(leftWidth), ViewGroup.LayoutParams.MATCH_PARENT);
        addView(new LeftView(mContext), layoutParams);

        HorizontalScrollView scrollView = new HorizontalScrollView(mContext);
        scrollView.setHorizontalScrollBarEnabled(false);
        scrollView.addView(new RightView(mContext), new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        addView(scrollView, new LayoutParams(getMeasuredWidth() - dp2px(leftWidth), ViewGroup.LayoutParams.MATCH_PARENT));
    }

3.初始化必要参数

    private void init() {
        yPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        yPaint.setTextSize(sp2px(yTextSize));
        yPaint.setColor(yTextColor);

        xPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        xPaint.setTextSize(sp2px(xTextSize));
        xPaint.setColor(xTextColor);

        topPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        topPaint.setTextSize(sp2px(topTextSize));


        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setStrokeWidth(sp2px(lineWidth));
        linePaint.setColor(lineColor);

        planPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        planPaint.setStyle(Paint.Style.FILL);
        planPaint.setStrokeWidth(sp2px(barWidth));
        planPaint.setColor(planColor);

        completePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        completePaint.setStyle(Paint.Style.FILL);
        completePaint.setStrokeWidth(sp2px(barWidth));
        completePaint.setColor(completeColor);

        Paint.FontMetricsInt xFontMetricsInt = xPaint.getFontMetricsInt();
        marginBottom = xFontMetricsInt.bottom - xFontMetricsInt.top;

        Paint.FontMetricsInt topFontMetricsInt = topPaint.getFontMetricsInt();
        marginTop = topFontMetricsInt.bottom - topFontMetricsInt.top + topMarginBottom;
        //因为顶部有两行文字
        marginTop *= 2;
        drawHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop() - marginBottom - marginTop;
        percentHeight = drawHeight / maxValue;
        yLabels = new ArrayList<>();
        int i = maxValue / step;
        for (int j = 0; j <= step; j++) {
            yLabels.add(String.valueOf(j * i));
        }
    }

4.自定义左侧LeftView

         @Override
        protected void onDraw(Canvas canvas) {
            //将坐标系移动至右下角
            canvas.translate(mWidth, mHeight - marginBottom);
            //计算每一段的长度
            float i = maxValue / step;

            for (int j = 0; j <= step; j++) {
                float i1 = j * i * percentHeight;
                // canvas.drawLine(0,-i1,-mWidth,-i1,linePaint);
                String s = yLabels.get(j);
                float v1 = yPaint.measureText(s);
                //绘制y轴文字
                canvas.drawText(s, -v1 - dp2px(yPaddingRight), -i1, yPaint);
            }
        }

5.自定义右侧RightView
测量自身所需宽度

      @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //计算所需宽度
            int i = barWidth * mDataWrappers.size() + spaceWidth * mDataWrappers.size();
            setMeasuredDimension(dp2px(i), MeasureSpec.getSize(heightMeasureSpec));

        }

开始绘制

        @Override
        protected void onDraw(Canvas canvas) {
            //将坐标移动至左下角
            canvas.translate(0, mHeight);
            float i = maxValue / step;
            //绘制网格线
            for (int j = 0; j <= step; j++) {
                float i1 = j * i * percentHeight;
                canvas.drawLine(0, -i1 - marginBottom, mWidth, -i1 - marginBottom, linePaint);
            }
            for (int j = 0; j < mDataWrappers.size(); j++) {
                DataWrapper dataWrapper = mDataWrappers.get(j);
                String label = dataWrapper.label;
                if (j == 0) {
                    canvas.translate(0, -marginBottom);
                }
                //计算绘制的x中心
                float x = j * (dp2px(spaceWidth)
                        + dp2px(barWidth))
                        + dp2px(barWidth / 2)
                        + dp2px(spaceWidth / 2);
                //绘制x轴文字
                float textWidth = xPaint.measureText(label);
                Paint.FontMetrics fontMetrics = xPaint.getFontMetrics();
                canvas.drawText(label, x - textWidth / 2, marginBottom - fontMetrics.bottom, xPaint);
                //绘制bar
                if (BAR_STYLE_OVERLAY == barstyle) {
                    //堆叠样式,先绘制大的数值再绘制小的数值,形成覆盖的效果
                    if (dataWrapper.complete > dataWrapper.plan) {
                        canvas.drawLine(x, 0, x, -dataWrapper.complete * percentHeight, completePaint);
                        canvas.drawLine(x, 0, x, -dataWrapper.plan * percentHeight, planPaint);
                    } else {
                        canvas.drawLine(x, 0, x, -dataWrapper.plan * percentHeight, planPaint);
                        canvas.drawLine(x, 0, x, -dataWrapper.complete * percentHeight, completePaint);
                    }
                } else if (BAR_STYLE_TIE == barstyle) {
                    //排列样式
                    planPaint.setStrokeWidth(dp2px(barWidth / 2));
                    completePaint.setStrokeWidth(dp2px(barWidth / 2));
                    canvas.drawLine(x - dp2px(barWidth / 4), 0, x - dp2px(barWidth / 4),
                            -dataWrapper.plan * percentHeight, planPaint);
                    canvas.drawLine(x + dp2px(barWidth / 4), 0,
                            x + dp2px(barWidth / 4), -dataWrapper.complete * percentHeight, completePaint);
                }
                //绘制顶部文字   200%
                //              200/100
                //应为需要中心对齐,所以要分别绘制左边的数值和右边的数值
                String str = "/";
                float separatorWidth = topPaint.measureText(str);
                topPaint.setColor(percentColor);
                canvas.drawText(str, x - separatorWidth / 2,
                        -(Math.max(dataWrapper.complete, dataWrapper.plan) * percentHeight)
                                - dp2px(topMarginBottom), topPaint);
                //绘制完成数值200
                topPaint.setColor(completeColor);
                textWidth = topPaint.measureText(String.valueOf(dataWrapper.complete));
                canvas.drawText(String.valueOf(dataWrapper.complete), x - separatorWidth / 2 - textWidth,
                        -(Math.max(dataWrapper.complete, dataWrapper.plan) * percentHeight)
                                - dp2px(topMarginBottom), topPaint);
                //绘制计划数值100
                topPaint.setColor(planColor);
                canvas.drawText(String.valueOf(dataWrapper.plan), x + separatorWidth / 2,
                        -(Math.max(dataWrapper.complete, dataWrapper.plan) * percentHeight)
                                - dp2px(topMarginBottom), topPaint);
                //绘制百分比 200%
                topPaint.setColor(percentColor);
                float percent = (float) dataWrapper.complete / dataWrapper.plan * 100;
                str = (int) percent + "%";
                textWidth = topPaint.measureText(str);
                Paint.FontMetricsInt fontMetricsInt = topPaint.getFontMetricsInt();
                int textHeight = fontMetricsInt.bottom - fontMetricsInt.top;
                canvas.drawText(str, x - textWidth / 2, -(Math.max(dataWrapper.complete, dataWrapper.plan) * percentHeight)
                        - dp2px(topMarginBottom) - textHeight, topPaint);
            }
        }

完成.查看完整代码请跳转https://github.com/TanZhiL/PlanCompleteBarView

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

  • 前言 在Android应用的开发中少不了导航栏的使用,Android 3.0 推了 ActionBar, 5.0...
    Mersens阅读 4,309评论 2 13
  • 随着科技的发展,人工智能越来越遍及,而关于人工智能的利弊的讨论也愈发激烈。众所周知,人工智能的发展,给我们的生活带...
    dream扬阅读 262评论 0 0
  • 今天晚上《大约是爱》就结局了,卫卿和周是在结局会幸福的在一起。在看《大约是爱》的时候,我觉得有些事就好像注定的,周...
    云端流水阅读 1,140评论 1 4