10

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

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

    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;
    }
}

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

4

5 回答 5

13

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

要进行撤消和重做,只需从变量中删除或添加最后一个路径路径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;
}




}
于 2012-05-11T19:55:55.817 回答
4

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

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

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

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

我希望它能给你一个提示

干杯!

于 2012-05-07T18:15:23.000 回答
4

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

在此模式中,每个动作都有自己的对象: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 为 Canvas 绘图实现命令模式的完整教程。

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

于 2012-05-11T23:22:11.163 回答
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();

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

于 2016-04-07T08:28:16.973 回答
0

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

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

于 2012-04-15T21:31:16.593 回答