1

我有 3 个圆圈的窗口,它们同时旋转。一切都很好,直到向圆圈添加文本,然后旋转开始滞后。

主窗口

如何优化画布上的绘图?这是我的代码:

@Override
protected void onDraw(final Canvas canvas) {
    if (mPaint == null) {
        mPaint = new Paint();
        mPaint.setTextSize(20f);
    }       
    drawUpperCircle(canvas);
    drawBottomCircle(canvas);
    drawMainCircle(canvas);

    try {
        Thread.sleep(1, 1);
        invalidate();
        mRotation += 0.9;
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    super.onDraw(canvas);
}
   private void drawUpperCircle(Canvas canvas) {
    canvas.save();
    canvas.rotate(mRotation, 0, mUpperCircleCentr);
    mPaint.setColor(Color.CYAN);
    canvas.drawCircle(0, mUpperCircleCentr, mUpperCirclRadius, mPaint);
    mPaint.setColor(Color.BLACK);
    for (int i = 0; i < SEG_COUNT; i++) {
        canvas.rotate(SEG_IN_GRAD, 0, mUpperCircleCentr);
        canvas.drawLine(0, mUpperCircleCentr, mUpperCirclRadius, mUpperCircleCentr, mPaint);
        //          canvas.drawText("my text" + String.valueOf(i), mUpperCirclRadius * 2 / 3, mUpperCircleCentr - 4, mPaint);
    }
    canvas.restore();
}

private void drawBottomCircle(Canvas canvas) {
    canvas.save();
    canvas.rotate(mRotation, 0, mBottomCircleCentr);
    mPaint.setColor(Color.RED);
    canvas.drawCircle(0, mBottomCircleCentr, mBottomCirclRadius, mPaint);
    mPaint.setColor(Color.BLACK);
    for (int i = 0; i < SEG_COUNT; i++) {
        canvas.rotate(SEG_IN_GRAD, 0, mBottomCircleCentr);
        canvas.drawLine(0, mBottomCircleCentr, mBottomCirclRadius, mBottomCircleCentr, mPaint);
        //          canvas.drawText("my text" + String.valueOf(i), mBottomCirclRadius * 2 / 3, mBottomCircleCentr - 4, mPaint);
    }
    canvas.restore();
}

private void drawMainCircle(Canvas canvas) {
    canvas.save();
    canvas.rotate(mRotation, 0, mMainCircleCentr);
    mPaint.setColor(Color.argb(100, 100, 100, 100));
    canvas.drawCircle(0, mMainCircleCentr, mMainCirclRadius, mPaint);
    mPaint.setColor(Color.BLACK);
    for (int i = 0; i < SEG_COUNT; i++) {
        canvas.rotate(SEG_IN_GRAD, 0, mMainCircleCentr);
        canvas.drawLine(0, mMainCircleCentr, mMainCirclRadius, mMainCircleCentr, mPaint);
        canvas.drawText("my text" + String.valueOf(i), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
    }
    canvas.restore();
}

编辑 为了提高性能并从 UI 线程中删除绘图,我使用了双缓冲SurfaceView并实现了 @Morgans 优化。它就是这样实现的。

绘图视图.java

public class DrawView extends SurfaceView implements SurfaceHolder.Callback {

...............................................................

public DrawView(Context context, AttributeSet attrs) {
    super(context, attrs);
    getHolder().addCallback(this);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    float currentX = event.getX();
    float currentY = event.getY();
    float deltaX, deltaY;
    switch (event.getAction()) {
    case MotionEvent.ACTION_MOVE:
        // Modify rotational angles according to movement
        deltaX = currentX - previousX;
        deltaY = currentY - previousY;
        mDrawThread.mRotation += deltaY * 180 / getHeight();
    }
    // Save current x, y
    previousX = currentX;
    previousY = currentY;
    return true; // Event handled
}

@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {

}

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    mDrawThread = new DrawThread(getHolder(), this);
    mDrawThread.setRunning(true);
    mDrawThread.start();
}

@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    boolean retry = true;
    mDrawThread.setRunning(false);
    while (retry) {
        try {
            mDrawThread.join();
            retry = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 }

并且主要工作是在DrawThread.java中完成的

public class DrawThread extends Thread {

private ArrayList<Path> mMainCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mUpperCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mCenterCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mBottomCirclePaths = new ArrayList<Path>(SEG_COUNT);

private boolean mRun = false;
private SurfaceHolder mSurfaceHolder;
private DrawView mDrawView;
private Paint mPaint;

private CirclesModel mCirclesModel;
public float mRotation = 0;

public DrawThread(SurfaceHolder surfaceHolder, DrawView drawView) {
    mSurfaceHolder = surfaceHolder;
    mDrawView = drawView;
    mCirclesModel = new CirclesModel(mDrawView.getHeight());
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setTextSize(18f);
    initPaths();
}

public void setRunning(boolean b) {
    mRun = b;
}

@Override
public void run() {
    while (mRun) {
        Canvas canvas = null;
        try {
            canvas = mSurfaceHolder.lockCanvas(null);
            synchronized (mSurfaceHolder) {
                drawMainCircle(canvas);
                mPaint.setColor(Color.WHITE);
                canvas.drawCircle(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
                        mCirclesModel.mSmallCirclesRadius, mPaint);
                drawCenterCircle(canvas);
                drawUpperCircle(canvas);
                drawBottomCircle(canvas);
                //mRotation += 0.5f;

            }
        } finally {
            if (canvas != null) {
                mSurfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }
}

private void drawMainCircle(Canvas canvas) {
    canvas.save();
    canvas.rotate(mRotation, mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y]);
    float rot = mRotation;
    mPaint.setColor(Color.LTGRAY/* argb(100, 255, 255, 255) */);
    canvas.drawCircle(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
            mCirclesModel.mBigCirclesRadius, mPaint);
    mPaint.setColor(Color.BLACK);
    for (int i = 0; i < SEG_COUNT; i++) {
        canvas.rotate(SEG_IN_GRAD, mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y]);
        rot += SEG_IN_GRAD;
        float absRot = Math.abs(rot % 360);
        if (absRot > mCirclesModel.mMainCircleSegment[0] && absRot < mCirclesModel.mMainCircleSegment[1]) {
            continue;
        }
        canvas.drawLine(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
                mCirclesModel.mBigCirclesRadius, mCirclesModel.mMainCircleCentr[CirclesModel.Y], mPaint);
        canvas.drawPath(mMainCirclePaths.get(i), mPaint);
        // canvas.drawText("my text" + String.valueOf(i),
        // mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
    }
    canvas.restore();
}
   .................................................................
}

双缓冲在两行代码中实现

canvas = mSurfaceHolder.lockCanvas(null);在这里,我取自将在其中绘制下一帧的表面视图画布。

mSurfaceHolder.unlockCanvasAndPost(canvas);在这里,我将 SurfaceView 上的当前图像与新的 canwas 重叠,这是图像发生变化的时刻。请注意,如果您有透明元素,那么之前的图像仍然可见,图像不会被替换,而是重叠。

4

2 回答 2

8

下面是包含一些优化的代码版本。

首先,我尽量不绘制当前屏幕外的线条和文本。我通过跟踪旋转角度并跳过 90 到 270 度之间的净旋转的绘图来做到这一点。在我的 2.3 模拟器上,整体性能提高了 25%。

其次,我“缓存”要绘制的字符串,方法是初始化一个数组ArrayList<Path>Path我在您一次性初始化mPaint. 然后我使用 canvas.drawPath(...) 绘制字符串。在我的 2.3 模拟器上,这又将性能提高了 33%。净效果是使旋转速度增加一倍左右。此外,它阻止了文本“摆动”。

其他一些注意事项:

我删除了Thread.sleep(1,1). 不知道你到底想用它来完成什么。

我将旋转增量从 0.9 更改为 1.0。不知道你为什么使用 0.9。请注意,如果您更改为 back,我的“旋转 10 度所需的日志时间”将无法正常工作,因为mRotation % 10可能很少为 0。

在 4.1 模拟器上,旋转通常比我的 2.3 模拟器快得多(大约 4 倍)。4.1 的设备速度更快。

public class AnimView extends View {
Paint mPaint;
ArrayList<Path> mTextPaths;

float mRotation = 0f;

float mUpperCircleCentr = 150f;
float mUpperCirclRadius = 150f;

private static final int SEG_COUNT = 60;
private static final float SEG_IN_GRAD = 360.0f / SEG_COUNT;

float mBottomCircleCentr = 450f;
float mBottomCirclRadius = 150f;

float mMainCircleCentr = 300f;
float mMainCirclRadius = 300f;

long mLastMillis = 0L;

// ctors removed

@Override
protected void onDraw(final Canvas canvas) {
    super.onDraw(canvas);

    if (mPaint == null) {
        mPaint = new Paint();
        mPaint.setTextSize(20f);

        // init text paths
        mTextPaths = new ArrayList<Path>(SEG_COUNT);
        for (int i = 0; i < SEG_COUNT; i++) {
            Path path = new Path();
            String s = "my text" + String.valueOf(i);
            mPaint.getTextPath(s, 0, s.length(), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, path);
            path.close(); // not required on 2.2/2.3 devices
            mTextPaths.add(path);
        }
    }
    if (mLastMillis == 0L) {
        mLastMillis = System.currentTimeMillis();
    }

    drawUpperCircle(canvas);
    drawBottomCircle(canvas);
    drawMainCircle(canvas);

    invalidate();

    if (((int) mRotation) % 10 == 0) {
        long millis = System.currentTimeMillis();
        Log.w("AnimateCanvas", "OnDraw called with mRotation == " + mRotation);
        Log.w("AnimateCanvas", "Last 10 degrees took millis: " + (millis - mLastMillis));
        mLastMillis = millis;
    }
}

private void drawUpperCircle(Canvas canvas) {
    canvas.save();
    canvas.rotate(mRotation, 0, mUpperCircleCentr);
    float rot = mRotation;
    mPaint.setColor(Color.CYAN);
    canvas.drawCircle(0, mUpperCircleCentr, mUpperCirclRadius, mPaint);
    mPaint.setColor(Color.BLACK);
    for (int i = 0; i < SEG_COUNT; i++) {
        canvas.rotate(SEG_IN_GRAD, 0, mUpperCircleCentr);
        rot += SEG_IN_GRAD;
        if (rot % 360 > 90 && rot % 360 < 270)
            continue;
        canvas.drawLine(0, mUpperCircleCentr, mUpperCirclRadius, mUpperCircleCentr, mPaint);
    }
    canvas.restore();
}

private void drawBottomCircle(Canvas canvas) {
    canvas.save();
    canvas.rotate(mRotation, 0, mBottomCircleCentr);
    float rot = mRotation;
    mPaint.setColor(Color.RED);
    canvas.drawCircle(0, mBottomCircleCentr, mBottomCirclRadius, mPaint);
    mPaint.setColor(Color.BLACK);
    for (int i = 0; i < SEG_COUNT; i++) {
        canvas.rotate(SEG_IN_GRAD, 0, mBottomCircleCentr);
        rot += SEG_IN_GRAD;
        if (rot % 360 > 90 && rot % 360 < 270)
            continue;
        canvas.drawLine(0, mBottomCircleCentr, mBottomCirclRadius, mBottomCircleCentr, mPaint);
    }
    canvas.restore();
}

private void drawMainCircle(Canvas canvas) {
    canvas.save();

    canvas.rotate(mRotation, 0, mMainCircleCentr);
    float rot = mRotation;
    mPaint.setColor(Color.argb(100, 100, 100, 100));
    canvas.drawCircle(0, mMainCircleCentr, mMainCirclRadius, mPaint);
    mPaint.setColor(Color.BLACK);
    for (int i = 0; i < SEG_COUNT; i++) {
        canvas.rotate(SEG_IN_GRAD, 0, mMainCircleCentr);
        rot += SEG_IN_GRAD;
        if (rot % 360 > 90 && rot % 360 < 270)
            continue;
        canvas.drawLine(0, mMainCircleCentr, mMainCirclRadius, mMainCircleCentr, mPaint);
        canvas.drawPath(mTextPaths.get(i), mPaint);
        // canvas.drawText("my text" + String.valueOf(i), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
    }
    canvas.restore();
}
}
于 2012-12-30T00:44:01.220 回答
3

您的代码非常漂亮和简单。您可以通过使用更少的循环来优化它,例如将所有东西绘制在一起或组合变量,但这很快就会变得混乱。

我建议您保持绘图代码或多或少相等。您实际上并没有做最糟糕的事情:实例化对象,而且它清晰且易于维护。

但是您也许可以尝试使用双缓冲区:在 ram 中绘制缓冲区并在屏幕上一次翻转缓冲区。这通常可以很好地获得恒定的动画速度。使用画布的锁定和解锁:在 Android 上使用画布和表面视图在 Java 中进行双缓冲

于 2012-12-29T22:27:34.560 回答