一直想实现一个动态绘图的功能,就是那种给定几张图片之后一张张的顺序画出来。说不明白,先上效果图。
这样可以做很多东西,像百度地图的历史轨迹绘制,引导界面做类似动画效果等。
之前我考虑用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();
}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);
}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();
}
});
}
}
}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);
}
}
源码下载
原文地址:http://blog.csdn.net/qmln31821007/article/details/42971067