`

View实现涂鸦、撤销以及重做功能

 
阅读更多
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

    "自定义控件--涂鸦View"这个主题聚焦于如何在Android中创建一个允许用户自由绘画的自定义视图,即实现一个可以涂鸦的功能。这篇博客(原文链接:http://blog.csdn.net/u010593680/article/details/38539913)深入...

    Android 实现画布涂鸦功能 源码

    通过这个“Android 实现画布涂鸦功能 源码”项目,开发者不仅可以学习到如何在Android上实现基本的绘图功能,还能了解到如何处理触摸事件、优化绘图性能以及实现高级特性,如颜色选择、橡皮擦和撤销/重做。...

    仿qq实现图文混排以及涂鸦等功能.zip

    在Android开发中,实现...通过学习和分析这个仿QQ的项目源码,开发者不仅可以掌握图文混排和涂鸦功能的实现,还能深入理解Android图形系统、事件处理机制以及自定义View的相关知识,对提升Android开发能力大有裨益。

    仿qq实现图文混排以及涂鸦等功能

    在“涂鸦”部分,开发者通常会使用Canvas和Paint类来构建一个画板,让用户能够自由绘制线条、填充颜色,并提供橡皮擦、撤销、重做等操作。为了保存和发送涂鸦内容,可以将绘制的图形序列化为Bitmap,然后转换为Base...

    安卓Android源码——仿qq实现图文混排以及涂鸦等功能.zip

    总结,实现“安卓Android源码——仿qq实现图文混排以及涂鸦等功能”涉及到Android UI设计、触摸事件处理、自定义View、图形绘制等多个核心知识点。通过深入理解和实践,开发者可以构建出更加丰富和有趣的用户体验。

    Android-用于涂鸦的自定义AndroidView

    7. **撤销和重做功能**:为了提供用户友好性,可以实现撤销和重做的功能。这通常需要维护一个历史记录栈,保存用户的每一个绘画动作。当用户触发撤销或重做时,可以从栈中取出相应的动作并执行。 8. **优化性能**:...

    Android高级应用源码-仿qq实现图文混排以及涂鸦等功能.zip

    4. **保存与恢复绘图状态**:为了支持撤销/重做操作,需要保存和恢复绘图的状态,这可以通过序列化`Path`对象,或者使用Android的`SaveLayer`功能来实现。 5. **颜色选择器与画笔粗细调节**:提供用户选择画笔颜色...

    安卓开发-仿qq实现图文混排以及涂鸦等功能.zip

    实现撤销和重做功能,可以利用栈的数据结构,每当用户完成一次绘图操作,就将当前状态压入栈中。撤销操作就是弹出栈顶状态,重做则是将上次被撤销的状态重新压回栈顶并绘制。 6. **手势识别**: 可以使用`...

    android涂鸦功能

    8. **撤销/重做机制**:为了提高用户体验,可以实现撤销和重做功能。这通常涉及到记录每一步的画笔动作,当用户触发撤销或重做时,回退或前进到历史记录中的某一步。 9. **画板大小调整**:允许用户调整画板的大小...

    swift-一款简单的图片涂鸦iOS控件方便实用可以轻松引用到你的工程中

    5. **撤销/重做功能**:许多涂鸦应用都提供了撤销和重做功能,这需要开发者维护一个操作历史栈,并能够根据用户的选择回滚或恢复操作。 6. **图片处理**:控件可能还包含了加载、保存和显示图片的功能。这可能涉及...

    一个简单的安卓涂鸦功能app

    这个“简单的安卓涂鸦功能app”就是这样一个实例,它旨在帮助初学者理解如何在安卓平台上实现画笔交互和图形绘制。下面我们将深入探讨这个应用的关键知识点。 1. **画布(Canvas)与画笔(Paint)**: 在安卓中,...

    android实现简易的涂鸦板

    此外,还需要一个保存和撤销/重做的功能,这可能需要额外的UI元素。 其次,触摸事件处理是实现涂鸦功能的关键。Android中的MotionEvent类用于处理触摸事件,包括ACTION_DOWN(按下)、ACTION_MOVE(移动)和ACTION_...

    Android仿微信图片编辑涂鸦.zip

    此外,撤销和重做功能的实现通常需要维护一个操作历史栈,每次用户绘制一笔或进行其他操作时,都将当前状态压入栈中。撤销时,从栈顶弹出状态并回放之前的操作;重做则将刚刚撤销的状态再次推入栈并回放。 最后,...

    Android画板 涂鸦板

    撤销/重做功能也是常见需求,这需要保存用户的绘图历史,每次绘图操作后更新历史栈,当用户触发撤销或重做时,根据历史记录回滚或推进状态。 最后,为了保存用户的创作,应用需要提供保存和分享功能。保存一般将...

    QT_简易涂鸦板

    撤销/重做功能则可以通过保存每次绘图操作的历史记录来实现,使用栈数据结构(如std::stack)来存储这些操作,当用户触发撤销或重做时,进行相应的操作回退或恢复。 对于文件保存和打开,QT提供了QFile、QIODevice...

    涂鸦画笔与橡皮擦

    可以通过维护一个操作栈来实现撤销与重做功能。每次用户绘制新的线条,就将其作为一个操作压入栈中。当用户点击撤销时,从栈顶弹出一个操作并反向执行;点击重做时,将最近撤销的操作重新执行。 7. **优化性能**:...

    android画写板涂鸦软件源码

    通过维护一个操作历史栈,可以实现撤销和重做功能。每次用户进行绘制操作时,都将当前状态压入栈中;当用户选择撤销或重做时,从栈中弹出或推回状态。 8. **画板背景**: 用户可能希望选择不同的背景,这涉及到...

    一个涂鸦画板控件。。

    3. **撤销与重做**:为了提供用户友好的体验,涂鸦画板通常会有撤销和重做功能。这需要记录每次绘制操作的历史记录,以便在需要时恢复或取消上一步操作。 4. **橡皮擦功能**:除了画笔,还需要一个橡皮擦模式,可以...

    android涂鸦

    此外,为了提供更好的用户体验,涂鸦应用还可以实现撤销/重做功能,这需要维护一个历史栈,每次操作都入栈,撤销时出栈恢复,重做时则从栈顶取出状态重新绘制。 总结来说,基于Android的涂鸦程序开发涵盖了Android...

Global site tag (gtag.js) - Google Analytics