Android自定义View实现折线图效果

下面就是结果图(每种状态用一个表情图片表示):

一、主页面的布局文件如下:

<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"
 tools:context=".MainActivity"
 xmlns:app="http://schemas.android.com/apk/res/ting.example.linecharview">
 <ting.example.linecharview.LineCharView
 android:id="@+id/test"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 app:xytextcolor="@color/bg"
 app:xytextsize="20sp"
 app:interval="80dp"
 />
</RelativeLayout> 

其中linecharview就是自定义的View,而app:xx就是这个View的各种属性。

二、在values的attrs文件中加入如下xml,来定义linecharview的各种属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="LineChar">
 <attr name="xylinecolor" format="color"/><!-- xy坐标轴颜色 -->
 <attr name="xylinewidth" format="dimension"/><!-- xy坐标轴宽度 -->
 <attr name="xytextcolor" format="color"/><!-- xy坐标轴文字颜色 -->
 <attr name="xytextsize" format="dimension"/><!-- xy坐标轴文字大小 -->
 <attr name="linecolor" format="color"/><!-- 折线图中折线的颜色 -->
 <attr name="interval" format="dimension"/><!-- x轴各个坐标点水平间距 -->
 <attr name="bgcolor" format="color"/><!-- 背景颜色 -->
 </declare-styleable>
</resources> 

三、接下来建个类LineCharView 继承View,并申明如下变量:

<span style="white-space:pre"> </span>private int xori;//圆点x坐标
 private int yori;//圆点y坐标
 private int xinit;//第一个点x坐标
 private int minXinit;//在移动时,第一个点允许最小的x坐标
 private int maxXinit;//在移动时,第一个点允许允许最大的x坐标
 private int xylinecolor;//xy坐标轴颜色
 private int xylinewidth;//xy坐标轴大小
 private int xytextcolor;//xy坐标轴文字颜色
 private int xytextsize;//xy坐标轴文字大小
 private int linecolor;//折线的颜色
 private int interval;//坐标间的间隔
 private int bgColor;//背景颜色
 private List<String> x_coords;//x坐标点的值
 private List<String> x_coord_values;//每个点状态值 

 private int width;//控件宽度
 private int heigth;//控件高度
 private int imageWidth;//表情的宽度
 private float textwidth;//y轴文字的宽度
 float startX=0;//滑动时候,上一次手指的x坐标 

在构造函数中读取各个属性值:

public LineCharView(Context context, AttributeSet attrs) {
 super(context, attrs);
 TypedArray typedArray= context.obtainStyledAttributes(attrs, R.styleable.LineChar);
 xylinecolor=typedArray.getColor(R.styleable.LineChar_xylinecolor, Color.GRAY);
 xylinewidth=typedArray.getInt(R.styleable.LineChar_xylinewidth, 5);
 xytextcolor=typedArray.getColor(R.styleable.LineChar_xytextcolor, Color.BLACK);
 xytextsize=typedArray.getLayoutDimension(R.styleable.LineChar_xytextsize, 20);
 linecolor=typedArray.getColor(R.styleable.LineChar_linecolor, Color.GRAY);
 interval=typedArray.getLayoutDimension(R.styleable.LineChar_interval, 100);
 bgColor=typedArray.getColor(R.styleable.LineChar_bgcolor, Color.WHITE);
 typedArray.recycle();
 x_coords=new ArrayList<String>();
 x_coord_values=new ArrayList<String>();
} 

四、接下来可以重写onLayout方法,来计算控件宽高和坐标轴的原点坐标,坐标轴原点的x坐标可以通过y轴文字的宽度,y轴宽度,和距离y的水平距离进行计算,这里y轴文字只有4种状态(A、B、C、D),可以使用下面方法来计算出原点的x坐标:

Paint paint=new Paint();
paint.setTextSize(xytextsize);
textwidth= paint.measureText("A");
xori=(int) (textwidth+6+2*xylinewidth);//6 为与y轴的间隔 

原点的y坐标也可以用类似的方法计算出来:

yori=heigth-xytextsize-2*xylinewidth-3; //3为x轴的间隔,heigth为控件高度。 

当需要展示的数据量多时候,无法全部展示时候,需要通过滑动折线图进行展示,我们只需要控制第一点x坐标,就可以通过interval这个属性计算出后面每个点的坐标,但是为了防止将所有的数据滑动出界面外,需要在滑动时进行控制,其实就是控制第一个点x坐标的范围,第一个点的x坐标的最小值可以通过控件的宽度减去原点x坐标再减去所有折线图的水平距离,代码如下:

minXinit=width-xori-x_coords.size()*interval; 

控件在默认第一个展示时,第一个点与y轴的水平距离等于interval的一半,在滑动时候如果第一个点出现在这个位置了,就不允许再继续向右滑动,所以第一个点x坐标的最大值就等这个起始x坐标。

xinit=interval/2+xori;
maxXinit=xinit; 

重写onLayout方法的代码如下:

@Override
 protected void onLayout(boolean changed, int left, int top, int right,
 int bottom) {
 if(changed){
 width=getWidth();
 heigth=getHeight();
 Paint paint=new Paint();
 paint.setTextSize(xytextsize);
 textwidth= paint.measureText("A");
 xori=(int) (textwidth+6+2*xylinewidth);//6 为与y轴的间隔
 yori=heigth-xytextsize-2*xylinewidth-3;//3为x轴的间隔
 xinit=interval/2+xori;
 imageWidth= BitmapFactory.decodeResource(getResources(), R.drawable.facea).getWidth();
 minXinit=width-xori-x_coords.size()*interval;
 maxXinit=xinit;
 setBackgroundColor(bgColor);
 }
 super.onLayout(changed, left, top, right, bottom);
 } 

五、接下来就可以画折线、x坐标轴上的小圆点和折线上表情

代码如下:

//画X轴坐标点,折线,表情
 @SuppressLint("ResourceAsColor")
 private void drawX (Canvas canvas) {
 Paint x_coordPaint =new Paint();
 x_coordPaint.setTextSize(xytextsize);
 x_coordPaint.setStyle(Paint.Style.FILL);
 Path path=new Path();
 //画坐标轴上小原点,坐标轴文字
 for(int i=0;i<x_coords.size();i++){
 int x=i*interval+xinit;
 if(i==0){
 path.moveTo(x, getYValue(x_coord_values.get(i)));
 }else{
 path.lineTo(x, getYValue(x_coord_values.get(i)));
 }
 x_coordPaint.setColor(xylinecolor);
 canvas.drawCircle(x, yori, xylinewidth*2, x_coordPaint);
 String text=x_coords.get(i);
 x_coordPaint.setColor(xytextcolor);
 canvas.drawText(text, x-x_coordPaint.measureText(text)/2, yori+xytextsize+xylinewidth*2, x_coordPaint);
 } 

 x_coordPaint.setStyle(Paint.Style.STROKE);
 x_coordPaint.setStrokeWidth(xylinewidth);
 x_coordPaint.setColor(linecolor);
 //画折线
 canvas.drawPath(path, x_coordPaint); 

 //画表情
 for(int i=0;i<x_coords.size();i++){
 int x=i*interval+xinit;
 canvas.drawBitmap(getYBitmap(x_coord_values.get(i)), x-imageWidth/2, getYValue(x_coord_values.get(i))-imageWidth/2, x_coordPaint);
 } 

 //将折线超出x轴坐标的部分截取掉
 x_coordPaint.setStyle(Paint.Style.FILL);
 x_coordPaint.setColor(bgColor);
 x_coordPaint.setXfermode(new PorterDuffXfermode( PorterDuff.Mode.SRC_OVER));
 RectF rectF=new RectF(0, 0, xori, heigth);
 canvas.drawRect(rectF, x_coordPaint); 

 } 

以上代码首先通过遍历x_coordsx_coord_values这两个List集合,来画坐标点,折线,表情,由于在向左滑动的时候有可能会将坐标点,折线绘制到y轴的左边,所以需要对其进行截取。其中getYValue和getYBitmap方法,可以通过x_coord_values的值计算y坐标和相应的表情。两方法如:

//得到y坐标
 private float getYValue(String value)
 {
 if(value.equalsIgnoreCase("A")){
 return yori-interval/2;
 }
 else if(value.equalsIgnoreCase("B")){
 return yori-interval;
 }
 else if(value.equalsIgnoreCase("C")){
 return (float) (yori-interval*1.5);
 }
 else if(value.equalsIgnoreCase("D")){
 return yori-interval*2;
 }else{
 return yori;
 }
 } 

 //得到表情图
 private Bitmap getYBitmap(String value){
 Bitmap bitmap=null;
 if(value.equalsIgnoreCase("A")){
 bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.facea);
 }
 else if(value.equalsIgnoreCase("B")){
 bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.faceb);
 }
 else if(value.equalsIgnoreCase("C")){
 bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.facec);
 }
 else if(value.equalsIgnoreCase("D")){
 bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.faced);
 }
 return bitmap;
 } 

六、画好了坐标点,折线,表情,接下来就简单,就可以画x y轴了,x y轴只要确定的原点坐标,就非常简单了,代码如下:

//画坐标轴
private void drawXY(Canvas canvas){
 Paint paint=new Paint();
 paint.setColor(xylinecolor);
 paint.setStrokeWidth(xylinewidth);
 canvas.drawLine(xori, 0, xori, yori, paint);
 canvas.drawLine(xori, yori, width, yori, paint);
} 

七、最后就可以画y轴上的坐标小原点和y轴的文字了:

//画Y轴坐标点
 private void drawY(Canvas canvas){
 Paint paint=new Paint();
 paint.setColor(xylinecolor);
 paint.setStyle(Paint.Style.FILL);
 for(int i=1;i<5 ;i++){
 canvas.drawCircle(xori, yori-(i*interval/2), xylinewidth*2, paint);
 } 

 paint.setTextSize(xytextsize);
 paint.setColor(xytextcolor);
 canvas.drawText("D",xori-textwidth-6-xylinewidth , yori-(2*interval)+xytextsize/2, paint);
 canvas.drawText("C",xori-textwidth-6-xylinewidth , (float) (yori-(1.5*interval)+xytextsize/2), paint);
 canvas.drawText("B",xori-textwidth-6-xylinewidth , yori-interval+xytextsize/2, paint);
 canvas.drawText("A",xori-textwidth-6-xylinewidth , (float) (yori-(0.5*interval)+xytextsize/2), paint);
 } 

八、写完了以上三个方法:只需要重写onDraw方法,就可以进行绘制了。

@Override
 protected void onDraw(Canvas canvas) {
 drawX(canvas);
 drawXY(canvas);
 drawY(canvas);
 } 

九、为了可以进行水平滑动,需要重写控件的onTouchEvent方法,在滑动时候,实时计算手指滑动的距离来改变第一个点的x坐标,然后调用invalidate();就可以刷新控件,重新绘制达到滑动效果。

@Override
 public boolean onTouchEvent(MotionEvent event) { 

 //如果不用滑动就可以展示所有数据,就不让滑动
 if(interval*x_coord_values.size()<=width-xori){
 return false;
 } 

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN:
 startX=event.getX();
 break; 

 case MotionEvent.ACTION_MOVE:
 float dis=event.getX()-startX;
 startX=event.getX();
 if(xinit+dis>maxXinit){
 xinit=maxXinit;
 }else if(xinit+dis<minXinit){
 xinit=minXinit;
 }else{
 xinit=(int) (xinit+dis);
 }
 invalidate(); 

 break;
 }
 return true;
 } 

十、最后添加一个设置数据源的方法,设置x_coordsx_coord_values这个两个List集合,在设置完成之后调用invalidate() ,进行控件刷新:

/**
 * 设置坐标折线图值
 * @param x_coords 横坐标坐标点
 * @param x_coord_values 每个点的值
 */
public void setValue( List<String> x_coords ,List<String> x_coord_values) {
 if(x_coord_values.size()!=x_coords.size()){
 throw new IllegalArgumentException("坐标轴点和坐标轴点的值的个数必须一样!");
 }
 this.x_coord_values=x_coord_values;
 this.x_coords=x_coords;
 invalidate();
} 

总结

以上就是Android自定义View实现折线图效果的全部内容,希望对大家开发Android能有所帮助。

(0)

相关推荐

  • Android实现上拉加载更多以及下拉刷新功能(ListView)

    首先为大家介绍Andorid5.0原生下拉刷新简单实现. 先上效果图: 相对于上一个19.1.0版本中的横条效果好看了很多.使用起来也很简单. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" and

  • Android开发使用自定义view实现ListView下拉的视差特效功能

    本文实例讲述了Android开发使用自定义view实现ListView下拉的视差特效功能.分享给大家供大家参考,具体如下: 一.概述: 现在流型的APP如微信朋友圈,QQ空间,微博个人展示都有视差特效的影子. 如图:下拉图片会产生图片拉升的效果,放手后图片有弹回到原处: 那我们如何实现呢? 1)重写ListView控件: 2)重写里面的overScrollBy方法 3)在松手后执行值动画 二.具体实现: 1.创建ParallaListView 自定义ListView public class P

  • Android自定义view实现阻尼效果的加载动画

    效果: 需要知识: 1. 二次贝塞尔曲线 2. 动画知识 3. 基础自定义view知识 先来解释下什么叫阻尼运动 阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减的振动,又称减幅振动.衰减振动.[1] 不论是弹簧振子还是单摆由于外界的摩擦和介质阻力总是存在,在振动过程中要不断克服外界阻力做功,消耗能量,振幅就会逐渐减小,经过一段时间,振动就会完全停下来.这种振幅随时间减小的振动称为阻尼振动.因为振幅与振动的能量有关,阻尼振动也就是能量不断减少的振动.阻尼振动是非简谐运

  • Android自定义View之继承TextView绘制背景

    本文实例为大家分享了TextView绘制背景的方法,供大家参考,具体内容如下 效果: 实现流程: 1.初始化:对画笔进行设置 mPaintIn = new Paint(); mPaintIn.setAntiAlias(true); mPaintIn.setDither(true); mPaintIn.setStyle(Paint.Style.FILL); mPaintIn.setColor(getResources().getColor(R.color.colorPrimary)); mPain

  • Android自定义View实现竖直跑马灯效果案例解析

    首先给出跑马灯效果图 中间的色块是因为视频转成GIF造成的失真,自动忽略哈. 大家知道,横向的跑马灯android自带的TextView就可以实现,详情请百度[Android跑马灯效果].但是竖直的跑马灯效果原生Android是不支持的.网上也有很多网友实现了自定义的效果,但是我一贯是不喜欢看别人的代码,所以这篇博客的思路完全是我自己的想法哈. 首先,我们需要给自定义的控件梳理一下格局,如下图所示: 1.首先我们将控件分为三个区块,上面绿色部分为消失不可见的块,中间黑色部分为可见区域,下面红色部

  • 实例讲解Android中的View类以及自定义View控件的方法

    View的简单理解和实例 1.View的基本概念 在Activity显示的控件 都叫做View(View类 是所有的控件类的父类  比如 文本 按钮) 2.在Activity当中获取代表View的对象 Activity读取布局文件生成相对应的 各种View对象 TextView textView=(TextView)findViewBy(R.id.textView) 3.设置view的属性 Activity_mian.xml 这样的xml布局文件中发现了,类似@+id/和@id/到底有什么区别呢

  • Android自定义View实现广告信息上下滚动效果

    先看看效果: 实现代码: public class ScrollBanner extends LinearLayout { private TextView mBannerTV1; private TextView mBannerTV2; private Handler handler; private boolean isShow; private int startY1, endY1, startY2, endY2; private Runnable runnable; private Li

  • Android自定义View实现带数字的进度条实例代码

    第一步.效果展示 图1.蓝色的进度条 图2.红色的进度条 图3.多条颜色不同的进度条 图4.多条颜色不同的进度条 第二步.自定义ProgressBar实现带数字的进度条 0.项目结构 如上图所示:library项目为自定义的带数字的进度条NumberProgressBar的具体实现,demo项目为示例项目以工程依赖的方式引用library项目,然后使用自定义的带数字的进度条NumberProgressBar来做展示 如上图所示:自定义的带数字的进度条的library项目的结构图 如上图所示:de

  • android开发教程之实现listview下拉刷新和上拉刷新效果

    复制代码 代码如下: public class PullToLoadListView extends ListView implements OnScrollListener { private static final String TAG = PullToLoadListView.class.getSimpleName(); private static final int STATE_NON = 0; private static final int STATE_PULL_TO_REFRE

  • Android自定义view制作绚丽的验证码

    废话不多说了,先给大家展示下自定义view效果图,如果大家觉得还不错的话,请继续往下阅读. 怎么样,这种验证码是不是很常见呢,下面我们就自己动手实现这种效果,自己动手,丰衣足食,哈哈~ 一. 自定义view的步骤 自定义view一直被认为android进阶通向高手的必经之路,其实自定义view好简单,自定义view真正难的是如何绘制出高难度的图形,这需要有好的数学功底(后悔没有好好学数学了~),因为绘制图形经常要计算坐标点及类似的几何变换等等.自定义view通常只需要以下几个步骤: 写一个类继承

  • Android 自定义View的使用介绍

    在项目开发中,可能系统自带的一些widget不能满足我们的需求,这时就需要自定义View. 通过查看系统中的常用widget如Button,TextView,EditText,他们都继承自View,所以我们在继承自定义View的时候也自然的需要继承View.1.首先新建一个类LView继承自View 复制代码 代码如下: public class LView extends View { private Paint paint; public LView(Context context) {  

随机推荐