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);

	}

}
源码下载




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。