Android自定义View--自己撸一个柱状图也没那么难_Hankkin_Coding的博客-程序员宅基地

技术标签: android开发  自定义View  柱状图  Android 自定义篇  android 开发进阶  

本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发。

绪论

*转眼间,2016伴随着互联网寒冬和帝都的雾霾马上就过去了,不知道大家今年一整年过得怎么样?最近票圈被各个城市的雾霾刷屏,
内心难免会动荡,庆幸自己早出来一年,也担忧着自己的未来的职业规划。无所谓了,既然选择了这个行业,我觉得大家就应该坚持下去,路是自己走的,及时再寒冬,只要你足够优秀,足够努力,相信你最后还是会找到自己满意的工作的。最后还要感谢今年博客之星大家对我的投票支持,非常感谢。不多说了,今天的主题是它–对,自定义View柱状图。
先来说说我最近在做什么吧?好久没有写博客了,最近手里有两个项目,闲的时候一直在忙着做项目,也封装了属于自己的一套Library,抽下来我会把它分享出来的。公司的项目也一直在忙,今天的柱状图就是公司的项目所用到的。先来看一下效果吧


*

这里写图片描述

背景

需求刚下来的时候我去网上找了一些开源的项目比如:
MpChart
hellocharts-android
AndroidCharts
等等有好多,但是最后为什么我选择了自定义,一来是这些开源库都很“重”,好多都用不到;二来很少有双条的柱状图;三来也想提高一下自己,仔细研究一下发现也并没有那么难。感兴趣的话可以去研究一下开源的那些图表,不过我曾经想过在上面的那几个基础上去修改,发现很难,所幸自己定义吧。


具体实现

可以看到,今天的柱状图分为三类:双条竖向柱状图、单条竖向柱状图以及单条横向柱状图,其实原理都是一样的,下面我们具体看一下怎么实现,怎么去画一个这样的柱状图。

双条竖向

我们可以看到这个柱状图主要包括下面几个方面:

  1. 双条柱状图
  2. 横坐标月份
  3. 点击tips显示具体数值
  4. 灰色阴影(图上没有显示具体看代码)
  5. 柱状图渐变、圆角、点击变色

好了上面五点就是需求和UI所提出来的所有东西,我们开始着手去“画”吧。


1.首先我们定义一些资源style供使用

包括
leftColor 左侧柱状图顶部颜色
leftColorBottom 左侧柱状图底部颜色
rightColor 右侧柱状图顶部颜色
rightColorBottom 右侧柱状图底部颜色
selectRightColor 左侧点击选中颜色
selectRightColor 右侧点击选中颜色
xyColor 横轴字体颜色

底部和顶部颜色是用于渐变用的

<declare-styleable name="MyChartView">
        <attr name="leftColor" format="color"></attr>
        <attr name="leftColorBottom" format="color"></attr>
        <attr name="selectLeftColor" format="color"></attr>
        <attr name="rightColor" format="color"></attr>
        <attr name="rightColorBottom" format="color"></attr>
        <attr name="selectRightColor" format="color"></attr>
        <attr name="xyColor" format="color"></attr>
    </declare-styleable>

2.接下来我们看具体代码,注释写的很详细了,仔细看:

  • 初始化属性、画笔、所用的size等
  • 测量计算高宽度等
  • 画坐标轴、画月份、画柱状图、画阴影
  • 柱状图渐变以及点击变色
  • touch点击事件判断点击所属哪个月份,接口回调给activity显示具体月份数值

    注意:onWindowVisibilityChanged这个方法(当屏幕焦点变化时重新侧向起始位置,必须重写次方法,否则当焦点变化时柱状图会跑到屏幕外面)
    

下面主要说一下绘制部分吧

OnDraw()部分

我们将每次onTouch的条的索引放到selectIndexRoles数组中,然后当这个数组包含该绘制的柱状图的索引是我们设置不用颜色以及不设置渐变;
同时我们给每两个双条之间的的空白处绘制成阴影;
最后drawRoundRect()就绘制了一个圆角的矩形。

//画柱状图
        for (int i = 0; i < list.size(); i++) {
            int size = mHeight / 120;
            if (selectIndexRoles.contains(i)) {
                //偶数
                mChartPaint.setShader(null);
                if (i % 2 == 0) {
                    mChartPaint.setColor(selectLeftColor);
                } else {
                    mChartPaint.setColor(selectRightColor);
                }
            } else {
                //偶数
                if (i % 2 == 0) {
                    LinearGradient lg = new LinearGradient(mChartWidth, mChartWidth + mSize, mHeight - 100,
                        (float) (mHeight - 100 - list.get(i) * size), lefrColorBottom, leftColor, Shader.TileMode.MIRROR);
                    mChartPaint.setShader(lg);
                } else {
                    LinearGradient lg = new LinearGradient(mChartWidth, mChartWidth + mSize, mHeight - 100,
                        (float) (mHeight - 100 - list.get(i) * size), rightColorBottom, rightColor, Shader.TileMode.MIRROR);
                    mChartPaint.setShader(lg);
                }
            }

            mChartPaint.setStyle(Paint.Style.FILL);
            //画阴影
            if (i == number * 2 || i == number * 2 + 1) {
                mShadowPaint.setColor(Color.BLUE);
            } else {
                mShadowPaint.setColor(Color.WHITE);
            }

            //画柱状图
            RectF rectF = new RectF();
            rectF.left = mChartWidth;
            rectF.right = mChartWidth + mSize;
            rectF.bottom = mHeight - 100;
            rectF.top = (float) (mHeight - 100 - list.get(i) * size);
            canvas.drawRoundRect(rectF, 10, 10, mChartPaint);
            //canvas.drawRect(mChartWidth, mHeight - 100 - list.get(i) * size, mChartWidth + mSize, mHeight - 100, mChartPaint)
            // ;// 长方形
            mChartWidth += (i % 2 == 0) ? (3 + getWidth() / 39) : (getWidth() / 13 - 3 - mSize);
        }
全部代码
package com.hankkin.mycartdemo.chatview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.hankkin.mycartdemo.R;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Hankkin on 2016/12/10.
 */

public class MyChartView extends View {
    

    private int leftColor;//双柱左侧
    private int rightColor;//双柱右侧
    private int lineColor;//横轴线
    private int selectLeftColor;//点击选中左侧
    private int selectRightColor;//点击选中右侧
    private int lefrColorBottom;//左侧底部
    private int rightColorBottom;//右侧底部
    private Paint mPaint, mChartPaint, mShadowPaint;//横轴画笔、柱状图画笔、阴影画笔
    private int mWidth, mHeight, mStartWidth, mChartWidth, mSize;//屏幕宽度高度、柱状图起始位置、柱状图宽度
    private Rect mBound;
    private List<Float> list = new ArrayList<>();//柱状图高度占比
    private Rect rect;//柱状图矩形
    private getNumberListener listener;//点击接口
    private int number = 1000;//柱状图最大值
    private int selectIndex = -1;//点击选中柱状图索引
    private List<Integer> selectIndexRoles = new ArrayList<>();

    public void setList(List<Float> list) {
        this.list = list;
        mSize = getWidth() / 39;
        mStartWidth = getWidth() / 13;
        mChartWidth = getWidth() / 13 - mSize - 3;
        invalidate();
    }

    public void setListener(getNumberListener listener) {
        this.listener = listener;
    }

    public MyChartView(Context context) {
        this(context, null);
    }

    public MyChartView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    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_selectLeftColor:
                    // 默认颜色设置为黑色
                    selectLeftColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_rightColor:
                    rightColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_selectRightColor:
                    selectRightColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_xyColor:
                    lineColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_leftColorBottom:
                    lefrColorBottom = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_rightColorBottom:
                    rightColorBottom = 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);
        mShadowPaint.setColor(Color.WHITE);
    }

    //测量高宽度
    @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;
    }

    //重写onDraw绘制
    @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(35);
            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 / 120;
            if (selectIndexRoles.contains(i)) {
                //偶数
                mChartPaint.setShader(null);
                if (i % 2 == 0) {
                    mChartPaint.setColor(selectLeftColor);
                } else {
                    mChartPaint.setColor(selectRightColor);
                }
            } else {
                //偶数
                if (i % 2 == 0) {
                    LinearGradient lg = new LinearGradient(mChartWidth, mChartWidth + mSize, mHeight - 100,
                        (float) (mHeight - 100 - list.get(i) * size), lefrColorBottom, leftColor, Shader.TileMode.MIRROR);
                    mChartPaint.setShader(lg);
                } else {
                    LinearGradient lg = new LinearGradient(mChartWidth, mChartWidth + mSize, mHeight - 100,
                        (float) (mHeight - 100 - list.get(i) * size), rightColorBottom, rightColor, Shader.TileMode.MIRROR);
                    mChartPaint.setShader(lg);
                }
            }

            mChartPaint.setStyle(Paint.Style.FILL);
            //画阴影
            if (i == number * 2 || i == number * 2 + 1) {
                mShadowPaint.setColor(Color.BLUE);
            } else {
                mShadowPaint.setColor(Color.WHITE);
            }

            //画柱状图
            RectF rectF = new RectF();
            rectF.left = mChartWidth;
            rectF.right = mChartWidth + mSize;
            rectF.bottom = mHeight - 100;
            rectF.top = (float) (mHeight - 100 - list.get(i) * size);
            canvas.drawRoundRect(rectF, 10, 10, mChartPaint);
            //canvas.drawRect(mChartWidth, mHeight - 100 - list.get(i) * size, mChartWidth + mSize, mHeight - 100, mChartPaint)
            // ;// 长方形
            mChartWidth += (i % 2 == 0) ? (3 + getWidth() / 39) : (getWidth() / 13 - 3 - mSize);
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {

        }
    }

    /**
     * 注意:
     * 当屏幕焦点变化时重新侧向起始位置,必须重写次方法,否则当焦点变化时柱状图会跑到屏幕外面
     */

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

    /**
     * 柱状图touch事件
     * 获取触摸位置计算属于哪个月份的
     * @param ev
     * @return
     */
    @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;
                        selectIndex = i;
                        selectIndexRoles.clear();
                        ;
                        selectIndexRoles.add(selectIndex * 2 + 1);
                        selectIndexRoles.add(selectIndex * 2);
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }

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

    public int getLeftColor() {
        return leftColor;
    }

    public void setLeftColor(int leftColor) {
        this.leftColor = leftColor;
    }

    public int getRightColor() {
        return rightColor;
    }

    public void setRightColor(int rightColor) {
        this.rightColor = rightColor;
    }

    public int getLineColor() {
        return lineColor;
    }

    public void setLineColor(int lineColor) {
        this.lineColor = lineColor;
    }

    public int getSelectLeftColor() {
        return selectLeftColor;
    }

    public void setSelectLeftColor(int selectLeftColor) {
        this.selectLeftColor = selectLeftColor;
    }

    public int getSelectRightColor() {
        return selectRightColor;
    }

    public void setSelectRightColor(int selectRightColor) {
        this.selectRightColor = selectRightColor;
    }

    public int getLefrColorBottom() {
        return lefrColorBottom;
    }

    public void setLefrColorBottom(int lefrColorBottom) {
        this.lefrColorBottom = lefrColorBottom;
    }

    public int getRightColorBottom() {
        return rightColorBottom;
    }

    public void setRightColorBottom(int rightColorBottom) {
        this.rightColorBottom = rightColorBottom;
    }
}

3.具体使用:

private void initChatView() {

        myChartView.setLefrColorBottom(getResources().getColor(R.color.leftColorBottom));
        myChartView.setLeftColor(getResources().getColor(R.color.leftColor));
        myChartView.setRightColor(getResources().getColor(R.color.rightColor));
        myChartView.setRightColorBottom(getResources().getColor(R.color.rightBottomColor));
        myChartView.setSelectLeftColor(getResources().getColor(R.color.selectLeftColor));
        myChartView.setSelectRightColor(getResources().getColor(R.color.selectRightColor));
        myChartView.setLineColor(getResources().getColor(R.color.xyColor));
        chartList = new ArrayList<>();

        relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout);
        relativeLayout.removeView(llChart);
        Random random = new Random();
        while (chartList.size() < 24) {
            int randomInt = random.nextInt(100);
            chartList.add((float) randomInt);
        }
        myChartView.setList(chartList);
        myChartView.setListener(new MyChartView.getNumberListener() {
            @Override
            public void getNumber(int number, int x, int y) {
                relativeLayout.removeView(llChart);
                //反射加载点击柱状图弹出布局
                llChart = (LinearLayout) LayoutInflater.from(MainActivity.this).inflate(R.layout.layout_shouru_zhichu, null);
                TextView tvZhichu = (TextView) llChart.findViewById(R.id.tv_zhichu);
                TextView tvShouru = (TextView) llChart.findViewById(R.id.tv_shouru);
                tvZhichu.setText((number + 1) + "月支出" + " " + chartList.get(number * 2));
                tvShouru.setText ( "收入: " + chartList.get(number * 2 + 1));
                llChart.measure(0, 0);//调用该方法后才能获取到布局的宽度
                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() - llChart.getMeasuredWidth()) {
                    //设置布局距左侧屏幕宽度减去布局宽度
                    params.leftMargin = relativeLayout.getWidth() - llChart.getMeasuredWidth();
                }
                llChart.setLayoutParams(params);
                relativeLayout.addView(llChart);
            }
        });
    }

经过以上步骤,我们的双条竖向柱状图就绘制完成了,也显示出来了。其实自己坐下来仔细拿笔算一下,画一下,也没有想象的那么难。至于单条和横向的实现原理都一样,比这个要简单的多。哦对了,横向的我只是自定义了一个横向的柱状图View,然后用ListView显示的各个部门的具体内容。好了最后感谢我的朋友帮了我很多忙Young_Kai,大家也可以看看他的博客,很不错的。

代码我已经上传我的Github,欢迎大家star,fork:
https://github.com/Hankkin/MyCartDemo

最后推荐一下老刘解析的MPChart

http://blog.csdn.net/qq_26787115

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lyhhj/article/details/53771477

智能推荐

hdu2601 An easy problem(数论)_hdu2601t_高1024的博客-程序员宅基地

An easy problemTime Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5758    Accepted Submission(s): 1411Problem DescriptionWhen Teddy

点赞模块设计_zhaoyang10的博客-程序员宅基地

https://juejin.im/post/5bdc257e6fb9a049ba410098

linux连接mysql常用命令_lunix链接mysql_你就像甜甜的益达的博客-程序员宅基地

//连接mysql:mysql -h 127.0.0.1 -P 3306 -u root -proot//docker连接本地mysql:docker exec -it root bash;mysql -uroot -proot;//docker操作自己mysql部分操作//启动mysql容器docker run -di --name=root -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root mysql:5.6//进入mysql容器docker exe

cocos creator 利用 zlib.js 进行数据的 压缩与解压_无赖君的博客-程序员宅基地

cocos creator 利用 zlib.js 进行数据的 压缩与解压。因本项目在cocos 项目中,使用了egret的部分数据结构,例如 egret.ByteArray。所以这里用举例。作者Githttps://github.com/imaya/zlib.js 文档不难也蛮详细的。关键代码:varinflate=newZlib.Gu...

使用C语言进行面向对象的开发--GObject入门[9]_g_object_set_yanbixing123的博客-程序员宅基地

转自: blog.csdn.net/pingf0 或www.cnblogs.com/pingfPART 9        注:这一部分的大部分内容源自google wallpaper上对gtk mail archive上关于gobject的一些翻译,这里只是引用了下【链接见前文】,因为原文对此部分的描述还是相当不错的,至少比我觉的自己要写还写不了这么好。    属性

QGC源码分析——UI界面的启动流程(从mian.cc到五大视图)_qgc五大页页面_火山上的企鹅的博客-程序员宅基地

src\main.cc:400://start_mark_xx_20200324_main.cc-1QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);//start_mark_xx_20200324_main.cc-2QGCApplication* app = new QGCApplication(argc, argv, runUnitTests);…#endifif (!app-&gt;_initForNormalAppBoot

随便推点

PHPExcel导出excel_weixin_34258782的博客-程序员宅基地

//设置PHPExcel类库的includepathset_include_path('.'.PATH_SEPARATOR.'D:\workspace\biznaligy_eh\dev_src\includes\PHPExcel'.PATH_SEPARATOR.get_include_path());require_once'PHPExcel.ph...

【转】一分钟读懂互联网广告竞价策略GFP+GSP+VCG_weixin_34235457的博客-程序员宅基地

参考这篇文章:http://ju.outofmemory.cn/entry/116780一分钟读懂互联网广告竞价策略GFP+GSP+VCG两个广告位,三家广告主竞价,广告平台究竟应该制定广告竞价策略呢?这是本文即将分享的一个问题。一、前序知识-传统竞价策略英式拍卖(English Auction)英式拍卖又叫公开增价拍卖OAB(Open Ascending Bid),卖家提供...

ROS安装及常见问题解决——适用于melodic和kinetic_rosmelodic和kinetic_科比遇见布莱恩特的博客-程序员宅基地

本文介绍如何在ubuntu18.04上安装ROS Melodic。其实在ubuntu16.04上安装kinetic也是类似的,一般来说是将Melodic改为kinetic就可以了。大家可以先参考官网的安装方式KineticMelodic两个ROS版本与Ubuntu的版本是一一对应的。即Kinetic——Ubuntu16.04Melodic——Ubuntu18.04推荐使用双系统安装,如果实在不方便或者只是日常体验的可以使用虚拟机虚拟机使用比较多的就是VMware Workstation和Or

win10安装virtualbox centos7_George_Fal的博客-程序员宅基地

http://wq1993en.blog.163.com/blog/static/21614010420169275307783/请看小白网易博客 转载于:https://blog.51cto.com/linawang/1866842

mui手机端页面下拉刷新,上拉刷新_吴举人的博客-程序员宅基地

最近套了一个手机端的页面,关于页面上列表形式展现的数据,数据太多,为了优化操作体验,使用了mui下拉刷新的功能,这里将这个模板功能记录下来,方便以后参考。1.使用mui的下拉刷新,最基本的要引入mui的样式和js&lt;link rel="stylesheet" href="css/mui.min.css"&gt;&lt;script src="js/mui.min.js"&gt;&lt;...

CS61A 2021Spring Lab: Cats_m0_53588133的博客-程序员宅基地

“打字比赛”:该程序能够测量打字速度,并在用户输入每一个字母后显示输入的内容是否正确。"""Typing test implementation"""from utils import lower, split, remove_punctuation, lines_from_filefrom ucb import main, interact, tracefrom datetime import datetime############ Phase 1 ############.

推荐文章

热门文章

相关标签