详解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;
}
}