2016-03-18 3 views
2

Je dessine un cercle (au toucher) sur le bitmap pour effacer le bitmap superposé pour cette zone dans le cercle. Comment puis-je ajouter des fonctions d'annulation et de rétablissement pour cela?ANDROID - Annuler et rétablir dans le canevas

EDIT: Veuillez vous référer à Android: Undo redo in CustomView car il a le problème auquel je suis actuellement confronté avec la solution donnée.

@Override 
    protected void onDraw(Canvas canvas) { 
     pcanvas.drawCircle(x, y, 10, mPaint); 
     canvas.drawBitmap(bitmap, 0, 0, null); 
     super.onDraw(canvas); 

    } 

onTouchEvent

public boolean onTouchEvent(MotionEvent ev) 
    { 
     switch (ev.getAction()) 
     { 
      case MotionEvent.ACTION_DOWN: 
      { 

       x = (int) ev.getX(); 
       y = (int) ev.getY(); 
       invalidate(); 

       break; 
      } 

      case MotionEvent.ACTION_MOVE: 
      { 

       x = (int) ev.getX(); 
       y = (int) ev.getY(); 
       invalidate(); 
       break; 

      } 

      case MotionEvent.ACTION_UP: 
       invalidate(); 
       break; 

     } 
     return true; 
    } 
+0

Vous pouvez probablement conserver une pile > 'pour suivre l'historique des coordonnées xy. Ensuite, pour annuler, vous en avez un. Une chose similaire peut être utilisée pour refaire, rappelez-vous juste d'effacer la pile de rétablissement une fois qu'une touche ajoute une nouvelle paire à la pile d'annulation. Il serait également sage de limiter le nombre de paires de xy que vous maintenez dans la pile à environ 50. – Kevin

+0

@Kevin Pourriez-vous m'aider avec du code? – zek54

Répondre

1

Comme mentionné dans les commentaires, vous pouvez garder pour suivre Stacks les coordonnées XY historique.

Les opérations Annuler et Rétablir s'articulent autour de la pression et de l'éclatement des piles séparées.

UndoCanvas

public class UndoCanvas extends View { 
    private final int MAX_STACK_SIZE = 50; 
    private Stack<Pair<Float, Float>> undoStack = new Stack<>(); 
    private Stack<Pair<Float, Float>> redoStack = new Stack<>(); 

    private Bitmap originalBitmap; 
    private Bitmap maskedBitmap; 
    private Canvas originalCanvas; 
    private Canvas maskedCanvas; 
    private Paint paint; 

    private float drawRadius; 

    private StackListener listener; 

    public UndoCanvas(Context context) { 
     super(context); 

     init(); 
    } 

    public UndoCanvas(Context context, AttributeSet attrs) { 
     super(context, attrs); 

     init(); 
    } 

    public UndoCanvas(Context context, AttributeSet attrs, int defStyleAttr) { 
     super(context, attrs, defStyleAttr); 

     init(); 
    } 

    private void init() { 
     drawRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics()); 

     paint = new Paint(); 
     // paint.setColor(Color.RED); 

     paint.setAlpha(0); 
     paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); 
     paint.setAntiAlias(true); 
     paint.setMaskFilter(new BlurMaskFilter(15, BlurMaskFilter.Blur.SOLID)); 
    } 

    public void setBitmap(Bitmap bitmap) { 
     if (bitmap != null) { 
      originalBitmap = bitmap.copy(bitmap.getConfig(), true); // Copy of the original, because we will potentially make changes to this 
      maskedBitmap = originalBitmap.copy(originalBitmap.getConfig(), true); 
      originalCanvas = new Canvas(originalBitmap); 
      maskedCanvas = new Canvas(maskedBitmap); 
     } else { 
      originalBitmap = null; 
      originalCanvas = null; 
      maskedBitmap = null; 
      maskedCanvas = null; 
     } 

     int undoSize = undoStack.size(); 
     int redoSize = redoStack.size(); 

     undoStack.clear(); 
     redoStack.clear(); 

     invalidate(); 

     if (listener != null) { 
      if (undoSize != undoStack.size()) { 
       listener.onUndoStackChanged(undoSize, undoStack.size()); 
      } 
      if (redoSize != redoStack.size()) { 
       listener.onRedoStackChanged(redoSize, redoStack.size()); 
      } 
     } 
    } 

    public StackListener getListener() { 
     return listener; 
    } 

    public void setListener(StackListener listener) { 
     this.listener = listener; 
    } 

    public boolean onTouchEvent(MotionEvent ev) { 
     switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: { 
       int undoSize = undoStack.size(); 
       int redoSize = redoStack.size(); 

       // Max stack size. Remove oldest item before adding new 
       if (undoStack.size() == MAX_STACK_SIZE) { 
        // The undo history does not go further back, so make the change permanent by updating the original canvas/bitmap 
        Pair<Float, Float> pair = undoStack.remove(0); 
        maskPoint(originalCanvas, pair.first, pair.second); 
       } 

       undoStack.push(new Pair<>(ev.getX(), ev.getY())); 
       redoStack.clear(); 
       invalidate(); 

       if (listener != null) { 
        if (undoSize != undoStack.size()) { 
         listener.onUndoStackChanged(undoSize, undoStack.size()); 
        } 
        if (redoSize != redoStack.size()) { 
         listener.onRedoStackChanged(redoSize, redoStack.size()); 
        } 
       } 

       break; 
      } 

      case MotionEvent.ACTION_MOVE: { 
       int undoSize = undoStack.size(); 
       int redoSize = redoStack.size(); 

       // Max stack size. Remove oldest item before adding new 
       if (undoStack.size() == MAX_STACK_SIZE) { 
        // The undo history does not go further back, so make the change permanent by updating the original canvas/bitmap 
        Pair<Float, Float> pair = undoStack.remove(0); 
        maskPoint(originalCanvas, pair.first, pair.second); 
       } 

       maskPoint(maskedCanvas, ev.getX(), ev.getY()); 
       undoStack.push(new Pair<>(ev.getX(), ev.getY())); 
       redoStack.clear(); 
       invalidate(); 

       if (listener != null) { 
        if (undoSize != undoStack.size()) { 
         listener.onUndoStackChanged(undoSize, undoStack.size()); 
        } 
        if (redoSize != redoStack.size()) { 
         listener.onRedoStackChanged(redoSize, redoStack.size()); 
        } 
       } 
       break; 

      } 

      case MotionEvent.ACTION_UP: 
       invalidate(); 
       break; 

     } 
     return true; 
    } 

    @Override 
    protected void onDraw(Canvas canvas) { 
     if (maskedBitmap != null) { 
      canvas.drawBitmap(maskedBitmap, 0, 0, null); 
     } 
     super.onDraw(canvas); 

    } 

    public boolean undo() { 
     if (!undoStack.empty()) { 
      int undoSize = undoStack.size(); 
      int redoSize = redoStack.size(); 

      Pair<Float, Float> pair = undoStack.pop(); 
      // Redraw a single part of the original bitmap 
      //unmaskPoint(maskedCanvas, pair.first, pair.second); 

      // Redraw the original bitmap, along with all the points in the undo stack 
      remaskCanvas(maskedCanvas); 

      redoStack.push(pair); // Do not need to check for > 50 here, since redoStack can only contain what was in undoStack 
      invalidate(); 

      if (listener != null) { 
       if (undoSize != undoStack.size()) { 
        listener.onUndoStackChanged(undoSize, undoStack.size()); 
       } 
       if (redoSize != redoStack.size()) { 
        listener.onRedoStackChanged(redoSize, redoStack.size()); 
       } 
      } 

      return true; 
     } 

     return false; 
    } 

    public boolean redo() { 
     if (!redoStack.empty()) { 
      int undoSize = undoStack.size(); 
      int redoSize = redoStack.size(); 

      Pair<Float, Float> pair = redoStack.pop(); 
      maskPoint(maskedCanvas, pair.first, pair.second); 
      undoStack.push(pair); // Do not need to check for > 50 here, since redoStack can only contain what was in undoStack 
      invalidate(); 

      if (listener != null) { 
       if (undoSize != undoStack.size()) { 
        listener.onUndoStackChanged(undoSize, undoStack.size()); 
       } 
       if (redoSize != redoStack.size()) { 
        listener.onRedoStackChanged(redoSize, redoStack.size()); 
       } 
      } 

      return true; 
     } 

     return false; 
    } 

    private void maskPoint(Canvas canvas, float x, float y) { 
     if (canvas != null) { 
      canvas.drawCircle(x, y, drawRadius, paint); 
     } 
    } 

    private void unmaskPoint(Canvas canvas, float x, float y) { 
     if (canvas != null) { 
      Path path = new Path(); 
      path.addCircle(x, y, drawRadius, Path.Direction.CW); 

      canvas.save(); 
      canvas.clipPath(path); 
      canvas.drawBitmap(originalBitmap, 0, 0, new Paint()); 
      canvas.restore(); 
     } 
    } 

    private void remaskCanvas(Canvas canvas) { 
     if (canvas != null) { 
      canvas.drawBitmap(originalBitmap, 0, 0, new Paint()); 

      for (int i = 0; i < undoStack.size(); i++) { 
       Pair<Float, Float> pair = undoStack.get(i); 
       maskPoint(canvas, pair.first, pair.second); 
      } 
     } 
    } 

    public interface StackListener { 
     void onUndoStackChanged(int previousSize, int newSize); 

     void onRedoStackChanged(int previousSize, int newSize); 
    } 
} 

Vous souhaitez limiter la taille de ces pile de sorte qu'ils ne débordent pas comme utilisateur fait glisser à travers l'écran. Vous pouvez jouer avec le nombre, mais 50 semble être un bon début pour moi.

EDIT

Comme une note de côté, il pourrait être bon de refaire/annuler plusieurs entrées à la fois. Puisque onTouchEvent va déclencher des mouvements très fins. Les mouvements que l'utilisateur ne remarquerait pas lorsque vous appuyez sur Annuler/Refaire.

EDIT 2

J'ai ajouté à la mise en œuvre ci-dessus, pour gérer la undo ainsi. J'ai trouvé que la stratégie qui se redessine seulement au point spécifique est insuffisante car les points qui se chevauchent sont incorrects. (Le chevauchement des points A et B, la suppression de B entraîne l'effacement d'une sous-section de A). Pour cette raison, je remappe la totalité du bitmap sur une opération d'annulation, ce qui signifie qu'un bitmap intermédiaire est requis pour l'annulation. Sans l'image bitmap intermédiaire, l'opération d'annulation entraînera la suppression de points qui ne sont plus dans la pile (50 max). Comme nous ne prenons pas en charge l'annulation de ce point, l'utilisation de l'image bitmap d'origine en tant que bitmap intermédiaire est suffisante.

Les deux méthodes sont dans le code afin que vous puissiez tester les deux. Enfin, j'ai ajouté un écouteur pour permettre à l'activité de connaître l'état des piles. Pour activer/désactiver les boutons.

MainActivity

public class MainActivity extends AppCompatActivity { 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     final UndoCanvas canvas = (UndoCanvas) findViewById(R.id.undoCanvas); 
     final Button undoButton = (Button) findViewById(R.id.buttonUndo); 
     final Button redoButton = (Button) findViewById(R.id.buttonRedo); 
     undoButton.setEnabled(false); 
     redoButton.setEnabled(false); 

     canvas.setListener(new UndoCanvas.StackListener() { 
      @Override 
      public void onUndoStackChanged(int previousSize, int newSize) { 
       undoButton.setEnabled(newSize > 0); 
      } 

      @Override 
      public void onRedoStackChanged(int previousSize, int newSize) { 
       redoButton.setEnabled(newSize > 0); 
      } 
     }); 

     undoButton.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       canvas.undo(); 
      } 
     }); 

     redoButton.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       canvas.redo(); 
      } 
     }); 

     canvas.setBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.image)); 
    } 
} 

Captures d'écran

Avant Annuler

Screen before undo operation(s)

Après Annuler

Screen after undo operation(s)

+0

Le code ne restaure pas la partie de bitmap qui est effacée lorsque je clique sur undo.Voulez-vous voir la question à nouveau pour examiner le détail ajouté juste de la mPaint et pcanvas et suggérer où je vais mal? – zek54

+0

Ce n'était pas dans le message original. Vous pouvez conserver le bitmap d'origine et le dessiner à nouveau avant d'appliquer le masque. Ou pour améliorer la performance, dessinez seulement la région affectée (qui serait maintenant dans le redoStack) – Kevin

+0

Bonjour Kevin, je n'ai pas pu ramener le bitmap après effacement et j'ai aussi ajouté un lien dans la question expliquant mon problème avec la solution à condition de. Si vous pouviez m'aider, ce serait génial. – zek54