ภาพเคลื่อนไหว 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 Thread ตามด้านล่างนี้

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) นุ่มนวลกว่ามุมมอง Surface ดังที่แสดงใน 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 จะได้รับ Canvas ที่เป็นโมฆะ ในขณะที่ TimerView onDraw() ไม่ทำงาน และแอนิเมชั่นก็หยุดลง ฉันต้องทำอะไรเพื่อให้มันดำเนินต่อไปจากที่เคยเป็นหรือไม่?




คำตอบ (2)


ดังที่ฉันได้ระบุไว้ในคำตอบความคิดเห็นเกี่ยวกับคำถามที่คุณเชื่อมโยง:

การเรนเดอร์ Canvas ไปยัง SurfaceView ไม่ได้รับการเร่งด้วยฮาร์ดแวร์ ในขณะที่การเรนเดอร์ Canvas ไปยังมุมมองธรรมดานั้นทำได้

เนื่องจากจำนวนพิกเซลของจอแสดงผลสูงขึ้น (เนื่องจากการขับเคลื่อนจอแสดงผล 4K บนอุปกรณ์ขนาดเล็กอย่างหลีกเลี่ยงไม่ได้) การเรนเดอร์ซอฟต์แวร์จึงช้าลง การเพิ่มประสิทธิภาพ CPU และแบนด์วิธหน่วยความจำที่เพิ่มขึ้นจะชดเชยสิ่งนี้ แต่จะทำงานได้ไม่ดีในอุปกรณ์บางชนิดเท่านั้น

คุณสามารถชดเชยสิ่งนี้ได้หลายวิธี เช่น ใช้ setFixedSize() เพื่อจำกัดจำนวนพิกเซล แต่โดยทั่วไปแล้วการเรนเดอร์ที่เร่งด้วยฮาร์ดแวร์จะเป็นแนวทางที่ดีกว่า

หากอัตราเฟรมของคุณถูกจำกัดโดย CPU ดังนั้นสิ่งใดก็ตามที่ต้องการใช้แกน CPU เดียวกันจะทำให้เกิดปัญหา ความจริงที่ว่าคุณสามารถวางตัวเรนเดอร์ SurfaceView บนเธรดแยกต่างหากนั้นมีประโยชน์ แต่ถ้าคุณขยายขีดจำกัดของอุปกรณ์ มันก็ไม่สำคัญ จอแสดงผลกำลังอัปเดตในอัตราที่กำหนด และหากคุณไม่ตรงตามกำหนดเวลาอย่างสม่ำเสมอ ภาพเคลื่อนไหวของคุณจะไม่ราบรื่น (ดูแนวคิดเพิ่มเติมได้ในภาคผนวกนี้)

person fadden    schedule 01.04.2016
comment
ใช่ ฉันทราบแล้วว่ามุมมอง Surface ทำงานช้าลง คำถามของฉันคือเหตุใด SurfaceView ของฉันจึงไม่ราบรื่นเท่ากับ View onDraw... ฉันคิดว่าเหตุผลหลักที่ใช้ SufaceView ก็คือเพราะมันสามารถเธรดการดำเนินการไปยังกระบวนการอื่นได้ และด้วยเหตุนี้จึงทำให้มันเคลื่อนไหวได้ราบรื่นกว่า View - person Elye; 02.04.2016
comment
ขอย้ำอีกครั้งว่า หากคุณพลาดกำหนดเวลาการรีเฟรชจอแสดงผล คุณจะต้องประสบปัญหา คุณมีเวลา 16.7 มิลลิวินาทีในการเรนเดอร์แต่ละเฟรมที่ 60fps และการเรนเดอร์ซอฟต์แวร์บนจอแสดงผลขนาดใหญ่จะมีปัญหาในการติดตาม ก้าวไปสู่จุดสูงสุด: หากซอฟต์แวร์เรนเดอร์ไปยัง SurfaceView ใช้เวลา 200 มิลลิวินาที (5fps) มันจะดูไม่ราบรื่นไปกว่าการเรนเดอร์ฮาร์ดแวร์ไปยังมุมมองแบบกำหนดเองที่ใช้เวลาเพียง 10 มิลลิวินาทีต่อเฟรม มัลติเธรดไม่ใช่สัญลักษณ์เงิน - person fadden; 02.04.2016

นักพัฒนา Android บอกว่าคุณควรทำแอนิเมชั่นทั้งหมดบนเธรดหลัก เนื่องจากเฟรมเวิร์กของพวกมันทำงานได้ไม่ดีนัก ไม่เช่นนั้นกับการทำงานของ UI

person Daniel Fletcher    schedule 29.07.2016
comment
คุณหมายถึงอะไรกับนักพัฒนา Android? คุณควรเพิ่มแหล่งที่มาของคุณ - person Thomas Betous; 29.07.2016