自定义View实例(四)----自定义条形柱状图

简介: 本来是想写一篇点赞效果的自定义View实例的,后来好基友因为项目紧急叫我帮他撸一个条形柱状图,大致瞄了一眼原型图以后便开撸了。最后做出来的效果与原型图八九不离十,看一下最后实现的效果图:一个普通的条形柱状图,统计的是12个月份两种状态的数值,选中的长条背景颜色会加深,并且显示当前两种状态的数值。

本来是想写一篇点赞效果的自定义View实例的,后来好基友因为项目紧急叫我帮他撸一个条形柱状图,大致瞄了一眼原型图以后便开撸了。最后做出来的效果与原型图八九不离十,看一下最后实现的效果图:

这里写图片描述

一个普通的条形柱状图,统计的是12个月份两种状态的数值,选中的长条背景颜色会加深,并且显示当前两种状态的数值。看看怎么实现的吧:

一.准备工作:

1.数据准备:

       myChartView = (MyChartView) findViewById(R.id.my_chart_view);
        relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout);
        Random random = new Random();
        while (list.size() < 24) {
            int randomInt = random.nextInt(100);
            list.add(randomInt);
        }
        myChartView.setList(list);

这里生成了24个100以内的随机整数,所以第二次进入的时候,柱状图的高度数值会有所改变。

2.设置属性,计算宽高,进行必要的初始化:

    public MyChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取我们自定义的样式属性
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView, defStyleAttr, 0);
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.MyChartView_leftColor:
                    // 默认颜色设置为黑色
                    leftColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_rightColor:
                    rightColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_xyColor:
                    lineColor = array.getColor(attr, Color.BLACK);
                    break;

            }
        }
        array.recycle();
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mBound = new Rect();
        mChartPaint = new Paint();
        mChartPaint.setAntiAlias(true);
        mShadowPaint = new Paint();
        mShadowPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = widthSize * 1 / 2;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = heightSize * 1 / 2;
        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mWidth = getWidth();
        mHeight = getHeight();
        mStartWidth = getWidth() / 13;
        mSize = getWidth() / 39;
        mChartWidth = getWidth() / 13 - mSize;
    }

相信看过前几篇博客的小伙伴,对这些代码以及非常熟悉了,这也是自定义View前必不可少的准备工作。

二.绘制柱状图:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPaint.setColor(lineColor);
        //画坐标轴
        canvas.drawLine(0, mHeight - 100, mWidth, mHeight - 100, mPaint);
        for (int i = 0; i < 12; i++) {
            //画刻度线
            canvas.drawLine(mStartWidth, mHeight - 100, mStartWidth, mHeight - 80, mPaint);
            //画数字
            mPaint.setTextSize(30);
            mPaint.setTextAlign(Paint.Align.CENTER);
            mPaint.getTextBounds(String.valueOf(i + 1) + "月", 0, String.valueOf(i).length(), mBound);
            canvas.drawText(String.valueOf(i + 1) + "月", mStartWidth - mBound.width() * 1 / 2, mHeight - 60 + mBound.height() * 1 / 2, mPaint);
            mStartWidth += getWidth() / 13;


        }
        //画柱状图
        for (int i = 0; i < list.size(); i++) {
            int size = mHeight / 150;
            //偶数
            if (i % 2 == 0) {
                mChartPaint.setColor(leftColor);
            } else {
                mChartPaint.setColor(rightColor);
            }
            mChartPaint.setStyle(Paint.Style.FILL);
            //画阴影
            if (i == number * 2 || i == number * 2 + 1) {
                mShadowPaint.setColor(Color.LTGRAY);

            } else {
                mShadowPaint.setColor(Color.WHITE);
            }
            canvas.drawRect(mChartWidth, 0, mChartWidth + mSize, mHeight - 100, mShadowPaint);
            //画柱状图
            canvas.drawRect(mChartWidth, mHeight - 100 - list.get(i) * size, mChartWidth + mSize, mHeight - 100, mChartPaint);// 长方形
            mChartWidth += (i % 2 == 0) ? (getWidth() / 39) : (getWidth() / 13 - mSize);

        }


    }

从上到下,循序渐进,这里分享一个小技巧:自定义View之前先在本子上把大致位置画出来,坐标在纸上算一遍,这样既不容易出错,也对自己写代码有帮助。实现不是很复杂,代码注释也比较清楚,就不详细分析了。

三.点击事件的实现:

示例图中可以看到,点击柱状图的不同位置,会加深点击区域的背景,并且显示当前数值,看看怎么实现的:

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        int x = (int) ev.getX();
        int y = (int) ev.getY();
        int left = 0;
        int top = 0;
        int right = mWidth / 12;
        int bottom = mHeight - 100;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                for (int i = 0; i < 12; i++) {
                    rect = new Rect(left, top, right, bottom);
                    left += mWidth / 12;
                    right += mWidth / 12;
                    if (rect.contains(x, y)) {
                        listener.getNumber(i, x, y);
                        number = i;
                        invalidate();
                    }
                }
                break;
        }
        return true;

    }

    public interface getNumberListener {
        void getNumber(int number, int x, int y);
    }

通过计算得到点击的区域代表的值,调用invalidate()方法进行重新绘制,加深背景颜色。
示例图中显示的数值会以一个小方块的形式显示出来,其实是通过接口回调得到点击区域代表的值,以及点击的x,y坐标。然后怎样在Activity中使用呢:

        myChartView.setListener(new MyChartView.getNumberListener() {
            @Override
            public void getNumber(int number, int x, int y) {
                relativeLayout.removeView(showText);
                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
                        RelativeLayout.LayoutParams.WRAP_CONTENT);
                params.leftMargin = x - 100;
                if (x - 100 < 0) {
                    params.leftMargin = 0;
                } else if (x - 100 > relativeLayout.getWidth() - showText.getWidth()) {
                    params.leftMargin = relativeLayout.getWidth() - showText.getWidth();
                }
                params.topMargin = 100;
                showText.setLayoutParams(params);
                showText.setTextColor(getResources().getColor(R.color.white));
                showText.setTextSize(10);
                showText.setText("选择的数字为:" + list.get(number * 2) + "," + list.get(number * 2 + 1));
                showText.setBackground(getResources().getDrawable(R.drawable.flag_01));
                relativeLayout.addView(showText);

            }
        });

实现接口,每次将这个显示的小方块动态添加到布局中去,显示点击的数值,加上x坐标左右的限制,防止出界。

四.注意事项:

之前几篇自定义View,有小伙伴和我反应,把应用切换至手机后台时,再打开应用,发现自定义的View会消失,这是因为Activity的生命周期影响了自定义View的生命周期。

可参考我之前一篇博客:Android自定义View探索(一)—生命周期

解决的办法有很多,推荐两个常用的:

1.在Activity中重写onResume()方法,设置自定义View需要的初始值

2.在自定义View方法中重写onWindowVisibilityChanged()方法,设置初始值

这篇博客我采用的是第二种方法:

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        if (visibility == VISIBLE) {
            mSize = getWidth() / 39;
            mStartWidth = getWidth() / 13;
            mChartWidth = getWidth() / 13 - mSize - 3;
        }
    }

这样只要你不退出当前界面,将应用切换至后台,过一段时间再次进入时,自定义View还是会保持最初的样子,不会消失的。

源码地址:

https://github.com/18722527635/MyCustomView

欢迎star,fork,提issues,一起进步!

希望能对你有所帮助,下一篇再见~~~

目录
相关文章
|
Python
mplfinance设置K线图中红涨绿跌的样式
要设置K线图中红涨绿跌的样式,您可以使用mplfinance库中的marketcolors参数来自定义K线图的颜色。默认情况下,mplfinance会使用红色表示上涨和绿色表示下跌,但您可以根据自己的需求进行修改。
846 1
31Echarts - 柱状图(特性示例:渐变色 阴影 点击缩放)
31Echarts - 柱状图(特性示例:渐变色 阴影 点击缩放)
106 0
|
数据可视化 数据挖掘
可视化 | Pyecharts象形柱图--图例自定义
可视化 | Pyecharts象形柱图--图例自定义
|
前端开发
Echarts柱状图y轴刻度标签图片和柱状渐变功能实现的解决方案
Echarts柱状图y轴刻度标签图片和柱状渐变功能实现的解决方案
278 0
|
数据可视化
数据可视化Echarts学习(2):柱图任务进度完成比例图属性练习柱图堆叠
数据可视化Echarts学习(2):柱图任务进度完成比例图属性练习柱图堆叠
121 0
如何用两种不同的方法动态绘制饼状图
如何用两种不同的方法动态绘制饼状图
199 0
如何用两种不同的方法动态绘制饼状图
|
数据可视化 BI Python
快速绘制动态排序图 — Pyecharts 高级组件 Timeline 实现!
之前写过一篇关于Python 制作 动态排序图的教程,里面利用的是 Matplotlib 中的 animation 函数,文章内容可参考动态排序图的详细制作教程,动态排序图的最终部分效果如下:
快速绘制动态排序图 — Pyecharts 高级组件 Timeline 实现!
|
测试技术
echarts 折线图 多条折线数据相同时展示的图形并没有重合
echarts 折线图 多条折线数据相同时展示的图形并没有重合
echarts 折线图 多条折线数据相同时展示的图形并没有重合
|
JavaScript 前端开发 关系型数据库
ECharts 饼状图颜色设置教程 - 4 种方式设置饼图颜色
ECharts 饼状图中的每个扇形颜色其实都可以自定义或者随机显示颜色。比如 X 轴是各销售渠道名,那么你可以需要使用全局统一的识别色彩,那么就需要指定每个扇面的颜色。本文讲解 4 种配置修改 ECharts 饼图颜色的方法。
2183 0
【MATLAB】基本绘图 ( 图形属性 | 绘图对象 | 图形属性界面 | 坐标轴属性 | 线属性 | 文本属性 | 图形属性设置策略 )(二)
【MATLAB】基本绘图 ( 图形属性 | 绘图对象 | 图形属性界面 | 坐标轴属性 | 线属性 | 文本属性 | 图形属性设置策略 )(二)
303 0
【MATLAB】基本绘图 ( 图形属性 | 绘图对象 | 图形属性界面 | 坐标轴属性 | 线属性 | 文本属性 | 图形属性设置策略 )(二)