Android动态绘图实现

来源:互联网 发布:波尔津吉斯体测数据 编辑:程序博客网 时间:2024/05/16 00:31

一直想实现一个动态绘图的功能,就是那种给定几张图片之后一张张的顺序画出来。说不明白,先上效果图。

这样可以做很多东西,像百度地图的历史轨迹绘制,引导界面做类似动画效果等。

之前我考虑用SurfaceView实现这个功能,想一想,要实现这种效果,需要开启一个子线程用于控制绘制时间间隔,以达到这种渐渐绘制的效果。动手去做了,发现用SurfaceView很难实现,SurfaceView中的Canvas与View中的Canvas不同,一个不同之处是View中的Canvas是只有一张画布,然后不停的在这张画布上操作,你所有画的图都是在一张画布上,但是SurfaceView不是的,SurfaceView中每次通过holder.lockCanvas()得到的都是一张新的画布,通过holder.unlockCanvasAndpost(canvas)提交之后会覆盖掉之前的画布,也就是说后面绘制的东西会把前面绘制的东西给遮挡住,另一个不同之处在SurfaceView可以在子线程中绘图,但是绘制之后并不马上显示出来,只有在holder.unlockCanvasAndpost(canvas)之后才会在显示出来,这一点与View中的canvas是明显不同的。基于以上两个不同,最后我还是采用了自定义View,利用View中的Canvas进行绘制。

绘制步骤:
(1)绘制传入的图像
(2)绘制第一张图像与第二张图像之间的线,这一步又分为两个小步
a、从第一张图中心店开始增加固定步长,一遍遍的绘制,直到接近第二张图的中心点
b、a绘制完成之后,重新绘制一条直线,路径跟a的路径保持一致,将a绘制的直线擦除
(3)重复(1)、(2)步,注意最后一张图片绘制完之后不用再绘线。

绘制过程中需要不停的控制时间间隔,也就意味需要不停的开启新的子线程,这样是很消耗内存的,极易造成内存溢出,所以这里我采用了线程池来管理线程。

代码:

新建一个项目AnimationView

新建一个类AnimationView继承View
添加构造函数,在构造函数中开启我们的线程池。

private void startExecutor() {executorThread = new Thread() {@Overridepublic void run() {// TODO Auto-generated method stubsuper.run();Looper.prepare();threadHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubsuper.handleMessage(msg);executorService.execute(getTask());try {semaphore.acquire();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}try {Thread.sleep(100);} catch (InterruptedException e1) {e1.printStackTrace();}}};handlerSemaphore.release();Looper.loop();}};executorThread.start();}
public AnimationView(Context context) {super(context);// TODO Auto-generated constructor stubstartExecutor();}public AnimationView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubstartExecutor();}public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// TODO Auto-generated constructor stubstartExecutor();}

关于线程池这里不多讲,想了解的话可以看看我的另一篇文章Android 线程池、信号量、Looper、缓存初探
添加两个方法用于添加任务和取出任务
private synchronized void addTask(Runnable r) {if (threadHandler == null)try {handlerSemaphore.acquire();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}task.add(r);threadHandler.obtainMessage().sendToTarget();}private synchronized Runnable getTask() {return task.removeFirst();}

添加一个方法用于向我们自定义的View添加图片及位置
public void addImageData(int id, Rect rect) {ImageData data = new ImageData();data.setId(id);data.setRect(rect);list.add(data);}
ImageData是自定义的一个类

package com.example.animationview;import android.graphics.Rect;public class ImageData {private int id;private Rect rect;public int getId() {return id;}public void setId(int id) {this.id = id;}public Rect getRect() {return rect;}public void setRect(Rect rect) {this.rect = rect;}}
覆写onDraw函数

@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubsuper.onDraw(canvas);if (this.list != null && this.list.size() != 0) {// 画图for (int i = 0; i <= index; i++) {canvas.save();ImageData data = this.list.get(i);Bitmap bitmap = BitmapFactory.decodeResource(getResources(),data.getId());canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth(),bitmap.getHeight()), data.getRect(), new Paint());canvas.restore();invalidate();bitmap = null;}if (isDrawBitmap && index <= this.list.size() - 2)// 判断后面还有没有图,有图则画线{isDrawBitmap = false;isDrawStep = true;isSendStepHandler = true;stepStartX = 0;stepStartY = 0;}// 画中间步骤if (isDrawStep) {ImageData data1 = this.list.get(index);ImageData data2 = this.list.get(index + 1);if (stepStartX == 0 && stepStartY == 0) {stepStartX = data1.getRect().centerX();stepStartY = data1.getRect().centerY();stepX = ((float) (data2.getRect().centerX() - data1.getRect().centerX())) / 16;stepY = ((float) (data2.getRect().centerY() - data1.getRect().centerY())) / 16;stepEndX = stepStartX + stepX;stepEndY = stepStartY + stepY;}canvas.save();Paint paint = new Paint();paint.setStrokeWidth(3);paint.setStyle(Style.STROKE);paint.setAntiAlias(true);paint.setColor(Color.RED);canvas.drawLine(stepStartX, stepStartY, stepEndX, stepEndY,paint);canvas.restore();invalidate();System.out.println("Math:  "+ Math.abs((double) (stepEndX - data2.getRect().centerX())) + "      "+ Math.abs((double) stepX));if (Math.abs((double) (stepEndX - data2.getRect().centerX())) <= Math.abs((double) stepX)&& Math.abs((double) (stepEndY - data2.getRect().centerY())) <= Math.abs((double) stepY))// 画线{isStopDrawStep = true;if (isSendStepHandler) {isSendStepHandler = false;addTask(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubhandler.obtainMessage(2).sendToTarget();semaphore.release();}});}} else {// 画中间步骤if (isSendStepHandler) {isSendStepHandler = false;addTask(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubhandler.obtainMessage(1).sendToTarget();semaphore.release();}});}}}// 画线for (int i = 0; i <= lineIndex; i++) {ImageData data1 = this.list.get(i);ImageData data2 = this.list.get(i + 1);startX = data1.getRect().centerX();startY = data1.getRect().centerY();endX = data2.getRect().centerX();endY = data2.getRect().centerY();canvas.save();Paint paint = new Paint();paint.setStrokeWidth(3);paint.setStyle(Style.STROKE);paint.setAntiAlias(true);paint.setColor(Color.RED);canvas.drawLine(startX, startY, endX, endY, paint);canvas.restore();invalidate();if (isStopDrawStep && (recodeLineIndex != lineIndex)) {recodeLineIndex = lineIndex;isDrawStep = false;isStopDrawStep = false;}}if (isDrawLine && lineIndex <= this.list.size() - 2) {isDrawLine = false;addTask(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubhandler.obtainMessage(0).sendToTarget();semaphore.release();}});}}}

这里是关键代码,里面我做了简单的注释,另外可以看到我在里面使用了大量的Boolean类型的变量用于控制绘制频率,不得不这样做,因为不做的话我们的View是不停的在刷新的,不加以控制,会产生很多的空任务,用线程池管理可以还不容易看出来,如果把线程池换成普通的子线程,你会发现,你的程序极有可能不到5秒钟就崩溃了,崩溃原因就是内存溢出。

AnimationView的完整代码

package com.example.animationview;import java.util.LinkedList;import java.util.List;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Paint.Style;import android.graphics.Rect;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.util.AttributeSet;import android.view.View;public class AnimationView extends View {private boolean isDrawBitmap = true, isDrawLine = false;private boolean isDrawStep = false;private boolean isStopDrawStep = false;private boolean isSendStepHandler = false;private int recodeLineIndex = -1;private List<ImageData> list = new LinkedList<ImageData>();private int index = 0, lineIndex = -1;// bitmap索引private float startX = 0, startY = 0, endX, endY;// 画线起点,间距private float stepStartX, stepStartY, stepX, stepY, stepEndX, stepEndY;private ExecutorService executorService = Executors.newFixedThreadPool(3);private Semaphore handlerSemaphore = new Semaphore(0);private Semaphore semaphore = new Semaphore(3);private LinkedList<Runnable> task = new LinkedList<Runnable>();private Handler threadHandler;private Thread executorThread;private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubsuper.handleMessage(msg);switch (msg.what) {case 0:isDrawBitmap = true;if (index < list.size() - 1)index++;break;case 1:isSendStepHandler = true;stepEndX += stepX;stepEndY += stepY;break;case 2:isDrawLine = true;if (lineIndex < list.size() - 2)lineIndex++;break;}}};public AnimationView(Context context) {super(context);// TODO Auto-generated constructor stubstartExecutor();}public AnimationView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubstartExecutor();}public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// TODO Auto-generated constructor stubstartExecutor();}private void startExecutor() {executorThread = new Thread() {@Overridepublic void run() {// TODO Auto-generated method stubsuper.run();Looper.prepare();threadHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubsuper.handleMessage(msg);executorService.execute(getTask());try {semaphore.acquire();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}try {Thread.sleep(100);} catch (InterruptedException e1) {e1.printStackTrace();}}};handlerSemaphore.release();Looper.loop();}};executorThread.start();}private synchronized void addTask(Runnable r) {if (threadHandler == null)try {handlerSemaphore.acquire();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}task.add(r);threadHandler.obtainMessage().sendToTarget();}private synchronized Runnable getTask() {return task.removeFirst();}public void addImageData(int id, Rect rect) {ImageData data = new ImageData();data.setId(id);data.setRect(rect);list.add(data);}@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubsuper.onDraw(canvas);if (this.list != null && this.list.size() != 0) {// 画图for (int i = 0; i <= index; i++) {canvas.save();ImageData data = this.list.get(i);Bitmap bitmap = BitmapFactory.decodeResource(getResources(),data.getId());canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth(),bitmap.getHeight()), data.getRect(), new Paint());canvas.restore();invalidate();bitmap = null;}if (isDrawBitmap && index <= this.list.size() - 2)// 判断后面还有没有图,有图则画线{isDrawBitmap = false;isDrawStep = true;isSendStepHandler = true;stepStartX = 0;stepStartY = 0;}// 画中间步骤if (isDrawStep) {ImageData data1 = this.list.get(index);ImageData data2 = this.list.get(index + 1);if (stepStartX == 0 && stepStartY == 0) {stepStartX = data1.getRect().centerX();stepStartY = data1.getRect().centerY();stepX = ((float) (data2.getRect().centerX() - data1.getRect().centerX())) / 16;stepY = ((float) (data2.getRect().centerY() - data1.getRect().centerY())) / 16;stepEndX = stepStartX + stepX;stepEndY = stepStartY + stepY;}canvas.save();Paint paint = new Paint();paint.setStrokeWidth(3);paint.setStyle(Style.STROKE);paint.setAntiAlias(true);paint.setColor(Color.RED);canvas.drawLine(stepStartX, stepStartY, stepEndX, stepEndY,paint);canvas.restore();invalidate();System.out.println("Math:  "+ Math.abs((double) (stepEndX - data2.getRect().centerX())) + "      "+ Math.abs((double) stepX));if (Math.abs((double) (stepEndX - data2.getRect().centerX())) <= Math.abs((double) stepX)&& Math.abs((double) (stepEndY - data2.getRect().centerY())) <= Math.abs((double) stepY))// 画线{isStopDrawStep = true;if (isSendStepHandler) {isSendStepHandler = false;addTask(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubhandler.obtainMessage(2).sendToTarget();semaphore.release();}});}} else {// 画中间步骤if (isSendStepHandler) {isSendStepHandler = false;addTask(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubhandler.obtainMessage(1).sendToTarget();semaphore.release();}});}}}// 画线for (int i = 0; i <= lineIndex; i++) {ImageData data1 = this.list.get(i);ImageData data2 = this.list.get(i + 1);startX = data1.getRect().centerX();startY = data1.getRect().centerY();endX = data2.getRect().centerX();endY = data2.getRect().centerY();canvas.save();Paint paint = new Paint();paint.setStrokeWidth(3);paint.setStyle(Style.STROKE);paint.setAntiAlias(true);paint.setColor(Color.RED);canvas.drawLine(startX, startY, endX, endY, paint);canvas.restore();invalidate();if (isStopDrawStep && (recodeLineIndex != lineIndex)) {recodeLineIndex = lineIndex;isDrawStep = false;isStopDrawStep = false;}}if (isDrawLine && lineIndex <= this.list.size() - 2) {isDrawLine = false;addTask(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubhandler.obtainMessage(0).sendToTarget();semaphore.release();}});}}}}
这样就定义好了这个AnimationView,那么在该怎么使用呢?很简单,可以加到布局文件中,也可以简单的在代码中直接使用。

布局文件中使用

fragment_main.xml

<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="com.example.animationview.MainActivity$PlaceholderFragment" >    <com.example.animationview.AnimationView        android:id="@+id/animationView"        android:layout_width="match_parent"        android:layout_height="match_parent" /></RelativeLayout>
package com.example.animationview;import android.graphics.Rect;import android.os.Bundle;import android.support.v7.app.ActionBarActivity;public class MainActivity extends ActionBarActivity {private AnimationView view;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.fragment_main);init();}private void init() {// TODO Auto-generated method stubview = (AnimationView) findViewById(R.id.animationView);view.addImageData(R.drawable.xin11, new Rect(0, 0, 100, 100));view.addImageData(R.drawable.xin16, new Rect(300, 150, 400, 250));view.addImageData(R.drawable.xin17, new Rect(300, 300, 500, 500));view.addImageData(R.drawable.xin2, new Rect(100, 200, 200, 300));view.addImageData(R.drawable.xin5, new Rect(100, 850, 200, 950));}}
直接在代码中使用
package com.example.animationview;import android.graphics.Rect;import android.os.Bundle;import android.support.v7.app.ActionBarActivity;public class MainActivity extends ActionBarActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);AnimationView view = new AnimationView(this);view.addImageData(R.drawable.xin11, new Rect(0, 0, 100, 100));view.addImageData(R.drawable.xin16, new Rect(300, 150, 400, 250));view.addImageData(R.drawable.xin17, new Rect(300, 300, 500, 500));view.addImageData(R.drawable.xin2, new Rect(100, 200, 200, 300));view.addImageData(R.drawable.xin5, new Rect(100, 850, 200, 950));setContentView(view);}}
源码下载




0 0
原创粉丝点击