Android FingerPaint Undo/Redo implementation



我正在做一个测试项目,类似于Android SDK Dedemo FingerPaint示例。我试图在我的项目中实现撤消/重做功能,但我尝试的事情并没有像我预期的那样工作。我在互联网和这里发现了一些类似的问题,但它们对我没有帮助,这就是我问一个新问题的原因。

这是我实际在做什么的一些想法:

    public class MyView extends View {
    //private static final float MINP = 0.25f;
    //private static final float MAXP = 0.75f;

    private Path    mPath;
    private Paint   mBitmapPaint;
    public MyView(Context c) {
        super(c);
        mPath = new Path();
        mBitmapPaint = new Paint(Paint.DITHER_FLAG);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
        mCanvas.drawColor(Color.WHITE);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
        canvas.drawPath(mPath, mPaint);
    }
    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;
    private void touch_start(float x, float y) {
        mPath.reset();
        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(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
            mX = x;
            mY = y;
        }
    }
    private void touch_up() {
        mPath.lineTo(mX, mY);
        // commit the path to our offscreen
        mCanvas.drawPath(mPath, mPaint);
        // kill this so we don't double draw
        mPath.reset();
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                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;
    }
}

任何建议/想法/示例,哪种是在我的项目中实现此类功能的最佳方式?

我不知道这

是否是你的想法,但这就是我的做法。您不是将其存储在一条路径中,而是存储一个包含所有路径的数组,像这样,用户可以绘制许多线条,只需稍作修改,您也可以添加多点触控。

要撤消和重做,只需从paths变量中删除或添加最后一个路径路径并将它们存储在新阵列中。像这样:

public void onClickUndo () { 
    if (paths.size()>0) { 
       undonePaths.add(paths.remove(paths.size()-1))
       invalidate();
     }
    else
     //toast the user 
}
public void onClickRedo (){
   if (undonePaths.size()>0) { 
       paths.add(undonePaths.remove(undonePaths.size()-1)) 
       invalidate();
   } 
   else 
     //toast the user 
}

这是我修改后的面板,我现在不能尝试,但上面的方法应该可以工作!希望对您有所帮助!(几乎没有额外的变量,只需将它们删除:)

private ArrayList<Path> undonePaths = new ArrayList<Path>(); 
public class DrawingPanel extends View implements OnTouchListener {
private Canvas  mCanvas;
private Path    mPath;
private Paint   mPaint,circlePaint,outercirclePaint;   
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>(); 
private float xleft,xright,xtop,xbottom;
public DrawingPanel(Context context) {
    super(context);
    setFocusable(true);
    setFocusableInTouchMode(true);
    this.setOnTouchListener(this);

    circlePaint = new Paint();
    mPaint = new Paint();
    outercirclePaint = new Paint();
    outercirclePaint.setAntiAlias(true);
    circlePaint.setAntiAlias(true);
    mPaint.setAntiAlias(true);        
    mPaint.setColor(0xFFFFFFFF);
    outercirclePaint.setColor(0x44FFFFFF);
    circlePaint.setColor(0xAADD5522);
    outercirclePaint.setStyle(Paint.Style.STROKE);
    circlePaint.setStyle(Paint.Style.FILL);        
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeJoin(Paint.Join.ROUND);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setStrokeWidth(6);
    outercirclePaint.setStrokeWidth(6);        
    mCanvas = new Canvas();
    mPath = new Path();
    paths.add(mPath);             

    cx = 400*DrawActivity.scale;
    cy = 30*DrawActivity.scale;
    circleRadius = 20*DrawActivity.scale;
    xleft = cx-10*DrawActivity.scale;
    xright = cx+10*DrawActivity.scale;
    xtop = cy-10*DrawActivity.scale;
    xbottom = cy+10*DrawActivity.scale;
}

public void colorChanged(int color) {
    mPaint.setColor(color);
}

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }
    @Override
    protected void onDraw(Canvas canvas) {            
        for (Path p : paths){
            canvas.drawPath(p, mPaint);
        }
    }
    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 0;
    private void touch_start(float x, float y) {
        mPath.reset();
        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(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
            mX = x;
            mY = y;
        }
    }
    private void touch_up() {
        mPath.lineTo(mX, mY);
        // commit the path to our offscreen
        mCanvas.drawPath(mPath, mPaint);
        // kill this so we don't double draw            
        mPath = new Path();
        paths.add(mPath);
    }

@Override
public boolean onTouch(View arg0, MotionEvent event) {
      float x = event.getX();
      float y = event.getY();
      switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
              if (x <= cx+circleRadius+5 && x>= cx-circleRadius-5) {
                  if (y<= cy+circleRadius+5 && cy>= cy-circleRadius-5){
                      paths.clear();
                      return true;
                      }
              }
              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;
}


}

最好的解决方案是实现自己的撤消/重做引擎。

  1. 将您在数组中执行的每个操作保存到数组中(即。[0] 位置 x1、y1 中的圆圈,[1] 从 x2、y2 到 x3、y3 等的线)

  2. 如果需要撤消,请清除画布并将所有 n - 1 操作从 [0] 重新绘制到 [n - 1]

  3. 如果您撤消更多操作,只需从 [0] 绘制到 [n - 2] 等

我希望它能给你一个提示

干杯!

实现 do/redo 功能的一种方法是将方法调用和调用所需的所有信息封装在一个对象中,以便您可以存储它并在以后调用它 - 命令模式。

在此模式中,每个操作都有自己的对象:DrawCircleCommand、DrawPathCommand、FillColorCommand 等。在每个对象中,draw() 方法以独特的方式实现,但始终称为 draw(Canvas canvas),它允许 CommandManager 遍历命令。要撤消,您需要遍历调用 undo() 方法的对象;

每个命令对象实现一个接口

public interface IDrawCommand {  
  public void draw(Canvas canvas);  
  public void undo();  
}  

对象如下所示:

public class DrawPathCommand implements IDrawCommand{  
  public Path path;  
  public Paint paint;  
  public void setPath(path){
    this.path = path
  }
  public void draw(Canvas canvas) {  
    canvas.drawPath( path, paint );  
  }  
  public void undo() {  
   //some  action to remove the path
 }  
}  

您的命令将添加到命令管理器中:

mCommandManager.addCommand(IDrawCommand command)

要撤消命令,您只需调用:

mCommandManager.undo();
命令管理器将命令

存储在数据结构中,例如列表,允许它对命令对象进行迭代。

您可以在此处找到有关如何在 Android 上使用 do/撤消 Canvas 绘图实现命令模式的完整教程。

这是另一个关于如何在 Java 中实现命令模式的教程;

幸运的是,我今天解决了这个问题。我找到了一种方法来做到这一点。像这样:

private ArrayList<Path> paths       = new ArrayList<>();
private ArrayList<Path> undonePaths = new ArrayList<>();
public void undo() {
    if (paths.size() > 0) {
        LogUtils.d("undo " + paths.size());
        clearDraw();
        undonePaths.add(paths.remove(paths.size() - 1));
        invalidate();
    }
}
public void redo() {
    if (undonePaths.size() > 0) {
        LogUtils.d("redo " + undonePaths.size());
        clearDraw();
        paths.add(undonePaths.remove(undonePaths.size() - 1));
        invalidate();
    }
}

public void clearDraw() {
    mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
    mCanvas.setBitmap(mBitmap);
    invalidate();
}
public void clear() {
    paths.clear();
    undonePaths.clear();
    invalidate();
}

在活动中。

private DrawView            mDrawView;
if (v == mIvUndo) {
        mDrawView.undo();
    } else if (v == mIvRedo) {
        mDrawView.redo();

在 XML 中

<com.cinread.note.view.DrawView
        android:id="@+id/paintView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

如果您有更好的解决方案,可以与我联系。

[这是我写的博客

http://blog.csdn.net/sky_pjf/article/details/51086901]

我认为在这种情况下

您可以使用两个画布。您知道用户何时开始绘制以及何时完成。因此,在touch_start您可以创建当前画布的副本。当用户单击撤消时,您将当前画布替换为以前保存的画布。

这应该可以保证您将拥有以前的图片状态,但我不确定性能。

最新更新