Android FingerPaint Undo / Redo实现

时间:2012-04-15 20:48:59

标签: android paint android-view

我正在开发一个测试项目,类似于Android SDK演示中的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;
    }
}

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

5 个答案:

答案 0 :(得分:11)

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

要进行撤消和重做,只需从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 :(得分:4)

最佳解决方案是您实施自己的撤消/重做引擎。

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

  2. 画出

  3. 如果您需要撤消,请清除画布并重新绘制从[0]到[n - 1]的所有n - 1个动作

  4. 如果你撤消更多只需要从[0]到[n - 2]等画画

  5. 我希望它能给你一个提示

    干杯!

答案 2 :(得分:4)

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

在这种模式中,每个动作都有自己的对象: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
 }  

}

您的命令已添加到CommandManager:

mCommandManager.addCommand(IDrawCommand command)

并撤消您刚刚调用的命令:

mCommandManager.undo();

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

您可以在Android上找到有关如何使用do / undo实现命令模式的完整教程here

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

答案 3 :(得分:1)

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

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();

in 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]

答案 4 :(得分:0)

我认为在这种情况下你可以使用两幅画布。您知道用户何时开始绘制以及何时完成绘制。因此,在touch_start中,您可以创建当前画布的副本。当用户单击“撤消”时,将当前画布替换为先前保存的。

这应该保证你将有以前的状态,但我不确定性能。