详解android绘制动画效果的曲线图的实现

iwfb6234 8年前

 安卓绘制统计图可以用androidchart,也可以自己绘制,不像iosandroid能找到的开源库在UI方面都很差,要做出吸引人地方还是需要自己绘制。

本文给出最常用的曲线图的绘制方法。

绘制曲线图首先需要画好横竖坐标轴建立坐标系,比如坐标系中的100距离应该在canvas中绘制多长,这个是需要计算的,其实坐标体系的建立是最复杂的,我看过很多第三方库的建立方法都不一样,有的要灵活一些,有的比较死板。至于绘制曲线要么是用Canvas.drawLine方法,要么是用Path.lineTo方法,看你自己的习惯。

 

为了做出一个外观良好的曲线图,我参考了两个开源代码,第一个的曲线图绘制限制较多,使用范围太窄,但是有数据变化时的动画效果。第二个的适用范围很广,他能根据数据集合自动计算横纵坐标的个数,在canvas上单元格的距离,只需输入坐标点就能自动建立坐标体系绘制曲线,但是没有动画效果。

先讲第一个LineView

 

LineViewdemo可以在这里下载,lineview其实只是github项目的一部分,我是将其提取出来了的,个人觉得他的其他部分没有参考价值。作者好像是个韩国人。

 

LineView的曲线绘制没有什么可取的部分,我想学习的是他实现动画效果的方法,设计的很好,但具体实现还需要改进,让动画更流畅。

 

Lineview的调用方法:

xml中添加lineview控件

<HorizontalScrollView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:id="@+id/horizontalScrollView"

android:layout_alignParentRight="true"

android:layout_above="@+id/line_button">

<view

android:layout_width="wrap_content"

android:layout_height="200dp"

class="com.example.widget.LineView"

android:id="@+id/line_view"/>

</HorizontalScrollView>

activity代码中获取lineview对象:

finalLineView lineView = (LineView)findViewById(R.id.line_view);

添加横坐标:

int randomint = 9;

ArrayList<String>test =newArrayList<String>();

for (int i=0;i<randomint; i++){

test.add(String.valueOf(i+1));

}

lineView.setBottomTextList(test);

允许绘制坐标点:

lineView.setDrawDotLine(true);

lineView.setShowPopup(LineView.SHOW_POPUPS_NONE);

ArrayList<Integer> dataList = newArrayList<Integer>();

intrandom = (int)(Math.random()*9+1);

for (int i=0;i<randomint; i++){

dataList.add((int)(Math.random()*random));

}

添加纵坐标的值:

ArrayList<ArrayList<Integer>>dataLists = newArrayList<ArrayList<Integer>>();

dataLists.add(dataList);

lineView.setDataList(dataLists);

 

从其用法中可以看出,lineview需要提前设定横坐标的范围,而且纵坐标的值必须和lineView.setBottomTextList(test)中添加的值一一对应(读lineview源码可以知道),使用起来很不方便,我觉得作者仅仅是做出了一条曲线而已,而不太关注是否有用。和很多曲线图的开源代码一样lineview允许一次绘制几根颜色不同的曲线。

只需在上面的代码中为dataLists再添加一个list成员就行。

 

Lineview的实现

 

Lineviewview的直接子类public class LineView extends View,因为其绘制曲线的方法本身没什么亮点就不讲了,简单提一下其measure方法。重点讲他是如何实现动画效果的。

其实我也不是很明白measure过程中有些代码,自己看吧:

 

@Override

protectedvoidonMeasure(int widthMeasureSpec, intheightMeasureSpec) {

intmViewWidth = measureWidth(widthMeasureSpec);

mViewHeight =measureHeight(heightMeasureSpec);

refreshAfterDataChanged();

setMeasuredDimension(mViewWidth,mViewHeight);

}

privateintmeasureWidth(intmeasureSpec){

inthorizontalGridNum = getHorizontalGridNum();

intpreferred = backgroundGridWidth*horizontalGridNum+sideLineLength*2;

returngetMeasurement(measureSpec, preferred);

}

privateintmeasureHeight(intmeasureSpec){

intpreferred = 0;

returngetMeasurement(measureSpec,preferred);

}

privateintgetMeasurement(intmeasureSpec, intpreferred){

intspecSize = MeasureSpec.getSize(measureSpec);

intmeasurement;

switch(MeasureSpec.getMode(measureSpec)){

caseMeasureSpec.EXACTLY:

measurement = specSize;

break;

caseMeasureSpec.AT_MOST:

measurement = Math.min(preferred,specSize);

break;

default:

measurement = preferred;

break;

}

returnmeasurement;

}

 

动画:

如何才能展现出一条曲线的变化过程呢,曲线在波动其实是纵坐标y值在上下波动,横坐标x值是没有变化的,但是不要紧这个办法即使x值变化也应该可以展示出动画来,只是可能动画有点乱乱的感觉。

一条曲线的动画其实是多个个点的值在不断变化引起的,因此传统的android动画满足不了这么细致的需求,得自己想办法。

假如我们有这样的9个点(1,2),(2,0),(3,0),(4,2),(5,1),(6,0),(7,1),(8,2),(9,1)则用lineview可以得到如下的曲线图:

我们先预设最初的点为(1,0), (2,0), (3,0), (4,0), (5,0), (6,0), (7,0), (8,0), (9,0)

然后每隔一段时间就让每个点的y+1,这样就能得到动画效果。

 

private Runnable animator = new Runnable() {

@Override

public void run() {

boolean needNewFrame = false;

for(ArrayList<Dot> data :drawDotLists){

for(Dotdot : data){

dot.update();

if(!dot.isAtRest()){

needNewFrame = true;

}

}

}

if (needNewFrame) {

postDelayed(this, 10);

}

invalidate();

}

};

 

其中postDelayed(this, 10);表示每隔10毫秒执行一次值变化,同时刷新曲线图;dot.update();表示更新该点的值。有意思的是表示坐标的dot类,他不仅仅包含了坐标值,还有目标值:

class Dot{

intx;

inty;

intdata;

inttargetX;

inttargetY;

intlinenumber;

intvelocity =MyUtils.dip2px(getContext(),2);

Dot(int x,int y,inttargetX,int targetY,Integer data,intlinenumber){

this.x = x;

this.y = y;

this.linenumber =linenumber;

setTargetData(targetX,targetY,data,linenumber);

}

Point getPoint(){

returnnewPoint(x,y);

}

Dot setTargetData(inttargetX,int targetY,Integer data,intlinenumber){

this.targetX =targetX;

this.targetY =targetY;

this.data =data;

this.linenumber =linenumber;

returnthis;

}

booleanisAtRest(){

return (x==targetX)&&(y==targetY);

}

voidupdate(){

x =updateSelf(x, targetX, velocity);

y =updateSelf(y, targetY, velocity);

}

privateintupdateSelf(int origin, inttarget, int velocity){

if(origin < target) {

origin += velocity;

} elseif(origin > target){

origin-= velocity;

}

if(Math.abs(target-origin)<velocity){

origin = target;

}

returnorigin;

}

}