Анимация Custom View более плавная, чем анимация Custom SurfaceView?

Я разработал аналогичную реализацию, чтобы проверить, следует ли мне использовать View или SurfaceView. Я реализовал представление, как показано ниже

public class TimerView extends View {

    private Paint mPiePaint;
    private RectF mShadowBounds;
    private float diameter;

    int startCount = 0;
    private PanelThread thread;

    public TimerView(Context context) {
        super(context);
        init();
    }

    public TimerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TimerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(VERSION_CODES.LOLLIPOP)
    public TimerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPiePaint.setStyle(Paint.Style.FILL);
        mPiePaint.setColor(0xff000000);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // Account for padding
        float xpad = (float)(getPaddingLeft() + getPaddingRight());
        float ypad = (float)(getPaddingTop() + getPaddingBottom());

        float ww = (float)w - xpad;
        float hh = (float)h - ypad;

        // Figure out how big we can make the pie.
        diameter = Math.min(ww, hh);
        mShadowBounds = new RectF(0, 0, diameter, diameter);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (startCount == 360) startCount= 0;
        canvas.drawArc(mShadowBounds,
                0, startCount, true, mPiePaint);
        invalidate();
        startCount++;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
        int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
        int h = resolveSizeAndState(MeasureSpec.getSize(w), heightMeasureSpec, 0);
        setMeasuredDimension(w, h);
    }

}

И я реализовал свой Surface View, как показано ниже.

public class TimerSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

    private Paint mPiePaint;
    private RectF mShadowBounds;
    private float diameter;

    int startCount = 0;
    private PanelThread thread;

    public TimerSurfaceView(Context context) {
        super(context);
        init();
    }

    public TimerSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TimerSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(VERSION_CODES.LOLLIPOP)
    public TimerSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        getHolder().addCallback(this);
        mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPiePaint.setStyle(Paint.Style.FILL);
        mPiePaint.setColor(0xff000000);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // Account for padding
        float xpad = (float)(getPaddingLeft() + getPaddingRight());
        float ypad = (float)(getPaddingTop() + getPaddingBottom());

        float ww = (float)w - xpad;
        float hh = (float)h - ypad;

        // Figure out how big we can make the pie.
        diameter = Math.min(ww, hh);
        mShadowBounds = new RectF(0, 0, diameter, diameter);

    }

    protected void drawSomething(Canvas canvas) {
        canvas.drawColor(0xFFEEEEEE);

        if (startCount == 360) startCount= 0;
        canvas.drawArc(mShadowBounds,
                0, startCount, true, mPiePaint);
        startCount++;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
        int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
        int h = resolveSizeAndState(MeasureSpec.getSize(w), heightMeasureSpec, 0);
        setMeasuredDimension(w, h);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        setWillNotDraw(false); //Allows us to use invalidate() to call onDraw()
        thread = new PanelThread(getHolder(), this); //Start the thread that
        thread.setRunning(true);                     //will make calls to
        thread.start();                              //onDraw()
    }

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

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // tell the thread to shut down and wait for it to finish
        // this is a clean shutdown
        boolean retry = true;
        while (retry) {
            try {
                thread.join();
                retry = false;
            } catch (InterruptedException e) {
                // try again shutting down the thread
            }
        }
    }
}

И поток SurfaceView, как показано ниже

class PanelThread extends Thread {
    private SurfaceHolder surfaceHolder;
    private TimerSurfaceView panel;
    private boolean starRunning = false;


    public PanelThread(SurfaceHolder surfaceHolder, TimerSurfaceView panel) {
        this.surfaceHolder = surfaceHolder;
        this.panel = panel;
    }


    public void setRunning(boolean run) { //Allow us to stop the thread
        starRunning = run;
    }


    @Override
    public void run() {
        Canvas c;
        while (starRunning) {     //When setRunning(false) occurs, starRunning is
            c = null;      //set to false and loop ends, stopping thread
            try {
                c = surfaceHolder.lockCanvas(null);
                synchronized (surfaceHolder) {
                    //Insert methods to modify positions of items in onDraw()
                    panel.drawSomething(c);
                }
            } finally {
                if (c != null) {
                    surfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }
}

Результат показывает, что пользовательское представление (TimerView) более плавное, чем поверхностное представление, как показано на https://www.youtube.com/watch?v=s9craUgY3I4 . Согласно http://stackoverflow.com/questions/23893266/why-surfaceview-is-slower-than-a-custom-view, SurfaceView хоть и медленнее, но должен быть более плавным.

Может ли это быть связано с тем, что в SurfaceView мне нужно перекрасить, чтобы стереть предыдущую функцию рисования canvas.drawColor(0xFFEEEEEE); на drawSomething? Есть ли способ устранить необходимость перекрашивания, как я сделал в TimerView, я просто invalidate() это во время onDraw?

Другая проблема, с которой я сталкиваюсь, заключается в том, что когда приложение переходит в фоновый режим и возвращается, drawSomthing TimerSurfaceView получит null Canvas, в то время как TimerView onDraw() не становится недействительным, а анимация просто останавливается. Есть ли что-то, что мне нужно сделать, чтобы позволить ему оставаться там, где он был?


person Elye    schedule 01.04.2016    source источник


Ответы (2)


Как я отметил в комментарии к ответу на вопрос, на который вы ссылались:

Рендеринг Canvas в SurfaceView не ускоряется аппаратно, в отличие от рендеринга Canvas в обычный вид.

По мере того, как количество пикселей на дисплее увеличивается (из-за неизбежного перехода на 4K-дисплеи на крошечных устройствах), программный рендеринг становится медленнее. Увеличение производительности ЦП и пропускной способности памяти компенсирует это, но на некоторых устройствах это не будет работать хорошо.

Вы можете компенсировать это различными способами, например. использование setFixedSize() для ограничения количества пикселей , но рендеринг с аппаратным ускорением, как правило, является лучшим подходом.

Если ваша частота кадров ограничена ЦП, то все, что хочет использовать одно и то же ядро ​​ЦП, вызовет рывки. Тот факт, что вы можете поместить средство визуализации SurfaceView в отдельный поток, полезен, но если вы выходите за пределы устройства, это просто не имеет значения. Отображение обновляется с определенной скоростью, и если вы не уложитесь в установленный срок, ваша анимация не будет гладкой. (Некоторые дополнительные мысли можно найти в этом приложении.)

person fadden    schedule 01.04.2016
comment
Да, я знаю, что представление Surface медленнее. Мой вопрос заключается в том, почему мой SurfaceView не такой гладкий, как View onDraw... Я думаю, что основная причина использования SufaceView заключается в том, что он может передать свою операцию другому процессу и, следовательно, сделать его более плавным, чем анимация, чем View. - person Elye; 02.04.2016
comment
Опять же, если вы пропустите крайний срок обновления дисплея, у вас будет джанк. У вас есть 16,7 мс для рендеринга каждого кадра со скоростью 60 кадров в секунду, и программный рендеринг на больших дисплеях будет иметь проблемы с ним. Доведите это до крайности: если программный рендеринг в SurfaceView занимает 200 мс (5 кадров в секунду), он не будет выглядеть более плавным, чем аппаратный рендеринг в пользовательский вид, который занимает всего 10 мс на кадр. Многопоточность — это не панацея. - person fadden; 02.04.2016

Разработчики Android сказали, что вы должны делать все анимации в основном потоке, так как их фреймворк не работает так хорошо, иначе с работой пользовательского интерфейса

person Daniel Fletcher    schedule 29.07.2016
comment
Что вы имеете в виду под Android-разработчиками? Вы должны добавить свой источник. - person Thomas Betous; 29.07.2016