2013-06-09 8 views
5

Ich habe diesen Code aus einer Antwort in einer der Fragen, die Frage, wie in Android zu zeichnen, aber dann bei der Verwendung und Testen in meiner App fand ich heraus, dass es nicht effizient ist, wenn große Dinge oder viele Wege zeichnen. Das Problem kommt vom Code innerhalb onDraw, weil jedes invalidate() genannt wird, das eine Schleife enthält, die alle paths wieder zum canvas zeichnet, und indem es mehr Wege ihm hinzufügt, wird es sehr sehr langsam. HierAndroid Drawing View ist sehr langsam

ist die Klasse:

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

Aber ich im Internet gesucht wieder einen besseren Weg zum Zeichnen zu finden, so fand ich folgende:

1 Fügen Sie den folgenden an den Konstruktor:

mBitmapPaint = new Paint(Paint.DITHER_FLAG); 

2 Aufschalten OnSizeChanged mit dem folgenden Code:

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 Setzen Sie diese 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); 
} 

Dieser Ansatz funktioniert und es nicht verlangsamt die Ansicht, aber das Problem mit diesem Ansatz ist, dass ich nicht rückgängig machen kann und Redo-Funktionen.

Ich habe viele verschiedene Dinge ausprobiert, um das Rückgängigmachen und Wiederholen mit dem zweiten Ansatz durchzuführen, aber ich konnte es nicht tun. Also, was ich hier frage, ist eines von drei Dingen: 1. Eine Möglichkeit, rückgängig zu machen und wiederholen mit dem zweiten Ansatz 2. Ein anderer Ansatz, der es ermöglicht, rückgängig machen und wiederholen 3. Eine ganz neue Klasse, die hat alles bereits erledigt, wie eine Open-Source-Bibliothek oder so.

Bitte helfen Sie, wenn Sie können. Dank

EDIT 1

OK, so beschränken ich es so weit nach unten und dann kann ich nichts mehr, ich habe jetzt über 8 Stunden versucht. Es funktioniert bis zum Rückgängigmachen (Sie können so viele Pfade rückgängig machen, wie Sie möchten), und wenn Sie dann erneut zeichnen, verschwinden alle verbleibenden Pfade, ich weiß nicht, was es dazu bringt.

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

so im Grunde, was ich ist, den ersten Ansatz zunächst verwenden hat (vor Undo genannt wird), dann, wenn Undo geklickt wird, wird undo auf true und der Code unter if (undo) ausgeführt, der tatsächlich der erste Ansatz ist (Berechnung aller Pfade erneut), dann zeichne ich das Ergebnis der Berechnung aller Pfade wieder in mBitmap, also wenn immer wieder die aufgerufen wird, zieht es sich an, aber dieser Teil muss noch funktionieren, ich hoffe jemand kann mit diesem Teil helfen.

+0

Ich habe Angst zu sagen, dass, wenn Sie so viele Pfade zeichnen, dann Ihre Ansicht wird langsam sein. Sie sollten versuchen, eine Methode zu finden, bei der ein wirklich langer Pfad gezeichnet wird. – jcw

+0

Wenn Sie die neuen Pfade immer auf die alten zeichnen, können Sie beide Methoden kombinieren, wobei alle außer den letzten n Pfaden in die Bitmap eingefügt werden, und das letzte n mit der vorherigen Methode zeichnen. Dann können Sie diese letzten mindestens auf rückgängig machen –

Antwort

0

Ich bin mir nicht sicher, ob dies der beste Weg zum Rückgängigmachen und Wiederherstellen ist. Allerdings funktionierte das unten auf meinem Gerät (Samsung Galaxy S3). Das Draw scheint schnell zu sein und das Rückgängigmachen funktioniert gut. Ich denke, dass das Folgende modifiziert werden kann, um die Leistung weiter zu verbessern.

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

Dies ist immer noch das gleiche Leistungsproblem in meiner Frage beschrieben. Aber trotzdem danke. –

+0

@AmjadAbuSaa auf welchem ​​Gerät testen Sie? – Raghunandan

+0

S3, Note 10.1, und der Emulator S3 ist der schnellste wie du gesagt hast, aber wir sollten immer noch langsamere Geräte in Betracht ziehen, wenn wir Apps machen, oder? –

5

Die Möglichkeit, einen solchen Fall zu behandeln, ist ein Bitmap, das die Größe der Ansicht hat. Bei Berührungsereignissen zeichnen Sie in die Zeichenfläche der Bitmap. Zeichnen Sie in onDraw die Bitmap einfach in die Zeichenfläche bei 0,0. Zum Rückgängigmachen/Wiederholen. Sie können die Bitmap löschen und alle Pfade neu zeichnen. Es dauert etwas länger, aber es passiert nur einmal pro Undo/Redo. Wenn Benutzer in der Regel ein Undo/Redo machen. Sie können optimieren, indem Sie ein anderes Bitmap für nur einen Schritt zurück haben.

+0

können Sie bitte die Bearbeitung überprüfen? Vielen Dank für Ihre Antwort –

+0

Nein, immer noch ein Problem, bitte sehen Sie es an –

+0

Ein paar Dinge: auf rückgängig machen, löschen Sie die Bitmap, so dass die nächste onDraw eine leere Bitmap haben wird. Ich bin mir nicht sicher, ob/wohin du alle Pfade in die Bitmap rückgängig machst. Tun Sie es an der Stelle, an der Sie den Rückgängig-Klick ausführen, damit Sie sich nicht um das Rückgängigmachen von onDraw kümmern müssen. Eine weitere Optimierung besteht in der Verwendung eines einzigen langen Pfads anstelle vieler kleiner Pfade. – yoah

1

Ok, hier ist das, was ich mit am Ende kam, war das Problem, dass ich die Pfade auf die Leinwand ziehen, bevor Sie die Bitmap auf Undo zu schaffen, die zu einem Verlust der Wege führen OnDraw nach rückgängig machen:

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

Alle Pfade immer wieder zeichnen ist immer noch da, aber nicht in onDraw(), was die Leistung der Zeichnung sehr verbessert. Aber der Benutzer kann wenig Verzögerung in onClickUndo() und onClickRedo() erfahren, wenn er viele Pfade gezeichnet hat, weil dort die Pfade von Grund auf neu gezeichnet werden, aber nur einmal pro Klick.