转载请注明出处:http://write.blog.csdn.net/postedit/50434634

接上篇 Android 圆形百分比(进度条) 自定义view

昨天分手了,不开心,来练练自定义view麻痹自己,毕竟菜鸟只能靠不断练习提高。#程序员不应该有女朋友#

我们要实现的是一种只有来看趋势,不需要看具体数值,比较简约的折线图。比如下图这样的:

这个时候,一些比较优秀的第三方图表库如:MPChart 就显得比较臃肿了。所以我们需要自定义一个折线图。

老规矩,先来看最终的实现效果:

其实这种做的很简约,大概分三个步骤:

一、画坐标轴

二、画点

三、画线

那么我们开始吧Let's go (Let it go)。

设计一下大概需要的东西。首先把X轴和Y轴的数据存放在两个String[]里。

具体的点的位置用一个Map<Integer,Integer>来存放.

步骤:

一、新建一个类,取名为SimpleLineChart继承View 重写他的构造方法。这里为了简便,就不添加自定义属性了attr.xml。

<span style="font-size:18px;">   </span><span style="font-size:12px;"> public SimpleLineChart(Context context) {
this(context, null);
} public SimpleLineChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public SimpleLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}</span>

二、测量大小。(继续偷懒,只支持EXACTLY,AT_MOST直接丢异常)

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;
}else if(widthMode == MeasureSpec.AT_MOST){
throw new IllegalArgumentException("width must be EXACTLY,you should set like android:width=\"200dp\"");
} if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
}else if(widthMeasureSpec == MeasureSpec.AT_MOST){ throw new IllegalArgumentException("height must be EXACTLY,you should set like android:height=\"200dp\"");
} setMeasuredDimension(mWidth, mHeight);
}

三、重点来了,开始draw。首先处理几种异常情况。当X轴或者Y轴为没有文字(也就是没有刻度的时候),抛出异常。

if(mXAxis.length==0||mYAxis.length==0){
throw new IllegalArgumentException("X or Y items is null");
}

当没有任何点的数据的时候,显示字符串提醒用户没有数据(实际上是往中心drawText)。

 //画坐标线的轴
        Paint axisPaint = new Paint();
        axisPaint.setTextSize(mYAxisFontSize);
        axisPaint.setColor(Color.parseColor("#3F51B5"));
if (mPointMap == null || mPointMap.size() == 0) {
int textLength = (int) axisPaint.measureText(mNoDataMsg);
canvas.drawText(mNoDataMsg, mWidth/2 - textLength/2, mHeight/2, axisPaint);
}

异常情况处理完了,开始上面三个步骤挨个画,首先来画个Y轴,计算每个刻度的间隔。他的值应该是mWidth / Y轴文字个数,然后用循环把每个刻度都画出来。再申请一个数组yPoints,存放每个Y刻度的具体坐标。

  //画 Y 轴

            //存放每个Y轴的坐标
int[] yPoints = new int[mYAxis.length]; //计算Y轴 每个刻度的间距
int yInterval = (int) ((mHeight - mYAxisFontSize - 2) / (mYAxis.length)); //测量Y轴文字的高度 用来画第一个数
Paint.FontMetrics fm = axisPaint.getFontMetrics();
int yItemHeight = (int) Math.ceil(fm.descent - fm.ascent); Log.e("wing", mHeight + "");
for (int i = 0; i < mYAxis.length; i++) {
canvas.drawText(mYAxis[i], 0, mYAxisFontSize + i * yInterval, axisPaint);
yPoints[i] = (int) (mYAxisFontSize + i * yInterval); }

我们运行一下,看到了如下效果:

需要注意的是,这里的坐标需要微调,大家多试一下。同理开始画X轴:

      //画 X 轴

            //x轴的刻度集合
int[] xPoints = new int[mXAxis.length]; Log.e("wing", xPoints.length + "");
//计算Y轴开始的原点坐标
int xItemX = (int) axisPaint.measureText(mYAxis[1]); //X轴偏移量
int xOffset = 50;
//计算x轴 刻度间距
int xInterval = (int) ((mWidth - xOffset) / (mXAxis.length));
//获取X轴刻度Y坐标
int xItemY = (int) (mYAxisFontSize + mYAxis.length * yInterval); for (int i = 0; i < mXAxis.length; i++) {
canvas.drawText(mXAxis[i], i * xInterval + xItemX + xOffset, xItemY, axisPaint);
xPoints[i] = (int) (i * xInterval + xItemX + axisPaint.measureText(mXAxis[i]) / 2 + xOffset + 10);
// Log.e("wing", xPoints[i] + "");
}

注意这里X轴的y坐标就是画Y轴时候的最下面的文字(最后一个)的坐标,存成了xItemY。

之后我们来画点,这里我采用的方法是画圆。直接drawCircle。从map中取出所有点的对应i,j然后再从两个数组 xPoints[] yPoints[]取出真实的X,Y坐标,最后画出来

        //画点
Paint pointPaint = new Paint(); pointPaint.setColor(mLineColor); Paint linePaint = new Paint(); linePaint.setColor(mLineColor);
linePaint.setAntiAlias(true);
//设置线条宽度
linePaint.setStrokeWidth(mStrokeWidth);
pointPaint.setStyle(Paint.Style.FILL); for (int i = 0; i < mXAxis.length; i++) {
if (mPointMap.get(i) == null) {
throw new IllegalArgumentException("PointMap has incomplete data!");
} //画点
canvas.drawCircle(xPoints[i], yPoints[mPointMap.get(i)], mPointRadius, pointPaint);
if (i > 0) {//画线
canvas.drawLine(xPoints[i - 1], yPoints[mPointMap.get(i - 1)], xPoints[i], yPoints[mPointMap.get(i)], linePaint);
}
}

上面画完点之后开始画线drawLine,参数是前一个点的坐标,和后一个点的坐标。挨个画出来。

这时候我们的最复杂的绘制就完成了。接下来来加入一点功能:参数的设置。

 /**
* 设置map数据
* @param data
*/
public void setData(HashMap<Integer,Integer> data){
mPointMap = data;
invalidate();
} /**
* 设置Y轴文字
* @param yItem
*/
public void setYItem(String[] yItem){
mYAxis = yItem;
} /**
* 设置X轴文字
* @param xItem
*/
public void setXItem(String[] xItem){
mXAxis = xItem;
} public void setLineColor(int color){
mLineColor = color;
invalidate();
}

以上代码很简单 我就不多说了。整个View完工,接下来介绍如何使用。

使用:

和普通的View一样,我们直接在XML布局文件中加入SimpleLineChart,注意不要忘记包名。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.wingsofts.simplelinechart.MainActivity"> <com.wingsofts.simplelinechart.SimpleLineChart
android:id="@+id/simpleLineChart"
android:layout_width="400dp"
android:layout_height="200dp" />
</RelativeLayout>

然后在Activity中findviewbyid,给他设置X轴的文字 Y轴的文字 还有数据源

public class MainActivity extends AppCompatActivity {
private SimpleLineChart mSimpleLineChart;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSimpleLineChart = (SimpleLineChart) findViewById(R.id.simpleLineChart);
String[] xItem = {"1","2","3","4","5","6","7"};
String[] yItem = {"10k","20k","30k","40k","50k"};
if(mSimpleLineChart == null)
Log.e("wing","null!!!!");
mSimpleLineChart.setXItem(xItem);
mSimpleLineChart.setYItem(yItem);
HashMap<Integer,Integer> pointMap = new HashMap();
for(int i = 0;i<xItem.length;i++){
pointMap.put(i, (int) (Math.random()*5));
}
mSimpleLineChart.setData(pointMap);
}
}

简单的几步,就可以得到预览图的效果了!是不是很好玩!觉得好的话评论一下,star一下。祭奠我死去的爱情。

项目下载地址(求关注 求星星 ):点击打开链接

下一篇来一个比较炫 比较复杂的view 自定义仪表盘 :时尚自定义仪表盘

Android 自定义View -- 简约的折线图的更多相关文章

  1. Android自定义View——多边形网格属性图

      1.初始化变量 2.属性图解   3.如果想切换到5.6.7边形等等,则必须修改下面几条数据 4.获取宽和高 5.绘制图形 1.开始画画前:我们要把画笔准备好,这里看代码就能明白意思了,接着把整个 ...

  2. 简单说说Android自定义view学习推荐的方式

    这几天比较受关注,挺开心的,嘿嘿. 这里给大家总结一下学习自定义view的一些技巧.  以后写自定义view可能不会写博客了,但是可以开源的我会把源码丢到github上我的地址:https://git ...

  3. android自定义View绘制天气温度曲线

    原文:android自定义View绘制天气温度曲线 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u012942410/article/detail ...

  4. Android 自定义View合集

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

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

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

  6. Android自定义View研究--View中的原点坐标和XML中布局自定义View时View触摸原点问题

    这里只做个汇总~.~独一无二 文章出处:http://blog.csdn.net/djy1992/article/details/9715047 Android自定义View研究--View中的原点坐 ...

  7. Android 自定义 View 圆形进度条总结

    Android 自定义圆形进度条总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 微信公众号:牙锅子 源码:CircleProgress 文中如有纰漏,欢迎大家留言指出. 最近 ...

  8. Android自定义View(CustomCalendar-定制日历控件)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeas ...

  9. Android自定义View(RollWeekView-炫酷的星期日期选择控件)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义Cus ...

随机推荐

  1. 阿里巴巴Java开发规约插件

    就在今天 10月14日上午9:00 阿里巴巴于在杭州云栖大会<研发效能峰会>上,正式发布<阿里巴巴Java开发手册>扫描插件,该插件在扫描代码后,将不符合<手册>的 ...

  2. OC基础之可循环滚动并突出中间图片,并且可点击

    前两天一哥们儿让我帮他写一下:可循环滚动并突出中间图片,并且可点击的一种滑动视图的效果,今天放在这里给大家展示一下,具体文字代码中都有注解,代码还有待完善,不喜勿喷,转载请注明,下载请点星,谢谢~ - ...

  3. Android开发技巧——设置系统状态栏颜色

    开门见山,先来三张效果图: 然后我们再来讲如何实现以及如何快速地实现. 如何实现 实现设置系统状态栏颜色需要至少在Android 4.4.2(API 19)以上.这是因为,在这个版本以下,没有任何的A ...

  4. Android给控件添加触摸回调

    Android给控件添加触摸回调 脑补一个场景,一个页面点击某个按钮会弹出PopupWindow,然后点击PopupWindow以外的任意位置关闭 效果图 实现方法 可以在布局的最外层容器监听触摸事件 ...

  5. Hadoop的RPC通信原理

    RPC调用: RPC(remote procedure call)远程过程调用: 不同java进程间的对象方法的调用. 一方称作服务端(server),一方称为客户端(client): server端 ...

  6. 初识Java多线程编程

    Java 多线程编程 Java给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 多线程是多任务的一种特别 ...

  7. 最优化方法:范数和规则化regularization

    http://blog.csdn.net/pipisorry/article/details/52108040 范数规则化 机器学习中出现的非常频繁的问题有:过拟合与规则化.先简单的来理解下常用的L0 ...

  8. Android4.3 屏蔽HOME按键返回桌面详解(源码环境下)

    点击打开链接 首先声明我是做系统开发的(高通平台),所以下面介绍的方法并不适合应用开发者. 最经有个需求要屏蔽HOME按键返回桌面并且实现自己的功能,发现以前的方式报错用不了,上网搜索了一下,发现都是 ...

  9. [ExtJS5学习笔记]第二十五节 利用window.open()函数实现ExtJS5的登陆页面跳转

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/40427543 mvvm方式实现登陆的博客:http://blog.csdn.net/s ...

  10. java虚拟机 jvm java堆 方法区 java栈

    java堆是java应用程序最密切的内存空间.几乎所有的对象都存在堆中.java堆完全自动化管理,通过垃圾回收机制,垃圾对象会自动清理,不需要显式释放. 根据java垃圾回收机制的不同,java堆可能 ...