【问题标题】:Smooth Painting in a SurfaceViewSurfaceView 中的平滑绘制
【发布时间】:2014-03-12 23:27:22
【问题描述】:

我正在开发一个类似于绘画的应用程序,但我在运行它时遇到了一些麻烦。

我知道我在每一帧都重新绘制了所有对象,但这反过来又会在许多对象可见时进一步降低性能。

我还注意到,只清除一次背景,然后在绘画活动时将要绘制的对象添加到表面,会使屏幕闪烁几乎到诱发癫痫的程度。

那么,无论绘制多少对象,让绘图应用 100% 平滑的最佳方法是什么?

癫痫诱发代码:

public void run()
{
    while (running)
    {

        if (!holder.getSurface().isValid())
            continue;

        if (drawObjectsToAdd.size() == 0)
            continue;

        drawing = true;

        Canvas c = holder.lockCanvas();

        if (redrawBackground)
        {
            c.drawARGB(255, 255, 255, 255);
            redrawBackground = false;
        }   

        drawObjects.addAll(drawObjectsToAdd);
        drawObjectsToAdd.clear();

        for (DrawObject draw : drawObjects)
        {
            draw.Draw(c, paint);
        }

        holder.unlockCanvasAndPost(c);
        drawing = false;
    }
}

一旦将新对象添加到线程之外,它就会添加一个新对象,但它会使屏幕闪烁得如此之多,这让我很头疼——而且我只重绘了一次背景。

一开始很流畅,但一段时间后变得迟钝,可能是由于最后不得不添加数百个对象:

public void run()
{
    while (running)
    {
        if (!holder.getSurface().isValid())
            continue;

        if (drawObjectsToAdd.size() > 0)
        {
            drawObjects.addAll(drawObjectsToAdd);
            drawObjectsToAdd.clear();

            redraw = true;
        }

        if (clear)
        {
            drawObjectsToAdd.clear();
            drawObjects.clear();
            redraw = true;
            clear = false;
        }

        if (!redraw)
            continue;

        drawing = true;

        Canvas c = holder.lockCanvas();

        c.drawARGB(255, 255, 255, 255);

        for (DrawObject draw : drawObjects)
        {
            try
            {
                draw.Draw(c, paint);
            }
            catch (Exception ex) { }
        }


        holder.unlockCanvasAndPost(c);

        drawing = false;
        redraw = false;
    }
}

我,至少对于这个应用程序来说,想要存储所有添加的对象,所以它的绘制方式并不重要,只要它一路平滑即可。最好添加一个圆圈 - 它会在表面上渲染一个新的位图,而不必每帧重绘大量对象 - 而是存储它们但不要添加已绘制的对象。

更新

我想要的伪代码:

If no background is drawn
   Draw background color
If new items have been added
   Draw only new items to the background
   Store new items in objects list

这样,我们只会绘制一次背景。添加新项目时,仅将该项目绘制到现有表面。当对象增加时,循环遍历每个项目会大大降低性能,并且使用起来会很不愉快。

更新 2:

private void Draw()
{
    while (running)
    {

        if (!holder.getSurface().isValid())
            continue;

        if (picture == null)
        {
            picture = new Picture();
            Canvas c = picture.beginRecording(getWidth(), getHeight());
            c.drawARGB(255,  255,  255,  255);
            picture.endRecording();
        }

        if (drawObjectsToAdd.size() > 0)
        {
            drawObjects.addAll(drawObjectsToAdd);
            drawObjectsToAdd.clear();

            Canvas c = picture.beginRecording(getWidth(), getHeight());

            for (DrawObject draw : drawObjects)
            {
                draw.Draw(c, paint);
            }

            picture.endRecording();
            drawObjects.clear();

        }

        Canvas c2 = holder.lockCanvas();
        c2.drawPicture(picture);

        holder.unlockCanvasAndPost(c2);
    }
}

Update 2 中的最后一个方法使它在添加圆圈时像“蛇游戏”一样渲染所有线条。看起来像背景上的一条移动的蛇,其中一些圆圈在一帧消失,而其他圆圈则在下一帧消失。如果我跳过重绘每一帧,它会改变哪些圆圈是可见的。

【问题讨论】:

  • 我们在谈论多少个对象?
  • 需要多少就多少。这是一个绘画应用程序,因此最好在不影响性能的情况下绘画你想要的数量。到目前为止,我唯一的想法是:添加一个对象,从视图中获取位图。添加另一个对象时,重新绘制位图,然后在位图顶部添加新对象。这样,它只会每帧重绘一个位图,而不是数百万个潜在对象。
  • 但是修改现有项目,撤消等呢?
  • 为什么我存储所有添加的对象,然后当启用该模式时,您只需检查最接近您想要“撤消”或“擦除”点的对象并删除它们。但截至目前,通过我的实现,“SurfaceView.getDrawingCache()”不会返回任何内容。我称之为“SurfaceViewThread 扩展 SurfaceView 实现 Runnable”,也许我做错了。但是我尝试实施的任何其他方法都没有正常工作。
  • 但是,是的,这种实现也可能对性能要求很高。但是我现在发现了解决性能问题的方法,因为我的方法有效,但是在添加了很多对象之后它变得很慢。而且我找不到任何关于如何只绘制一次背景,然后只将新添加的对象绘制到表面的信息,而无需重新绘制整个屏幕并添加已添加的每个项目。

标签: android drawing paint surfaceview


【解决方案1】:

那个 Picture 实现呢?将 MAX_DRAWERS 增加到某个合理的值,看看它是如何工作的

class SV extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private static final int MAX_DRAWERS = 8;

    private boolean mRunning = true;
    private List<Picture> mPictures = new LinkedList<Picture>();
    private List<Drawer> mDrawers = new LinkedList<Drawer>();
    private Paint mPaint;

    public SV(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setColor(0xffffff00);
        getHolder().addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        new Thread(this).start();
    }

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

    @Override
    public synchronized void surfaceDestroyed(SurfaceHolder holder) {
        mRunning = false;
        notify();
    }

    @Override
    public synchronized boolean onTouchEvent(MotionEvent event) {
        Drawer drawer = new Drawer(event.getX(), event.getY());
        mDrawers.add(drawer);

        if (mDrawers.size() > MAX_DRAWERS) {
            Picture picture = new Picture();
            Canvas canvas = picture.beginRecording(getWidth(), getHeight());
            mPaint.setAlpha(0xbb);
            for (Drawer dr : mDrawers) {
                dr.draw(canvas, mPaint);
            }
            picture.endRecording();
            mPaint.setAlpha(0xff);
            mPictures.add(picture);
            mDrawers.clear();
            Log.d(TAG, "onTouchEvent new Picture");
        }
        notify();
        return false;
    }

    @Override
    public synchronized void run() {
        SurfaceHolder holder = getHolder();
        while (mRunning) {
//            Log.d(TAG, "run wait...");
            try {
                wait();
            } catch (InterruptedException e) {
            }
            if (mRunning) {
//                Log.d(TAG, "run woke up");
                Canvas canvas = holder.lockCanvas();
                canvas.drawColor(0xff0000ff);
                for (Picture picture : mPictures) {
                    picture.draw(canvas);
                }
                for (Drawer drawer : mDrawers) {
                    drawer.draw(canvas, mPaint);
                }
                holder.unlockCanvasAndPost(canvas);
            }
        }
        Log.d(TAG, "run bye bye");
    }

    class Drawer {
        private float x;
        private float y;
        public Drawer(float x, float y) {
            this.x = x;
            this.y = y;
        }
        public void draw(Canvas canvas, Paint paint) {
            canvas.drawCircle(x, y, 8, paint);
        }
    }
}

【讨论】:

  • 这是一种工作方法,但为什么我不能用一张图片来存储所有内容呢?尝试用你的代码实现它,但每当我触摸屏幕时它就变成了一个移动的点。它比每帧渲染所有对象要快一点。
  • 因为你不能同时录制(添加新东西)和绘图,至少我认为是这样......但是当你有太多图片时,你可以将它们组合成一个 SuperHiperMegaPicture跨度>
  • 嗯,除了录制到多张照片之外,这不是我已经在做的事情了吗?据我所知,它在将新数据记录到图片中时等待 notify() 的代码,所以它不会被绘制。此外,使用所有这些线程方法,不确定在哪里设置默认背景颜色而不添加任何内容。不能在构造函数中完全调用它,并且它已经在 surfaceCreated 中得到通知,并且我没有一个 onTouch 事件,而是有一个 Add 方法来做同样的事情。
  • 因此,您可以拥有一个位图,而不是 mPictures,它将使用一个 Canvas.drawBitmap 绘制并在 mDrawers 被 clear()ed 的地方更新
  • 我只是尝试了另一种方法:创建图片,如果“this.picture!= null”将其绘制到新图片中,然后添加DrawObject,EndRecording将其存储为“this.picture”和通知()。产生与其他方法大致相同的性能,但应用程序中存储的数据较少。但是,在几百圈之后 - 它仍然变得缓慢。不知道为什么。此时只渲染了DrawObject 的MAX_DRAWERS 和mPictures.size() 数量的图片,但是性能下降非常明显。或者我实现我的方法的方式。和以前差不多。
【解决方案2】:

您在每一帧都生成对象列表,只是在绘制后将其丢弃。为什么?生成一次绘制对象列表,然后绘制它们。

这就是你的绘图线程所需要的一切......

public void run() {
while (running)
{

    Canvas c = holder.lockCanvas();

    c.drawARGB(255, 255, 255, 255);

    for (DrawObject draw : drawObjects)
    {
        try
        {
            draw.Draw(c, paint);
        }
        catch (Exception ex) { }
    }


    holder.unlockCanvasAndPost(c);

}

}

【讨论】:

  • 我不生成我的绘图对象列表,如果屏幕在添加新的 DrawObject 时被“重绘”,我将其添加到“drawObjectsToAdd”,然后一旦绘图完成,它会检查列出并添加要添加的内容。我在我面前看到的只是一个对我来说崩溃的更多错误的解决方案。我还需要检查 SurfaceHolder 是否有效,或者不跳过循环(否则它会立即崩溃)。通过将对象直接添加到我的 drawObjects 会导致应用程序在几次后崩溃(因为它可能“正在”渲染对象,而我仍然将它添加到列表中),为什么我使用“drawObjectsToAdd”。
  • 触摸屏幕,添加对象。但是将它添加到一个单独的列表中,只有在绘图完成后才会被检查。如果找到要绘制的新对象,将其添加到绘制列表并清除该列表。然后用drawObjects中的所有对象重绘。画完之后我从不丢弃。所有对象仍然存在于“drawObjects”中,只是较新的对象被添加到单独的列表中,因此它不会与线程中间的“for”循环发生冲突。因为它们在单独的线程上运行,所以当我调用我的方法“添加”和执行所有绘画的线程时。
  • ...另外,当绘制完成并且没有添加新对象时 - 不一定需要重新绘制表面。我添加到您的方法中:如果 SurfaceHolder 无效:继续;将 drawObjectsToAdd 添加到 drawObjects。从应用程序开始,它就落后了。使用我的方法,最近添加的对象正是我的手指所在的位置,而使用这种方法它落后了。如果只是将手指放在屏幕上画画,不管它是什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-19
  • 1970-01-01
  • 2020-08-31
  • 1970-01-01
  • 1970-01-01
  • 2017-06-10
相关资源
最近更新 更多