/**
* @Title: MyView.java*/
package com.zero.view;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Formatter.BigDecimalLayoutForm;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Environment;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* @ClassName: MyView
* @Description: 塗鴉功能實現
* @author ZeRo_Ci
* @date 2014-3-21 下午3:06:42
*
*/
public class MyView extends View {
/** 用于画线 */
private Paint mPaint = null;
/** 用于绘制背景 */
private Paint mBitmapPaint = null;
/** 保存点 */
private Path mPath = null;
/** 用于选择图片 */
private Bitmap mBitmap = null;
/** 用于背景图 */
private Bitmap mBottomBitmap = null;
Canvas mCanvas = null;
/** 用于触摸点 */
float posX, posY;
private final float TOUCH_TOLERANCE = 4;
private DrawPath mDrawPath = null;
/** 保存 */
private List<DrawPath> mSavePath = null;
/** 清除 */
private List<DrawPath> mDeletePath = null;
/** 图片路径 */
private String mImagePath = null;
/** 图片的宽度 */
private int mImageWidth = 480;
/** 图片的长度 */
private int mImageHeight = 800;
private int mBottomBitmapDrawHeight = 0;
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context) {
super(context);
init();
}
/**
* 初始化实例
*/
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFFCCCCCC);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
mSavePath = new ArrayList<MyView.DrawPath>();
mDeletePath = new ArrayList<MyView.DrawPath>();
mImagePath = initPath();
}
/**
* 初始化路径
*/
private String initPath() {
/** 获取外部存储的路径返回绝对路径的,其实就是你的SD卡的文件路径 */
String ph = Environment.getExternalStorageDirectory().getAbsolutePath();
if (ph == null) {
return null;
}
ph += "/zerotuya";
File imageFile = new File(ph);
/** 文件操作 */
if (!imageFile.exists()) {
/** 创建目录 */
imageFile.mkdir();
}
return ph;
}
private class DrawPath {
Path path;
Paint paint;
}
/*
* (non-Javadoc)
*
* @see android.view.View#onSizeChanged(int, int, int, int)
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBottomBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBottomBitmap);
}
/*
* (non-Javadoc)
*
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFF000000);
int nCanvasWidth = canvas.getWidth();
int nCanvasHeight = canvas.getHeight();
int nBitmapWidth = mBottomBitmap.getWidth();
int nBitmaopHeight = mBottomBitmap.getHeight();
mBottomBitmapDrawHeight = (nCanvasHeight - nBitmaopHeight) / 2;
canvas.drawBitmap(mBottomBitmap, 0, mBottomBitmapDrawHeight,
mBitmapPaint);
if (mPath != null) {
canvas.drawPath(mPath, mPaint);
}
}
/*
* (non-Javadoc)
*
* @see android.view.View#onTouchEvent(android.view.MotionEvent)
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDown(x, y);
break;
case MotionEvent.ACTION_MOVE:
touchMove(x, y);
break;
case MotionEvent.ACTION_UP:
touchUp();
break;
}
return true;
}
/**
* 按下的点
*/
private void touchDown(float x, float y) {
mPath = new Path();
mDrawPath = new DrawPath();
mPath.moveTo(x, y);
mDrawPath.paint = new Paint(mPaint);
mDrawPath.path = mPath;
posX = x;
posY = y;
/**
* Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,
* 其中前者是在UI线程自身中使用,而后者在非UI线程中使用。
* Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用
* ,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。
*
* Android程序中可以使用的界面刷新方法有两种,分别是利用Handler和利用postInvalidate()来实现在线程中刷新界面
* 。
*
* @author1,利用invalidate()刷新界面
* 实例化一个Handler对象,并重写handleMessage方法调用invalidate
* () 实现界面刷新;而在线程中通过sendMessage发送界面更新消息。
*
* @author 2,使用postInvalidate()刷新界面
* 使用postInvalidate则比较简单,不需要handler,直接在线程中调用postInvalidate即可。
*/
postInvalidate();
}
/**
* 移动
*/
private void touchMove(float x, float y) {
float dx = Math.abs(x - posX);
float dy = Math.abs(y - posY);
if (dx >= TOUCH_TOLERANCE || dy > TOUCH_TOLERANCE) {
mPath.quadTo(posX, posY, (x + posX) / 2, (y + posY) / 2);
posX = x;
posY = y;
}
postInvalidate();
}
/**
* 抬起
*/
private void touchUp() {
mPath.lineTo(posX, posY);
mPath.offset(0, -mBottomBitmapDrawHeight);
mCanvas.drawPath(mPath, mPaint);
mSavePath.add(mDrawPath);
postInvalidate();
}
/**
* 背景图
*
* @return
*
*/
private boolean setBitmap(String imagePath) {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float nxScale = -1;
float nyScale = -1;
if (width != 0 && height != 0) {
nxScale = (float) width / mImageWidth;
nyScale = (float) height / mImageHeight;
if (nxScale >= 1 && nyScale >= 1 || nxScale < 1 && nyScale < 1) {
if (nxScale > nyScale) {
width = (int) (width / nxScale);
height = (int) (height / nxScale);
} else {
width = (int) (width / nyScale);
height = (int) (height / nyScale);
}
}
if (nxScale >= 1 && nyScale < 1) {
width = mImageWidth;
}
if (nxScale <= 1 && nyScale >= 1) {
height = mImageHeight;
}
mBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
mBottomBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mSavePath.clear();
mDeletePath.clear();
mCanvas.setBitmap(mBottomBitmap);
mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
postInvalidate();
return true;
} else
return false;
}
/**
* 背景颜色
*/
private void setBitmapColor(int color) {
mBottomBitmap.eraseColor(color);
mSavePath.clear();
mDeletePath.clear();
postInvalidate();
}
/**
* 画笔
*/
private void setPaint(Paint paint) {
mPaint = paint;
postInvalidate();
}
/**
* 保存图片
*/
private void saveImage(String imagePath) {
if (mImagePath == null || mBitmap == null) {
return;
}
String imageName = null;
int nStart = imagePath.lastIndexOf('/');
int nEnd = imagePath.lastIndexOf('.');
imageName = imagePath.substring(nStart, nEnd);
imageName += ".png";
imageName = mImagePath + imageName;
File file = new File(imageName);
/**
* @author createNewFile
* 当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的一个新的空文件。检查文件是否存在
* ,如果不存在则创建该文件,这是单个操作,对于其他所有可能影响该文件的文件系统活动来说,该操作是原子的。
*
* @author createTempFile File.createTempFile
* 的用途是你想要建立一个档案暂时使用,但是你不在乎其精确的档案名
* ,只要不覆盖到已存在的档案时。可以制定临时文件的文件名前缀、后缀及文件所在的目录
* ,如果不指定目录,则存放在系统的临时文件夹下。 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称
*/
try {
file.createNewFile();
/** 创建一个向指定File对象表示的文件中写入数据的文件输出流 */
FileOutputStream out = new FileOutputStream(file);
/** 压缩图片//100是压缩率,表示压缩率为0 即不压缩 ,如果是30 ,表示压缩70% */
mBottomBitmap.compress(CompressFormat.PNG, 100, out);
/**
* flush() 是把缓冲区的数据强行输出,
* 主要用在IO中,即清空缓冲区数据,一般在读写流(stream)的时候,数据是先被读到了内存中
* ,再把数据写到文件中,当你数据读完的时候不代表你的数据已经写完了
* ,因为还有一部分有可能会留在内存这个缓冲区中。这时候如果你调用了close
* ()方法关闭了读写流,那么这部分数据就会丢失,所以应该在关闭读写流之前先flush()。
*/
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 清理图片
*/
private void clearImage() {
mSavePath.clear();
mDeletePath.clear();
if (mBitmap != null) {
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
mBottomBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBottomBitmap);
mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
} else {
int width = mCanvas.getWidth();
int height = mCanvas.getHeight();
mBottomBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBottomBitmap);
}
postInvalidate();
}
/**
*
*/
private void undo() {
int nSize = mSavePath.size();
if (nSize >= 1) {
mDeletePath.add(0, mSavePath.get(nSize - 1));
mSavePath.remove(nSize - 1);
} else
return;
if (mBitmap != null) {
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
mBottomBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBottomBitmap);
mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
} else {
int width = mCanvas.getWidth();
int height = mCanvas.getHeight();
mBottomBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBottomBitmap);
}
/**
* 迭代器(Iterator)
*
* 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象
* ,因为创建它的代价小。
*
* Java中的Iterator功能比较简单,并且只能单向移动:
*
* (1)
* 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素
* 。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
*
* (2) 使用next()获得序列中的下一个元素。
*
* (3) 使用hasNext()检查序列中是否还有元素。
*
* (4) 使用remove()将迭代器新返回的元素删除。
*
* Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,
* 也可以从List中插入和删除元素。
*/
Iterator<DrawPath> iter = mSavePath.iterator();
DrawPath temp;
/**
* hasNext() 如果仍有元素可以迭代,则返回 true。 返回迭代的下一个元素。并把迭代输出的结果强制转换成Car对象
* hasNext()是判断是否有下一个元素 next() 得到下一个元素 iter.hasNext():判断集合中是否有下一个car
* iter.next():返回集合中的下一个car
* */
while (iter.hasNext()) {
temp = iter.next();
mCanvas.drawPath(temp.path, temp.paint);
}
postInvalidate();
}
private void redo() {
int nSeize = mDeletePath.size();
if (nSeize >= 1) {
mSavePath.add(mDeletePath.get(0));
mDeletePath.remove(0);
} else {
return;
}
if (mBitmap != null) {
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
mBottomBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBottomBitmap);
mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
} else {
int width = mCanvas.getWidth();
int height = mCanvas.getHeight();
mBottomBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBottomBitmap);
}
Iterator<DrawPath> iter = mSavePath.iterator();
DrawPath temp;
while (iter.hasNext()) {
temp = iter.next();
mCanvas.drawPath(temp.path, temp.paint);
}
postInvalidate();
}
}