2013-06-09 11 views
5

Ho ottenuto questo codice da una risposta in una delle domande che chiedevano come disegnare in Android, ma poi quando lo usavo e lo testavo nella mia app, ho scoperto che non è efficiente quando disegnare grandi cose o molti percorsi. Il problema deriva dal codice onDraw perché ogni volta invalidate() viene chiamato onDraw viene chiamato che contiene un ciclo che richiama tutto paths nuovamente nello canvas e aggiungendo più percorsi ad esso, diventa molto molto lento.Visualizzazione disegno Android è molto lenta

Qui è la classe:

public class DrawingView extends View implements OnTouchListener { 
private Canvas m_Canvas; 

private Path m_Path; 

private Paint m_Paint; 

ArrayList<Pair<Path, Paint>> paths = new ArrayList<Pair<Path, Paint>>(); 

ArrayList<Pair<Path, Paint>> undonePaths = new ArrayList<Pair<Path, Paint>>(); 

private float mX, mY; 

private static final float TOUCH_TOLERANCE = 4; 

public static boolean isEraserActive = false; 

private int color = Color.BLACK; 
private int stroke = 6; 

public DrawingView(Context context, AttributeSet attr) { 
    super(context); 
    setFocusable(true); 
    setFocusableInTouchMode(true); 

    setBackgroundColor(Color.WHITE); 

    this.setOnTouchListener(this); 

    onCanvasInitialization(); 
} 

public void onCanvasInitialization() { 
    m_Paint = new Paint(); 
    m_Paint.setAntiAlias(true); 
    m_Paint.setDither(true); 
    m_Paint.setColor(Color.parseColor("#000000")); 
    m_Paint.setStyle(Paint.Style.STROKE); 
    m_Paint.setStrokeJoin(Paint.Join.ROUND); 
    m_Paint.setStrokeCap(Paint.Cap.ROUND); 
    m_Paint.setStrokeWidth(2); 

    m_Canvas = new Canvas(); 

    m_Path = new Path(); 
    Paint newPaint = new Paint(m_Paint); 
    paths.add(new Pair<Path, Paint>(m_Path, newPaint)); 
} 

@Override 
public void setBackground(Drawable background) { 
    mBackground = background; 
    super.setBackground(background); 
} 

@Override 
protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
    super.onSizeChanged(w, h, oldw, oldh); 
} 

public boolean onTouch(View arg0, 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; 
} 

@Override 
protected void onDraw(Canvas canvas) { 
    for (Pair<Path, Paint> p : paths) { 
     canvas.drawPath(p.first, p.second); 
    } 
} 

private void touch_start(float x, float y) { 

    if (isEraserActive) { 
     m_Paint.setColor(Color.WHITE); 
     m_Paint.setStrokeWidth(50); 
     Paint newPaint = new Paint(m_Paint); // Clones the mPaint object 
     paths.add(new Pair<Path, Paint>(m_Path, newPaint)); 
    } else { 
     m_Paint.setColor(color); 
     m_Paint.setStrokeWidth(stroke); 
     Paint newPaint = new Paint(m_Paint); // Clones the mPaint object 
     paths.add(new Pair<Path, Paint>(m_Path, newPaint)); 
    } 

    m_Path.reset(); 
    m_Path.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) { 
     m_Path.quadTo(mX, mY, (x + mX)/2, (y + mY)/2); 
     mX = x; 
     mY = y; 
    } 
} 

private void touch_up() { 
    m_Path.lineTo(mX, mY); 

    // commit the path to our offscreen 
    m_Canvas.drawPath(m_Path, m_Paint); 

    // kill this so we don't double draw 
    m_Path = new Path(); 
    Paint newPaint = new Paint(m_Paint); // Clones the mPaint object 
    paths.add(new Pair<Path, Paint>(m_Path, newPaint)); 
} 

public void onClickUndo() { 
    if (!paths.isEmpty()) {//paths.size() > 0) { 
     undonePaths.add(paths.remove(paths.size() - 1)); 
     undo = true; 
     invalidate(); 
    } 
} 

public void onClickRedo() { 
    if (!undonePaths.isEmpty()){//undonePaths.size() > 0) { 
     paths.add(undonePaths.remove(undonePaths.size() - 1)); 
     undo = true; 
     invalidate(); 
    } 
}} 

Ma ho cercato su internet di nuovo per trovare un modo migliore per disegnare, così ho trovato la seguente:

1 Aggiungere il seguente al costruttore:

mBitmapPaint = new Paint(Paint.DITHER_FLAG); 

2 Override onSizeChanged con il codice seguente:

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_4444); 
    m_Canvas = new Canvas(mBitmap); 
} 

3 mettere questo in OnDraw:

protected void onDraw(Canvas canvas) { 
    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); 
    if (!paths.isEmpty()) 
     canvas.drawPath(paths.get(paths.size() - 1).first, paths.get(paths.size() - 1).second); 
} 

Questo approccio funziona e non rallenta la vista, ma il problema di questo approccio è che non posso avere undo e redo funzionalità.

Ho provato molte cose per annullare e ripetere con il secondo approccio, ma non potevo farlo. Quindi, quello che sto chiedendo qui è una delle tre cose: 1. Un modo per annullare e ripristinare il secondo approccio 2. Un altro approccio che rende possibile annullare e ripristinare 3. Una classe completamente nuova che ha tutto già fatto, come una libreria open source o qualcosa del genere.

Si prega di aiutare se è possibile. Grazie

EDIT 1

OK, quindi ho limitato giù a questo e quindi non ho potuto fare qualcosa di più, ho cercato per più di 8 ore ormai. Funziona fino all'annullamento (puoi annullare tutti i percorsi che vuoi), poi quando si disegnano di nuovo tutti i percorsi rimanenti scompaiono, non so cosa lo faccia fare.

@Override 
protected void onDraw(Canvas canvas) { 
    if (mBitmap != null) 
     canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); 
    if (!paths.isEmpty() && !undo) 
     canvas.drawPath(paths.get(paths.size() - 1).first, paths.get(paths.size() - 1).second); 

    if (undo) { 
     setBackground(mBackground); 
     for (Pair<Path, Paint> p : paths) 
      canvas.drawPath(p.first, p.second); 

     mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444); 
     m_Canvas = new Canvas(mBitmap); 

     undo = false; 
    } 
} 

quindi fondamentalmente quello che ho fatto è utilizzare il primo approccio in un primo momento (prima di undo viene chiamato), allora se undo viene cliccato, undo è impostato su true e viene eseguito il codice sotto if (undo) che è in realtà il primo approccio (calcolando di nuovo tutti i percorsi), quindi disegno il risultato del calcolo di tutti i percorsi di nuovo in mBitmap così ogni volta che viene richiamato lo onDraw, ma quella parte deve ancora funzionare, spero che qualcuno possa aiutare con quella parte.

+0

Ho paura di dire che se si tracciano così tanti percorsi, la visualizzazione sarà lenta. Dovresti provare a trovare un metodo che comporta il tracciamento di un percorso veramente lungo. – jcw

+0

Se si disegna sempre i nuovi percorsi sopra quelli vecchi, è possibile combinare entrambi i metodi combinando tutti gli ultimi n percorsi nella Bitmap e disegnare l'ultima n con il metodo precedente. Quindi è possibile rimuovere almeno questi ultimi n per annullare –

risposta

0

Non sono sicuro se questo è il modo migliore per annullare e ripristinare. Tuttavia il sotto ha funzionato sul mio dispositivo (Samsung Galaxy s3). Il Draw sembra essere veloce e l'annullamento funziona bene. Penso che il seguito possa essere modificato per migliorare ulteriormente le prestazioni.

public class MainActivity extends Activity { 
MyView mv; 
LinearLayout ll; 
private ArrayList<Path> undonePaths = new ArrayList<Path>(); 
private ArrayList<Path> paths = new ArrayList<Path>(); 
Button b; 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     mv= new MyView(this); 
      mv.setDrawingCacheEnabled(true); 
      ll= (LinearLayout) findViewById(R.id.ll); 
      ll.addView(mv); 
      b= (Button) findViewById(R.id.button1); 
      b.setOnClickListener(new OnClickListener() 
      { 
       @Override 
       public void onClick(View v) { 
        // TODO Auto-generated method stub 
        if (paths.size() > 0) { 
         undonePaths.add(paths 
           .remove(paths.size()-2)); 
         mv.invalidate(); 
        } 
       } 

      }); 

    } 
    public class MyView extends View implements OnTouchListener { 

      private Canvas mCanvas; 
      private Path mPath; 
      private Paint mPaint; 

      // private ArrayList<Path> undonePaths = new ArrayList<Path>(); 
      private float xleft, xright, xtop, xbottom; 

      public MyView(Context context) { 
       super(context); 
       setFocusable(true); 
       setFocusableInTouchMode(true); 
       this.setOnTouchListener(this); 
       mPaint = new Paint(); 
       mPaint.setAntiAlias(true); 
       mPaint.setColor(Color.RED); 
       mPaint.setStyle(Paint.Style.STROKE); 
       mPaint.setStrokeJoin(Paint.Join.ROUND); 
       mPaint.setStrokeCap(Paint.Cap.ROUND); 
       mPaint.setStrokeWidth(6); 
       mCanvas = new Canvas(); 
       mPath = new Path(); 
       paths.add(mPath); 
      } 

      @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: 

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

activity_main.XML

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:orientation="vertical" > 

    <LinearLayout 
     android:id="@+id/ll" 
     android:layout_width="match_parent" 
     android:layout_height="fill_parent" 
     android:layout_weight="1" 
     android:orientation="vertical" > 

</LinearLayout> 

<Button 
    android:id="@+id/button1" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:layout_gravity="center" 
    android:text="Undo" /> 

</LinearLayout> 
+0

Questo ha ancora lo stesso problema di prestazioni descritto nella mia domanda. Ma grazie comunque. –

+0

@AmjadAbuSaa su quale dispositivo testate? – Raghunandan

+0

S3, Nota 10.1, e l'emulatore S3 è il più veloce come hai detto, ma dovremmo considerare i dispositivi più lenti mentre creiamo app, vero? –

5

Il modo per gestire un caso è quello di avere una bitmap che ha le dimensioni della vista. Sugli eventi tattili, disegnare nella tela della bitmap. in onDraw, basta disegnare la bitmap nella tela a 0,0. Per annullare/ripristinare ,. puoi cancellare la bitmap e ridisegnare tutti i percorsi. Ci vuole un po 'più di tempo, ma succede solo una volta per annullare/ripristinare. Se gli utenti in genere eseguono un annullamento/ripristino. puoi ottimizzare avendo un'altra bitmap per un solo passo indietro.

+0

puoi controllare la modifica, per favore? Grazie per la tua risposta –

+0

no, c'è ancora un problema, per favore guardalo –

+0

Alcune cose: all'annullamento, cancelli la bitmap, quindi la prossima suDraw avrà una bitmap vuota. Non sono sicuro se/dove disegni tutti i percorsi in undo nella bitmap. Fallo nel punto in cui gestisci il clic di annullamento, in questo modo non devi preoccuparti di annullare in onDraw. Un'altra cosa da ottimizzare, come altri hanno suggerito, è quella di utilizzare un singolo percorso lungo anziché molti piccoli. – yoah

1

Ok, ecco cosa mi è venuta alla fine, il problema era che traggo i percorsi per la tela prima di creare la bitmap su undo, che ha portato alla perdita dei percorsi OnDraw dopo l'annullamento:

@Override 
    protected void onDraw(Canvas canvas) { 
     if (mBitmap != null) 
      canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); 
     if (!paths.isEmpty()) { 
      canvas.drawPath(paths.get(paths.size() - 1).first, paths.get(paths.size() - 1).second); 
     } 
    } 

    public void onClickUndo() { 
     if (paths.size() >= 2) { 
      undonePaths.add(paths.remove(paths.size() - 2)); 
      mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 
      m_Canvas = new Canvas(mBitmap); 

      for (Pair<Path, Paint> p : paths) 
       m_Canvas.drawPath(p.first, p.second); 
      invalidate(); 
     } 
    } 

    public void onClickRedo() { 
     if (undonePaths.size() >= 2){ 
      paths.add(undonePaths.remove(undonePaths.size() - 2)); 
      mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 
      m_Canvas = new Canvas(mBitmap); 

      for (Pair<Path, Paint> p : paths) 
       m_Canvas.drawPath(p.first, p.second); 
      invalidate(); 
     } 
    } 

Disegnare tutti i percorsi più e più volte è ancora lì ma non in onDraw(), che migliora molto le prestazioni del disegno. Ma l'utente potrebbe sperimentare un po 'di ritardo in onClickUndo() e onClickRedo() se ha disegnato molti percorsi perché là dove i tracciati vengono disegnati di nuovo da zero, ma solo una volta per clic.

Problemi correlati