绪论
转眼间,2016伴随着互联网寒冬和帝都的雾霾马上就过去了,不知道大家今年一整年过得怎么样?最近票圈被各个城市的雾霾刷屏,内心难免会动荡,庆幸自己早出来一年,也担忧着自己的未来的职业规划。无所谓了,既然选择了这个行业,我觉得大家就应该坚持下去,路是自己走的,及时再寒冬,只要你足够优秀,足够努力,相信你最后还是会找到自己满意的工作的。最后还要感谢今年博客之星大家对我的投票支持,非常感谢。不多说了,今天的主题是它–对,自定义View柱状图。
先来说说我最近在做什么吧?好久没有写博客了,最近手里有两个项目,闲的时候一直在忙着做项目,也封装了属于自己的一套Library,抽下来我会把它分享出来的。公司的项目也一直在忙,今天的柱状图就是公司的项目所用到的。先来看一下效果吧
具体实现
可以看到,今天的柱状图分为三类:双条竖向柱状图、单条竖向柱状图以及单条横向柱状图,其实原理都是一样的,下面我们具体看一下怎么实现,怎么去画一个这样的柱状图。
双条竖向
我们可以看到这个柱状图主要包括下面几个方面:
好了上面五点就是需求和UI所提出来的所有东西,我们开始着手去“画”吧。
1.首先我们定义一些资源style供使用
包括
底部和顶部颜色是用于渐变用的
<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.接下来我们看具体代码,注释写的很详细了,仔细看:
注意: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显示的各个部门的具体内容。
代码下载:demo
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍Android自定义View详解,包括了Android自定义View详解的使用技巧和注意事项,需要的朋友参考一下 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义Vi
本文向大家介绍Android自定义View构造函数详解,包括了Android自定义View构造函数详解的使用技巧和注意事项,需要的朋友参考一下 初始Custom View的构造函数 之前写过一篇实现圆形进度条的博客(自定义圆形进度条),通常我们在实现Custom View的时候,都会先继承View并实现View的三个构造函数,例如: 网上有很多关于三个构造函数使用时机的说法,但是说法正确的却没有几
本文向大家介绍Android 自定义View步骤,包括了Android 自定义View步骤的使用技巧和注意事项,需要的朋友参考一下 例子如下:Android 自定义View 密码框 例子 1 良好的自定义View 易用,标准,开放。 一个设计良好的自定义view和其他设计良好的类很像。封装了某个具有易用性接口的功能组合,这些功能能够有效地使用CPU和内存,并且十分开放的。但是,除了开始一个设计良好
本文向大家介绍Android中自定义一个View的方法详解,包括了Android中自定义一个View的方法详解的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Android中自定义一个View的方法。分享给大家供大家参考,具体如下: Android中自定义View的实现比较简单,无非就是继承父类,然后重载方法,即便如此,在实际编码中难免会遇到一些坑,我把自己遇到的一些问题和解决方法总结一下
本文向大家介绍Android自定义单例AlertDialog详解,包括了Android自定义单例AlertDialog详解的使用技巧和注意事项,需要的朋友参考一下 当Android开发处理错误信息时,经常会以Dialog的形式显示错误信息,但是每次都new一个Dialog,很麻烦,也增加程序的开销,所以今天就分享一种自定义单例AlertDialog 布局文件view_alertdialog.xml
本文向大家介绍Android自定义View圆形图片控件代码详解,包括了Android自定义View圆形图片控件代码详解的使用技巧和注意事项,需要的朋友参考一下 前言 在日常开发中,圆形的图片效果还是很常见的。可以通过给Paint设置Xfermode来实现,这里简单记录如下。 实现 实现圆形效果的核心是PorterDuffXfermode,对于PorterDuffXfermode,这里不展开,可以查