Android动态绘图实现
一直想实现一个动态绘图的功能,就是那种给定几张图片之后一张张的顺序画出来。说不明白,先上效果图。
这样可以做很多东西,像百度地图的历史轨迹绘制,引导界面做类似动画效果等。
之前我考虑用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() { @Override public void run() { // TODO Auto-generated method stub super.run(); Looper.prepare(); threadHandler = new Handler() { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); executorService.execute(getTask()); try { semaphore.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.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 stub startExecutor(); } public AnimationView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub startExecutor(); } public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // TODO Auto-generated constructor stub startExecutor(); }
关于线程池这里不多讲,想了解的话可以看看我的另一篇文章Android 线程池、信号量、Looper、缓存初探
添加两个方法用于添加任务和取出任务
private synchronized void addTask(Runnable r) { if (threadHandler == null) try { handlerSemaphore.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.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函数
@Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.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() { @Override public void run() { // TODO Auto-generated method stub handler.obtainMessage(2).sendToTarget(); semaphore.release(); } }); } } else {// 画中间步骤 if (isSendStepHandler) { isSendStepHandler = false; addTask(new Runnable() { @Override public void run() { // TODO Auto-generated method stub handler.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() { @Override public void run() { // TODO Auto-generated method stub handler.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() { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.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 stub startExecutor(); } public AnimationView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub startExecutor(); } public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // TODO Auto-generated constructor stub startExecutor(); } private void startExecutor() { executorThread = new Thread() { @Override public void run() { // TODO Auto-generated method stub super.run(); Looper.prepare(); threadHandler = new Handler() { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); executorService.execute(getTask()); try { semaphore.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.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 block e.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); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.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() { @Override public void run() { // TODO Auto-generated method stub handler.obtainMessage(2).sendToTarget(); semaphore.release(); } }); } } else {// 画中间步骤 if (isSendStepHandler) { isSendStepHandler = false; addTask(new Runnable() { @Override public void run() { // TODO Auto-generated method stub handler.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() { @Override public void run() { // TODO Auto-generated method stub handler.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; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_main); init(); } private void init() { // TODO Auto-generated method stub view = (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 { @Override protected 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); } }源码下载
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。