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

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

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

绘制曲线图首先需要画好横竖坐标轴建立坐标系,比如坐标系中的100距离应该在canvas中绘制多长,这个是需要计算的,其实坐标体系的建立是最复杂的,我看过很多第三方库的建立方法都不一样,有的要灵活一些,有的比较死板。至于绘制曲线要么是用[Canvas](http://eclipse-javadoc:/?=NetMoonDeviceManager/D:\/android\/adt-bundle-windows-x86_64-20131030\/sdk\/platforms\/android-19\/android.jar%3candroid.graphics(Canvas.class?Canvas).drawLine方法,要么是用[Path](http://eclipse-javadoc:/?=NetMoonDeviceManager/D:\/android\/adt-bundle-windows-x86_64-20131030\/sdk\/platforms\/android-19\/android.jar%3candroid.graphics(Path.class?Path).lineTo方法,看你自己的习惯。

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

先讲第一个LineView

LineView的demo可以在这里下载,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;

ArrayListtest =newArrayList();

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 dataList = newArrayList();

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

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

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

}

添加纵坐标的值:

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

dataLists.add(dataList);

lineView.setDataList(dataLists);

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

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

Lineview****的实现

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

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

@Override

protected****voidonMeasure(int widthMeasureSpec, intheightMeasureSpec) {

intmViewWidth = measureWidth(widthMeasureSpec);

mViewHeight =measureHeight(heightMeasureSpec);

refreshAfterDataChanged();

setMeasuredDimension(mViewWidth,mViewHeight);

}

private****intmeasureWidth(intmeasureSpec){

inthorizontalGridNum = getHorizontalGridNum();

intpreferred = backgroundGridWidth*horizontalGridNum+sideLineLength*2;

returngetMeasurement(measureSpec, preferred);

}

private****intmeasureHeight(intmeasureSpec){

intpreferred = 0;

returngetMeasurement(measureSpec,preferred);

}

private****intgetMeasurement(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 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(){

return****newPoint(x,y);

}

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

this.targetX =targetX;

this.targetY =targetY;

this.data =data;

this.linenumber =linenumber;

return****this;

}

booleanisAtRest(){

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

}

voidupdate(){

x =updateSelf(x, targetX, velocity);

y =updateSelf(y, targetY, velocity);

}

private****intupdateSelf(int origin, inttarget, int velocity){

if(origin < target) {

origin += velocity;

} else****if(origin > target){

origin-= velocity;

}

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

origin = target;

}

returnorigin;

}

}