6

我从一个询问如何在 Android 中绘图的问题的答案中得到了这段代码,但是当我在我的应用程序中使用它并测试它时,我发现它在绘制大东西或许多路径时效率不高。问题来自内部代码,onDraw因为每次invalidate()调用onDraw都包含一个循环,该循环将所有内容paths再次绘制到canvas,并且通过向其添加更多路径,它变得非常非常慢。

这是课程:

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

这种方法有效并且不会减慢视图的速度,但这种方法的问题是我不能拥有撤消和重做功能。

我尝试了很多方法来使用第二种方法进行撤消和重做,但我做不到。所以我在这里要问的是三件事之一: 1. 使用第二种方法进行撤消和重做的方法 2. 另一种可以进行撤消和重做的方法 3. 一个已经完成所有事情的全新类,比如开源库什么的。

如果可以的话请帮忙。谢谢

编辑 1

好的,所以我把它限制在这个范围内,然后我不能再做任何事情了,我已经尝试了 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;
    }
}

所以基本上我所做的是首先使用第一种方法(在调用撤消之前),然后如果单击撤消,undo则设置为true并执行下面的代码,if (undo)这实际上是第一种方法(再次计算所有路径),然后我绘制再次计算所有路径的结果,mBitmap因此每当onDraw再次调用它时,它都会在此基础上绘制,但该部分仍需要工作,我希望有人可以帮助解决该部分。

4

3 回答 3

5

处理这种情况的方法是拥有一个具有视图大小的位图。在触摸事件上,绘制到位图的画布中。在 onDraw 中,只需在 0,0 处将位图绘制到画布中。对于撤消/重做,。您可以擦除位图并重新绘制所有路径。它需要更长的时间,但每次撤消/重做只会发生一次。如果用户通常执行一次撤消/重做。您可以通过让另一个位图退后一步来进行优化。

于 2013-06-09T16:29:40.090 回答
1

Ok, here is what I came up with at the end, the problem was that I draw the paths to the canvas before creating the bitmap on undo, which lead to loss of the paths onDraw after undo:

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

Drawing all paths again and again is still there but not in onDraw(), which improves the performance of drawing quite very much. But the user might experience little bit of delay in onClickUndo() and onClickRedo() if he has drawn a lot of paths because there where the paths are getting drawn again from scratch, but just one time per click.

于 2013-06-15T14:53:06.993 回答
0

我不确定这是否是撤消和重做的最佳方式。但是,以下内容在我的设备(三星 Galaxy 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>
于 2013-06-09T16:26:42.100 回答