Animasi Tampilan Kustom lebih halus daripada animasi Custom SurfaceView?

Saya telah mengembangkan implementasi serupa untuk menguji apakah saya harus menggunakan View atau SurfaceView. Saya menerapkan Tampilan seperti di bawah ini

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

}

Dan saya menerapkan Surface View saya seperti di bawah ini

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

Dan SurfaceView Thread seperti di bawah ini

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

Hasilnya menunjukkan bahwa Tampilan Kustom (TimerView) lebih mulus dibandingkan Tampilan Permukaan, seperti yang ditunjukkan di https://www.youtube.com/watch?v=s9craUgY3I4 . Menurut http://stackoverflow.com/questions/23893266/why-surfaceview-is-slower-than-a-custom-view, SurfaceView meskipun lebih lambat, seharusnya lebih lancar.

Mungkinkah karena di SurfaceView, saya perlu mewarnai ulang untuk menghapus fungsi undian canvas.drawColor(0xFFEEEEEE); pada drawSomething sebelumnya? Apakah ada cara untuk menghilangkan kebutuhan pewarnaan ulang, seperti yang saya lakukan di TimerView, saya hanya invalidate() selama onDraw?

Masalah lain yang saya hadapi adalah, ketika Aplikasi pergi ke latar belakang dan kembali, drawSomthing dari TimerSurfaceView akan menerima Kanvas nol, sedangkan TimerView onDraw() tidak valid, dan animasi berhenti begitu saja. Adakah yang perlu saya lakukan agar hal ini tetap berlanjut seperti semula?


person Elye    schedule 01.04.2016    source sumber


Jawaban (2)


Seperti yang saya catat di jawaban-komentar atas pertanyaan yang Anda tautkan:

Rendering kanvas ke SurfaceView tidak dipercepat perangkat keras, sedangkan rendering Canvas ke Tampilan biasa dipercepat.

Ketika jumlah piksel layar semakin tinggi (karena dorongan yang tak terhindarkan untuk tampilan 4K pada perangkat kecil), rendering perangkat lunak menjadi lebih lambat. Peningkatan kinerja CPU dan bandwidth memori akan mengimbangi hal ini, namun kinerjanya tidak akan baik pada beberapa perangkat.

Anda dapat mengimbanginya dengan berbagai cara, mis. menggunakan setFixedSize() untuk membatasi jumlah piksel , namun rendering yang dipercepat perangkat keras umumnya merupakan pendekatan yang lebih baik.

Jika kecepatan bingkai Anda dibatasi oleh CPU, apa pun yang ingin menggunakan inti CPU yang sama akan menyebabkan jank. Fakta bahwa Anda dapat menempatkan penyaji SurfaceView di thread terpisah sangat membantu, tetapi jika Anda melampaui batas perangkat maka itu tidak masalah. Tampilan diperbarui dengan kecepatan tertentu, dan jika Anda tidak memenuhi tenggat waktu secara konsisten maka animasi Anda tidak akan mulus. (Beberapa pemikiran tambahan dapat ditemukan di lampiran ini.)

person fadden    schedule 01.04.2016
comment
Ya, saya sadar bahwa tampilan Surface lebih lambat. Pertanyaan saya adalah mengapa SurfaceView saya tidak semulus View onDraw... Saya pikir alasan utama seseorang menggunakan SufaceView adalah karena ia dapat meneruskan operasinya ke proses lain, dan karenanya membuatnya lebih lancar dianimasikan daripada View. - person Elye; 02.04.2016
comment
Sekali lagi, jika Anda melewatkan batas waktu penyegaran tampilan, Anda akan mengalami jank. Anda memiliki waktu 16,7 ms untuk merender setiap frame pada 60fps, dan rendering perangkat lunak pada layar yang lebih besar akan mengalami kesulitan untuk mengimbanginya. Ambil contoh ekstrem: jika rendering perangkat lunak ke SurfaceView memerlukan waktu 200 md (5fps), rendering tersebut tidak akan terlihat lebih mulus dibandingkan rendering perangkat keras ke Tampilan kustom yang hanya memerlukan waktu 10 md per frame. Multi-threading bukanlah solusi terbaik. - person fadden; 02.04.2016

Pengembang Android mengatakan Anda harus melakukan semua animasi di thread utama, karena kerangka kerja mereka tidak berfungsi dengan baik, sebaliknya dengan pekerjaan UI

person Daniel Fletcher    schedule 29.07.2016
comment
Apa yang Anda maksud dengan pengembang Android? Anda harus menambahkan sumber Anda. - person Thomas Betous; 29.07.2016