2012-04-15 13 views
8

Estoy trabajando en un proyecto de prueba que es algo similar al ejemplo FingerPaint en Android SDK Demos. Estaba intentando implementar la funcionalidad deshacer/rehacer en mi proyecto, pero las cosas que probé no funcionaron como esperaba. Encuentro algunas preguntas similares a esta sobre internet y aquí, pero no me ayudaron, es por eso que estoy haciendo una nueva pregunta.Android FingerPaint Deshacer/Rehacer implementación

Aquí es una idea de lo que estoy haciendo realidad:

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

Cualquier sugerencias/ideas/ejemplos que es la mejor manera de implementar este tipo de funcionalidad en mi proyecto?

+0

¿Qué soluciones ha intentado ya? – marwinXXII

+0

este: http://stackoverflow.com/questions/7633282/android-add-undo-feature-to-finger-paint-example-in-api-demo –

Respuesta

11

No sé si esto es lo que tenía en mente, pero es cómo lo estoy haciendo. En lugar de almacenarlo en una sola ruta, usted almacena una matriz con todas las rutas, de esta manera el usuario puede dibujar muchas líneas, con una pequeña modificación puede agregar también varias teclas táctiles.

Para deshacer y rehacer, simplemente elimine o agregue la última ruta de ruta de la variable paths y guárdela en una nueva matriz. Algo como:

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 
} 

Aquí está mi panel modificado, no puedo probarlo ahora pero los métodos anteriores deberían funcionar. ¡Espero eso ayude! (Hay pocas variables adicionales sólo eliminarlos :)

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

Gracias amigo! Esta implementación funciona como un encanto !!!! Solo necesita agregar 'invalidate();' en 'onClickUndo()' y 'onClickRedo();' para que pueda obtener el resultado justo después de hacer clic en el botón. –

+0

Me alegra ayudar :) ¡Lo agregué a la respuesta! ¡Aclamaciones! – caiocpricci2

+0

Acabo de ver un problema en realidad. Al usar 'mPaint.setXfermode (new PorterDuffXfermode (PorterDuff.Mode.CLEAR));' con su código, el resultado son líneas negras, no actúa como un pincel. Y la otra cosa es si trato de establecer algún efecto en el trazo, también está cambiando todas las líneas viejas. –

0

Creo que en este caso puede usar dos lienzos. Usted sabe cuando el usuario comienza a dibujar y cuando termina. Por lo tanto, en touch_start puede crear una copia de su lienzo actual. Cuando el usuario hace clic en deshacer, reemplaza el lienzo actual con el guardado previamente.

Esto debería garantizar que tendrá el estado previo de la imagen, pero no estoy seguro del rendimiento.

4

La mejor solución es que implemente su propio motor Deshacer/Rehacer.

  1. Guardar en una matriz de cada acción se realiza en una matriz (es decir. [0] círculo en la posición x1, y1, [1] la línea de x2, y2 a x3, y3, etc)

  2. dibujar

  3. si necesita deshacer, borrar el lienzo y volver a pintar todo el n - 1 acciones de [0] a [n - 1]

  4. Si deshace más solo hay que pintar de [0] a [n - 2] etc.

espero que le da un toque

Salud!

+1

En realidad esa fue mi idea, pero quiero saber si eso es lo mejor que puedo hacer para hacer esto. –

4

Una forma de implementar una funcionalidad do/rehacer es encapsular una llamada al método y toda la información necesaria para la llamada en un objeto de manera que se puede almacenar y llámalo más tarde - el Command Pattern.

En este patrón, cada acción tiene su propio objeto: DrawCircleCommand, DrawPathCommand, FillColorCommand, etc. En cada objeto, el método draw() se implementa de una manera única pero siempre se llama como draw (Canvas canvas) que permite CommandManager para iterar a través de los comandos.Para deshacer, itera sobre los objetos que llaman al método undo();

Cada objeto de comando implementa una interfaz

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

Un objeto se vería así:

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 
} 

}

Sus comandos se añaden al Administrador de comandos:

mCommandManager.addCommand(IDrawCommand command) 

y para deshacer un comando que acaba de llamar:

mCommandManager.undo(); 

Las tiendas CommandManager los comandos en una estructura de datos, por ejemplo, lista que le permite iterar sobre los objetos de comando.

Puede encontrar un tutorial completo here sobre cómo implementar el patrón de comando con do/undo para el dibujo de lienzo en Android.

Here es otro tutorial sobre cómo implementar el patrón de comando en Java;

0

Afortunadamente, lo resolví hoy. Encuentro una forma de hacer esto. Algo como:

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

en Actividad.

private DrawView   mDrawView; 

if (v == mIvUndo) { 
     mDrawView.undo(); 

    } else if (v == mIvRedo) { 
     mDrawView.redo(); 

en XML

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

Si usted tiene una mejor solución puede ponerse en contacto conmigo.

[aquí está mi blog que escribo en

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

Cuestiones relacionadas