2013-06-09 22 views
5

Androidでの描画方法を尋ねていた質問の1つで、このコードを回答したが、アプリで使用してテストしたときに効率が悪いことがわかった大きなものや多くの道を描く。この問題はonDraw内のコードから発生します。invalidate()が呼び出され、onDrawが呼び出され、すべてpathsを再びcanvasに描画するループが含まれており、さらに多くのパスを追加すると非常に遅くなるためです。Android図面ビューが非常に遅い

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

しかし、私は、描画のためのより良い方法を見つけるために、もう一度、インターネット上で検索、私は、次を発見した:ここで

はクラスである

1コンストラクタに以下を追加します。

mBitmapPaint = new Paint(Paint.DITHER_FLAG); 

2次のコードでonSizeChanged上書き:

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

をこのアプローチは動作し、それが表示が遅くなることはありませんが、このアプローチの問題は、私はアンドゥを持っや機能をやり直すことができないということです。

2番目の方法で元に戻したりやり直したりするのに多くのことを試みましたが、できませんでした。 2.を元に戻すと、第二のアプローチでやり直し元に戻す操作を行うと 3.持つまったく新しいクラスをやり直すことを可能にする別のアプローチを行うには 1.方法:だから私はここに求めていることの3つのうちの1つですオープンソースのライブラリなどのように、すでに完了しているすべてのもの。

できるだけお手伝いをしてください。 おかげ

EDIT 1

OK、私はこれまでそれを制限して、私はより多くの何もできなかった、私は今、8時間以上しようとしています。元に戻すまで機能します(必要なだけ多くのパスを元に戻すことができます)。残りのすべてのパスが再び描画されると、それが何をするのかわかりません。したがって、基本的に、私は何をしたか

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

は、(アンドゥが呼び出される前に)最初に第一のアプローチを使用して元に戻すがクリックされた場合、その後、実際に最初のアプローチであるundotrueに設定され、if (undo)下コードが実行されますonDrawが再び呼び出されるたびので、それはその上に描画しますが、その部分はまだ働いて必要とされ、その後、私はmBitmapに再びすべてのパスを計算した結果を描画し、(再びすべてのパスを計算する)、私は誰かがその一部を支援することができます願っています。

+0

あなたが非常に多くの道を描くと、あなたの意見は遅くなると言われるのは恐ろしいことです。あなたは本当に長い道のりを描く方法を見つけるべきです。 – jcw

+0

新しいパスを常に古いパスの上に描画する場合は、最後のnパス以外のすべてのパスをビットマップに結合し、最後のnを前のメソッドで描画する両方の方法を組み合わせることができます。その後、少なくともこれらの最後のnを取り消すことができます –

答えて

0

私は、これは元に戻すとやり直しのための最善の方法であるかどうかわからないです。しかし、以下は私のデバイス(サムスン銀河s3)で働いた。ドローは速く、アンドゥはうまくいくようです。私はパフォーマンスをさらに向上させるために以下を修正できると思います。

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

これは私の質問に記載されているのと同じパフォーマンスの問題がまだあります。しかし、とにかくありがとう。 –

+0

@AmjadAbuSaaどのデバイスをテストしていますか? – Raghunandan

+0

S3、ノート10.1、エミュレータ S3は、あなたが述べたように最も速いですが、アプリの作成中にデバイスを遅くする必要があります。 –

5

このようなケースを処理する方法は、ビューのサイズを持つビットマップを持つことです。タッチイベントでは、ビットマップのキャンバスに描画します。 onDrawでビットマップを0,0のキャンバスに描画します。取り消し/やり直しの場合。ビットマップを消去してすべてのパスを再描画することができます。もう少し時間がかかりますが、取り消し/やり直しごとに1回しか発生しません。 ユーザーが通常は元に戻す/やり直しを1回だけ行う場合。 1つのステップだけ戻って別のビットマップを戻すことによって最適化することができます。

+0

編集を確認できますか? 返信ありがとうございました –

+0

いいえ、まだ問題があります。それを見てください –

+0

いくつかのことがあります:元に戻すとビットマップがクリアされ、次のonDrawは空のビットマップになります。あなたが元に戻すときにすべてのパスをビットマップに描画するかどうかわかりません。元に戻すクリックを処理する場所で行います。この方法で、onDrawで元に戻すことを心配する必要はありません。最適化するもう1つのことは、他のものが示唆しているように、多くの小さなパスの代わりに単一の長いパスを使用することです。 – yoah

1

[OK]を、ここで私が最後に思い付いたものです、問題は、私は後に元に戻すonDrawパスの損失につながるアンドゥ上のビットマップを作成する前にキャンバスへのパスを描くことだった:

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

すべてのパスを何度も描画することはまだありますが、onDraw()にはありません。描画のパフォーマンスが非常に向上します。 しかし、多くのパスを描画している場合は、パスがゼロから再び描画されるため、1回のクリックで1回だけ描​​画されるため、ユーザーはonClickUndo()onClickRedo()で少し遅延が発生する可能性があります。