2013-06-09 15 views
5

Dostałem ten kod z odpowiedzi na jedno z pytań, które pytało, jak narysować w systemie Android, ale kiedy go użyłem i przetestowałem w mojej aplikacji, okazało się, że nie jest ono wydajne rysowanie dużych rzeczy lub wielu ścieżek. Problem pochodzi z kodu wewnątrz onDraw, ponieważ za każdym razem invalidate() nazywa się onDraw jest wywoływana, która zawiera pętlę, która ponownie rysuje wszystkie paths do canvas, a dodając więcej ścieżek do niego, staje się bardzo powolna.Widok rysunku Androida jest bardzo powolny.

Oto Klasa:

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

Ale szukałem w internecie ponownie znaleźć lepszy sposób na rysunek, więc znalazłem następujące:

1 Dodaj poniższe linie do konstruktora:

mBitmapPaint = new Paint(Paint.DITHER_FLAG); 

2 Zastąpienie onSizeChanged następującym kodem:

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 umieścić to w 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); 
} 

Podejście to działa i nie spowalnia pogląd, ale problem w tym, że nie mogę mieć cofania i ponawiania funkcjonalności.

Próbowałem wielu rzeczy, aby cofnąć i przerobić z drugim podejściem, ale nie mogłem tego zrobić. To, o co tutaj pytam, to jedna z trzech rzeczy: 1. Sposób na cofanie i ponawianie z drugim podejściem 2. Inne podejście, które umożliwia cofanie i ponawianie 3. Cała nowa klasa, która ma wszystko już zrobione, jak biblioteka open source czy coś takiego.

Pomóż, jeśli możesz. Dzięki

EDIT 1

OK, więc ogranicza ją do tego i wtedy nie mogłem zrobić nic więcej, staram ponad 8 godzin teraz. Działa aż do cofnięcia (możesz cofnąć dowolną liczbę ścieżek), a gdy rysujesz ponownie wszystkie pozostałe ścieżki znikają, nie wiem, co to powoduje.

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

więc w zasadzie to, co zrobiłem, to użyć pierwsze podejście na początku (przed cofanie się nazywa), a następnie, jeśli cofanie kliknięciu undo jest ustawiony na true i kod pod if (undo) jest wykonywany co jest faktycznie pierwszym podejściem (obliczając ponownie wszystkie ścieżki), następnie narysuję wynik obliczenia wszystkich ścieżek ponownie na mBitmap, więc zawsze, gdy onDraw jest wywoływany ponownie, rysuje się na wierzchu, ale ta część wciąż wymaga pracy, mam nadzieję, że ktoś może pomóc z tą częścią.

+0

Obawiam się powiedzieć, że jeśli narysujesz tyle ścieżek, twój widok będzie powolny. Powinieneś spróbować znaleźć metodę, która pociągnie za sobą jedną naprawdę długą ścieżkę. – jcw

+0

Jeśli zawsze narysujesz nowe ścieżki na starszych, możesz połączyć obie metody, łącząc wszystkie z wyjątkiem ostatnich n ścieżek do Bitmapy i narysować ostatnią n za pomocą poprzedniej metody. Następnie możesz przynajmniej usunąć te ostatnie n przy cofaniu –

Odpowiedz

0

Nie jestem pewien, czy jest to najlepszy sposób na cofanie i ponawianie. Jednak poniżej działało na moim urządzeniu (Samsung galaxy s3). Losowanie wydaje się szybkie, a cofanie działa dobrze. Myślę, że poniższe elementy można zmodyfikować, aby jeszcze bardziej zwiększyć wydajność.

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

To nadal ma ten sam problem wydajności opisany w moim pytaniu. Ale i tak dziękuję. –

+0

@AmjadAbuSaa na jakim urządzeniu testujesz? – Raghunandan

+0

S3, Uwaga 10.1 i emulator S3 jest najszybszy, jak stwierdzono, ale nadal powinniśmy rozważyć wolniejsze urządzenia podczas robienia aplikacji, prawda? –

5

Sposób postępowania z takim przypadkiem polega na utworzeniu mapy bitowej o rozmiarze widoku. W przypadku zdarzeń dotyku narysuj na płótnie mapy bitowej. w onDraw, po prostu narysuj bitmapę na płótnie o 0,0. Do cofania/ponawiania. możesz wymazać mapę bitową i ponownie narysować wszystkie ścieżki. Trochę potrwa to dłużej, ale zdarza się tylko raz na cofnięcie/ponowienie. Jeśli użytkownicy zwykle wykonują jedno cofnięcie/ponowienie. możesz zoptymalizować, mając inną mapę bitową tylko o jeden krok wstecz.

+0

Czy możesz sprawdzić Edycję, proszę? Dzięki za odpowiedź –

+0

nie, wciąż jest problem, proszę spojrzeć na to –

+0

Kilka rzeczy: po cofnięciu, wyczyścisz bitmapę, więc następny onDraw będzie miał pustą bitmapę. Nie jestem pewien, czy/gdzie narysujesz wszystkie ścieżki w cofnięciu do mapy bitowej. Zrób to w miejscu, w którym obsługujesz cofanie, w ten sposób nie musisz martwić się cofaniem w onDraw. Jeszcze jedna rzecz do zoptymalizowania, jak sugerowali inni, to użycie pojedynczej długiej ścieżki zamiast wielu małych. – yoah

1

Ok, oto co wymyśliłem w końcu, problemem było to, że rysuję ścieżki do płótna przed utworzeniem bitmapę na undo, które prowadzą do utraty ścieżek OnDraw po cofnąć:

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

Rysowanie wszystkich ścieżek wciąż tam jest, ale nie w onDraw(), co znacznie poprawia wydajność rysowania. Ale użytkownik może doświadczyć niewielkiego opóźnienia w onClickUndo() i onClickRedo(), jeśli wytyczył wiele ścieżek, ponieważ tam, gdzie ścieżki są rysowane ponownie od nowa, ale tylko raz na kliknięcie.

Powiązane problemy