http://www.cnblogs.com/top5/archive/2012/04/09/2439626.html
Java代码
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Bitmap.CompressFormat;
import android.os.Environment;
import android.view.MotionEvent;
import android.view.View;
/**
* View实现涂鸦、撤销以及重做功能
*/
public class TuyaView extends View {
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mBitmapPaint;// 画布的画笔
private Paint mPaint;// 真实的画笔
private float mX, mY;// 临时点坐标
private static final float TOUCH_TOLERANCE = 4;
// 保存Path路径的集合,用List集合来模拟栈
private static List<DrawPath> savePath;
// 记录Path路径的对象
private DrawPath dp;
private int screenWidth, screenHeight;
private class DrawPath {
public Path path;// 路径
public Paint paint;// 画笔
}
public TuyaView(Context context, int w, int h) {
super(context);
screenWidth = w;
screenHeight = h;
mBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);
// 保存一次一次绘制出来的图形
mCanvas = new Canvas(mBitmap);
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘
mPaint.setStrokeCap(Paint.Cap.ROUND);// 形状
mPaint.setStrokeWidth(5);// 画笔宽度
savePath = new ArrayList<DrawPath>();
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawColor(0xFFAAAAAA);
// 将前面已经画过得显示出来
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
if (mPath != null) {
// 实时的显示
canvas.drawPath(mPath, mPaint);
}
}
private void touch_start(float x, float y) {
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(mY - y);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
// 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也是可以的)
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
//将一条完整的路径保存下来(相当于入栈操作)
savePath.add(dp);
mPath = null;// 重新置空
}
/**
* 撤销的核心思想就是将画布清空,
* 将保存下来的Path路径最后一个移除掉,
* 重新将路径画在画布上面。
*/
public void undo() {
if (savePath != null && savePath.size() > 0) {
savePath.remove(savePath.size() - 1);
redrawOnBitmap();
}
}
/**
* 重做
*/
public void redo(){
if (savePath != null && savePath.size() > 0) {
savePath.clear();
redrawOnBitmap();
}
}
private void redrawOnBitmap(){
mBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBitmap);// 重新设置画布,相当于清空画布
Iterator<DrawPath> iter = savePath.iterator();
while (iter.hasNext()) {
DrawPath drawPath = iter.next();
mCanvas.drawPath(drawPath.path, drawPath.paint);
}
invalidate();// 刷新
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 每次down下去重新new一个Path
mPath = new Path();
//每一次记录的路径对象是不一样的
dp = new DrawPath();
dp.path = mPath;
dp.paint = mPaint;
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
public void saveToSDCard(){
String fileUrl = Environment.getExternalStorageDirectory()
.toString() + "/android/data/test.png";
try {
FileOutputStream fos = new FileOutputStream(new File(fileUrl));
mBitmap.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Java代码
import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
public class TuyaActivity extends Activity {
private TuyaView tuyaView = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
tuyaView = new TuyaView(this, dm.widthPixels, dm.heightPixels);
setContentView(tuyaView);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {// 返回键
tuyaView.undo();
return true;
}else if(keyCode == KeyEvent.KEYCODE_MENU){//MENU
tuyaView.redo();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
相关推荐
"自定义控件--涂鸦View"这个主题聚焦于如何在Android中创建一个允许用户自由绘画的自定义视图,即实现一个可以涂鸦的功能。这篇博客(原文链接:http://blog.csdn.net/u010593680/article/details/38539913)深入...
通过这个“Android 实现画布涂鸦功能 源码”项目,开发者不仅可以学习到如何在Android上实现基本的绘图功能,还能了解到如何处理触摸事件、优化绘图性能以及实现高级特性,如颜色选择、橡皮擦和撤销/重做。...
在Android开发中,实现...通过学习和分析这个仿QQ的项目源码,开发者不仅可以掌握图文混排和涂鸦功能的实现,还能深入理解Android图形系统、事件处理机制以及自定义View的相关知识,对提升Android开发能力大有裨益。
在“涂鸦”部分,开发者通常会使用Canvas和Paint类来构建一个画板,让用户能够自由绘制线条、填充颜色,并提供橡皮擦、撤销、重做等操作。为了保存和发送涂鸦内容,可以将绘制的图形序列化为Bitmap,然后转换为Base...
总结,实现“安卓Android源码——仿qq实现图文混排以及涂鸦等功能”涉及到Android UI设计、触摸事件处理、自定义View、图形绘制等多个核心知识点。通过深入理解和实践,开发者可以构建出更加丰富和有趣的用户体验。
7. **撤销和重做功能**:为了提供用户友好性,可以实现撤销和重做的功能。这通常需要维护一个历史记录栈,保存用户的每一个绘画动作。当用户触发撤销或重做时,可以从栈中取出相应的动作并执行。 8. **优化性能**:...
4. **保存与恢复绘图状态**:为了支持撤销/重做操作,需要保存和恢复绘图的状态,这可以通过序列化`Path`对象,或者使用Android的`SaveLayer`功能来实现。 5. **颜色选择器与画笔粗细调节**:提供用户选择画笔颜色...
实现撤销和重做功能,可以利用栈的数据结构,每当用户完成一次绘图操作,就将当前状态压入栈中。撤销操作就是弹出栈顶状态,重做则是将上次被撤销的状态重新压回栈顶并绘制。 6. **手势识别**: 可以使用`...
8. **撤销/重做机制**:为了提高用户体验,可以实现撤销和重做功能。这通常涉及到记录每一步的画笔动作,当用户触发撤销或重做时,回退或前进到历史记录中的某一步。 9. **画板大小调整**:允许用户调整画板的大小...
5. **撤销/重做功能**:许多涂鸦应用都提供了撤销和重做功能,这需要开发者维护一个操作历史栈,并能够根据用户的选择回滚或恢复操作。 6. **图片处理**:控件可能还包含了加载、保存和显示图片的功能。这可能涉及...
这个“简单的安卓涂鸦功能app”就是这样一个实例,它旨在帮助初学者理解如何在安卓平台上实现画笔交互和图形绘制。下面我们将深入探讨这个应用的关键知识点。 1. **画布(Canvas)与画笔(Paint)**: 在安卓中,...
此外,还需要一个保存和撤销/重做的功能,这可能需要额外的UI元素。 其次,触摸事件处理是实现涂鸦功能的关键。Android中的MotionEvent类用于处理触摸事件,包括ACTION_DOWN(按下)、ACTION_MOVE(移动)和ACTION_...
此外,撤销和重做功能的实现通常需要维护一个操作历史栈,每次用户绘制一笔或进行其他操作时,都将当前状态压入栈中。撤销时,从栈顶弹出状态并回放之前的操作;重做则将刚刚撤销的状态再次推入栈并回放。 最后,...
撤销/重做功能也是常见需求,这需要保存用户的绘图历史,每次绘图操作后更新历史栈,当用户触发撤销或重做时,根据历史记录回滚或推进状态。 最后,为了保存用户的创作,应用需要提供保存和分享功能。保存一般将...
撤销/重做功能则可以通过保存每次绘图操作的历史记录来实现,使用栈数据结构(如std::stack)来存储这些操作,当用户触发撤销或重做时,进行相应的操作回退或恢复。 对于文件保存和打开,QT提供了QFile、QIODevice...
可以通过维护一个操作栈来实现撤销与重做功能。每次用户绘制新的线条,就将其作为一个操作压入栈中。当用户点击撤销时,从栈顶弹出一个操作并反向执行;点击重做时,将最近撤销的操作重新执行。 7. **优化性能**:...
通过维护一个操作历史栈,可以实现撤销和重做功能。每次用户进行绘制操作时,都将当前状态压入栈中;当用户选择撤销或重做时,从栈中弹出或推回状态。 8. **画板背景**: 用户可能希望选择不同的背景,这涉及到...
3. **撤销与重做**:为了提供用户友好的体验,涂鸦画板通常会有撤销和重做功能。这需要记录每次绘制操作的历史记录,以便在需要时恢复或取消上一步操作。 4. **橡皮擦功能**:除了画笔,还需要一个橡皮擦模式,可以...
此外,为了提供更好的用户体验,涂鸦应用还可以实现撤销/重做功能,这需要维护一个历史栈,每次操作都入栈,撤销时出栈恢复,重做时则从栈顶取出状态重新绘制。 总结来说,基于Android的涂鸦程序开发涵盖了Android...