1、介绍

周末在逛慕课网的时候,看到了一张学习计划报告图,详细记录了自己一周的学习情况,天天都是0节课啊!正好在学习Android自定义View,于是就想着自己去写了一个,这里先给出一张慕课网的图,和自己的效果图。

yissan的博客,未经允许严禁转载 http://blog.csdn.net/yissan

2、实现分析

我们要实现这样一个折线统计图,必要的信息主要有下面几个

先看纵轴,纵轴需要的信息有最大值,还有用来确定每个间距代表的单位,比如最大值是100,我们还要有一个将值分为几份的数据。

接下来看横轴,因为横轴的信息一般是文字,不能像数字通过累加就可以得到,所以直接保存一个字符串数组变量。

然后就到了折线了,画折线只需要每个横轴单位的纵轴数据y坐标确定然后连接起来就ok了,这里只需要根据左边的单位的间距和每个单位的值就可以获取到y的具体坐标。

那么总结起来就需要:

1、纵轴最大值

2、纵轴分割数量

3、纵轴每个小单位的值 通过 最大值/分割数量计算

4、用来横轴显示的数组

5、横轴间距、纵轴间距

6、具体的数组(用来画折线)

有了上面的信息就可以去draw了,下面开始具体的自定义View步骤讲解

3、具体实现

在之前的文章,写过一篇介绍了自定义的步骤的文章——一起来学习Android自定义控件1,我们就按照这个步骤来讲解说明。

(1) 创建View

主要确定该继承View还是一些特定的View,定义和获取属性、添加设置属性方法。

定义属性

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="StatisticsView">
  4. <attr name="maxValue" format="integer"></attr>
  5. <attr name="dividerCount" format="integer"></attr>
  6. <attr name="title" format="integer"></attr>
  7. <attr name="lineColor" format="color"></attr>
  8. <attr name="textColor" format="color"></attr>
  9. <attr name="pathColor" format="color"></attr>
  10. </declare-styleable>
  11. </resources>

在构造方法中获取属性

  1. public class StatisticsView extends View {
  2. //画横纵轴
  3. private Paint mBorderPaint;
  4. //画坐标点的圆心
  5. private Paint circlePaint;
  6. //画折线图
  7. private Paint mPathPaint;
  8. private Path mPath;
  9. //纵轴最大值
  10. private int maxValue = 100;
  11. //纵轴分割数量
  12. private int dividerCount = 10;
  13. private String title = "七日学习情况(单位节)";
  14. //纵轴每个单位值
  15. private int perValue = maxValue/dividerCount;
  16. //底部显示String
  17. private String[] bottomStr = {};
  18. //具体的值
  19. private float[] values = {};
  20. //底部横轴单位间距
  21. private float bottomGap;
  22. //左边纵轴间距
  23. private float leftGap;
  24. private TextPaint textPaint;
  25. public void setValues(float[] values) {
  26. this.values = values;
  27. invalidate();
  28. }
  29. public void setBottomStr(String[] bottomStr) {
  30. this.bottomStr = bottomStr;
  31. requestLayout();
  32. }
  33. public StatisticsView(Context context) {
  34. super(context);
  35. }
  36. public StatisticsView(Context context, AttributeSet attrs) {
  37. this(context, attrs,0);
  38. }
  39. public StatisticsView(Context context, AttributeSet attrs, int defStyleAttr) {
  40. super(context, attrs, defStyleAttr);
  41. TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.StatisticsView);
  42. maxValue =array.getInt(R.styleable.StatisticsView_maxValue,100);
  43. dividerCount = array.getInt(R.styleable.StatisticsView_dividerCount,10);
  44. title = array.getString(R.styleable.StatisticsView_title);
  45. int lineColor = array.getColor(R.styleable.StatisticsView_lineColor,Color.BLACK);
  46. int textColor =array.getColor(R.styleable.StatisticsView_textColor,Color.BLACK);
  47. mBorderPaint = new Paint();
  48. circlePaint = new Paint();
  49. mPathPaint = new Paint();
  50. mBorderPaint.setAntiAlias(true);
  51. mBorderPaint.setColor(lineColor);
  52. mBorderPaint.setStrokeWidth(1);
  53. mBorderPaint.setStyle(Paint.Style.STROKE);
  54. mPathPaint.setAntiAlias(true);
  55. mPathPaint.setStyle(Paint.Style.STROKE);
  56. mPathPaint.setStrokeWidth(3);
  57. textPaint = new TextPaint();
  58. textPaint.setColor(textColor);
  59. textPaint.setTextSize(dip2px(getContext(),12));
  60. mPath = new Path();
  61. circlePaint.setStyle(Paint.Style.FILL);
  62. circlePaint.setAntiAlias(true);
  63. array.recycle();
  64. }

上面的代码简单的获取到了属性、初始化了一些信息。同时对外提供了设置values值的方法

(2)处理View的布局

处理布局首先考虑的是根据需要重写onMeasure方法。这里为了简单就直接让wrap_content的情况下直接宽高相等。当然你也可以有一个代表每个间距宽高的属性,然后去计算wrap_content下的宽高。

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  4. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  5. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  6. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  7. if (widthMode==MeasureSpec.EXACTLY&&heightMode==MeasureSpec.EXACTLY){
  8. setMeasuredDimension(widthSize,heightSize);
  9. }else if (widthMeasureSpec==MeasureSpec.EXACTLY){
  10. setMeasuredDimension(widthSize,widthSize);
  11. }else if (heightMeasureSpec==MeasureSpec.EXACTLY){
  12. setMeasuredDimension(heightSize,heightSize);
  13. }
  14. }

由于在draw的时候要确定横轴的单位间距,我们需要获取它,一般我们获取值可以在onSizeChange方法中获取,但是由于我们底部的gap需要根据要显示几个来确定。但是才开始的时候bottomStr[]的length为0,之后通过set方法为bottomStr设置不会再次调用onSizeChange。bottomGap就会是最开始的值,这样效果会出问题,所以就在onLayout方法中获取。


  1. @Override
  2. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  3. bottomGap = getWidth()/(bottomStr.length+1);
  4. leftGap = getHeight()/(dividerCount+2);
  5. super.onLayout(changed, left, top, right, bottom);
  6. }

(3)、绘制View(Draw)

接下来就可以实现onDraw()来绘制View了

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. if (bottomStr==null||bottomStr.length==0){
  5. return;
  6. }
  7. //画左边的线
  8. canvas.drawLine(bottomGap,getHeight()-leftGap,bottomGap,leftGap,mBorderPaint);
  9. float fontHeight =(textPaint.getFontMetrics().descent-textPaint.getFontMetrics().ascent);
  10. //画下边线
  11. canvas.drawLine(bottomGap,getHeight()-leftGap,getWidth()-bottomGap,getHeight()-leftGap,mBorderPaint);
  12. for (int i = 1;i<=bottomStr.length;i++){
  13. canvas.drawCircle(i*bottomGap,getHeight()-leftGap,6,circlePaint);
  14. canvas.drawText(bottomStr[i-1],i*bottomGap-(textPaint.measureText(bottomStr[i-1])/2),getHeight()-leftGap/2+fontHeight/2,textPaint);
  15. }
  16. canvas.drawText(title,bottomGap,leftGap/2,textPaint);
  17. for (int i = 1;i<=dividerCount+1;i++){
  18. //画左边的字
  19. canvas.drawText(perValue*(i-1)+"",bottomGap/2-(textPaint.measureText(perValue*(i-1)+"")/2),(((dividerCount+2-i)))*leftGap+fontHeight/2,textPaint);
  20. //画横线
  21. canvas.drawLine(bottomGap,getHeight()-((i)*leftGap),getWidth()-bottomGap,getHeight()-((i)*leftGap),mBorderPaint);
  22. }
  23. /**
  24. * 画轨迹
  25. * y的坐标点根据 y/leftGap = values[i]/perValue 计算
  26. *
  27. */
  28. for (int i = 0;i<values.length;i++){
  29. if (i==0){
  30. mPath.moveTo(bottomGap,(dividerCount+1)*leftGap-(values[i]*leftGap/perValue));
  31. }else{
  32. mPath.lineTo((i+1)*bottomGap,(dividerCount+1)*leftGap-(values[i]*leftGap/perValue));
  33. }
  34. /**
  35. * 画轨迹圆点
  36. */
  37. canvas.drawCircle((i+1)*bottomGap,(dividerCount+1)*leftGap-(values[i]*leftGap/perValue),6,circlePaint);
  38. }
  39. canvas.drawPath(mPath,mPathPaint);
  40. }
  41. public static int dip2px(Context context, float dpValue) {
  42. final float scale = context.getResources().getDisplayMetrics().density;
  43. return (int) (dpValue * scale + 0.5f);
  44. }

代码都加了注释,主要是一些计算,还有drawLine,drawPath,drawText,以及获取text宽高的一些知识。

yissan的博客,未经允许严禁转载 http://blog.csdn.net/yissan

4、使用

声明View,然后在Activity里获取View并且调用setBottomStr和setValues方法

  1. <com.qiangyu.test.statisticsview.view.StatisticsView
  2. android:id="@+id/statisticsView"
  3. android:layout_width="match_parent"
  4. android:layout_height="300dp"
  5. app:viewTitle="七日学习情况(单位 节)"/>
  1. public void invalidate(View view) {
  2. this.view.setBottomStr(new String[]{"星期一","星期二","星期三","星期四","星期五","星期六","星期天"});
  3. this.view.setValues(new float[]{10f,90f,33f,66f,42f,99f,0f});
  4. }

再来一张效果图

5、总结

自定义View就是多练,看到一个喜欢的效果,想不想能不能自己的画一个,时间久了,相信我们都可以轻松的写出很好的自定义View

因为最近工作有点忙,所以很多地方不完善。在这里分享一下,希望大家喜欢。

Android自定义View4——统计图View的更多相关文章

  1. Android 自定义View及其在布局文件中的使用示例

    前言: 尽管Android已经为我们提供了一套丰富的控件,如:Button,ImageView,TextView,EditText等众多控件,但是,有时候在项目开发过程中,还是需要开发者自定义一些需要 ...

  2. Android自定义View 画弧形,文字,并增加动画效果

    一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类   B ...

  3. (转)[原] Android 自定义View 密码框 例子

    遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...

  4. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  5. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  6. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  7. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

  8. Android 自定义view(二) —— attr 使用

    前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...

  9. Android 自定义View

    Android 自定义View流程中的几个方法解析: onFinishInflate():从布局文件.xml加载完组件后回调 onMeasure() :调用该方法负责测量组件大小 onSizeChan ...

随机推荐

  1. Android初级教程_获取Android控件的宽和高

    转载:http://blog.csdn.net/johnny901114/article/details/7839512 我们都知道在onCreate()里面获取控件的高度是0,这是为什么呢?我们来看 ...

  2. n个结点,不同形态的二叉树(数目+生成)

    题目链接: 不同的二叉查找树:http://www.lintcode.com/zh-cn/problem/unique-binary-search-trees/ 不同的二叉查找树 II:http:// ...

  3. Dapper简明教程

    Dapper是一款轻量级的ORM框架,有关Dapper优缺点的文章网上一大堆,这里小编就不再赘述啦.下面直接进入正题: 使用前准备 添加对Dapper的引用 在使用Dapper之前,我们要首先添加对D ...

  4. redux+flux(一:入门篇)

    React是facebook推出的js框架,React 本身只涉及UI层,如果搭建大型应用,必须搭配一个前端框架.也就是说,你至少要学两样东西,才能基本满足需要:React + 前端框架. Faceb ...

  5. MS SQL 字符拆分存处理

    MS SQL Server没有split()函数,但是我们可以写一个Table-valued Functions定义函数[dbo].[udf_SplitStringToTable] : CREATE ...

  6. 基于CkEditor实现.net在线开发之路(5)列表页面开发

    这章主要讲解利用控件开发列表页面,我们先从最简单的列表页面开始讲解,这个列表只有一个列表展示.具体开发步骤请看下面动态图 由上动态图可以看出,开发一个简单的列表只有两步, 第一步:拖拽查询控件,设置好 ...

  7. ComponentOne 2016 V3 发布

    ComponentOne Studio Enterprise 2016 V3 新特性 我们很高兴的宣布ComponentOne 2016 V3发布了!2016 Connect开发者大会上微软发布了Vi ...

  8. 转载:《TypeScript 中文入门教程》 13、类型兼容性

    版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 TypeScript里的类型兼容性基于结构子类型的. 结构类型是只一种只使用其成 ...

  9. 51nod 算法马拉松18 B 非010串 矩阵快速幂

    非010串 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 如果一个01字符串满足不存在010这样的子串,那么称它为非010串. 求长度为n的非010串的个数.(对1e9+7取模) ...

  10. 【原创-算法-实现】异步HTTP请求操作

    一.说明 1) 这个类 是我 在真实项目中,优化解决真实问题 时,不参考第三方代码,完全由自己查阅MSDN官方文档 , 完成的一个真实生产环境中使用的功能类 2) 读者在使用此类时,请尊重原创,在代码 ...