自定义 View 动画比自定义 SurfaceView 动画更流畅?

Posted

技术标签:

【中文标题】自定义 View 动画比自定义 SurfaceView 动画更流畅?【英文标题】:Custom View animation is smoother than Custom SurfaceView animation? 【发布时间】:2016-04-01 12:46:53 【问题描述】:

我已经开发了类似的实现来测试我应该选择 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);
                
            
        
    

结果显示 Custom View (TimerView) 比 Surface View 更平滑,如https://www.youtube.com/watch?v=s9craUgY3I4 所示。根据http://***.com/questions/23893266/why-surfaceview-is-slower-than-a-custom-view 的说法,SurfaceView 虽然速度较慢,但​​应该更流畅。

可能是因为在 SurfaceView 中,我需要重新着色以擦除 drawSomething 函数上之前绘制的 canvas.drawColor(0xFFEEEEEE); 吗?有没有办法消除重新着色的需要,就像我在 TimerView 中所做的那样,我只是在 onDraw 期间 invalidate() 它?

我面临的另一个问题是,当应用程序进入后台并返回时,TimerSurfaceView 的drawSomthing 将收到一个 null Canvas,而 TimerView onDraw() 不会无效,并且动画会停止。我需要做些什么才能让它继续原来的样子吗?

【问题讨论】:

【参考方案1】:

正如我在对您链接到的问题的回答评论中指出的那样:

画布渲染到 SurfaceView 不是硬件加速的,而画布渲染到普通视图是。

随着显示像素数越来越高(由于不可避免地需要在微型设备上显示 4K 显示器),软件渲染变得越来越慢。 CPU 性能和内存带宽的提高将抵消这一点,但它在某些设备上表现不佳。

您可以通过多种方式对此进行补偿,例如using setFixedSize() to limit the pixel count,但硬件加速渲染通常是更好的方法。

如果您的帧速率受到 CPU 的限制,那么任何想要使用相同 CPU 内核的东西都会导致卡顿。您可以将 SurfaceView 渲染器放在单独的线程上这一事实很有帮助,但如果您正在推动设备的限制,那么它就无关紧要了。显示正在以一定的速度更新,如果您没有始终如一地满足最后期限,那么您的动画将不会流畅。 (一些额外的想法可以在this appendix找到。)

【讨论】:

是的,我知道 Surface 视图速度较慢。我的问题是为什么我的 SurfaceView 不如 View onDraw 流畅......我使用 SufaceView 的主要原因是因为它可以将其操作线程化到另一个进程,因此使其动画比 View 更流畅。 同样,如果您错过了显示刷新截止日期,您将会遇到卡顿。你有 16.7 毫秒的时间来以 60 fps 的速度渲染每一帧,而在更大的显示器上进行软件渲染将难以跟上。把它发挥到极致:如果软件渲染到 SurfaceView 需要 200 毫秒(5fps),它看起来不会比硬件渲染到每帧只需要 10 毫秒的自定义视图更流畅。多线程不是灵丹妙药。【参考方案2】:

android 开发人员说你应该在主线程上做所有动画,因为他们的框架不能很好地工作,否则 UI 工作

【讨论】:

Android 开发者是什么意思?您应该添加您的来源。

以上是关于自定义 View 动画比自定义 SurfaceView 动画更流畅?的主要内容,如果未能解决你的问题,请参考以下文章

Android 自定义view之悬浮动画

自定义UI 属性动画

自定义UI 属性动画

自定义UI 属性动画

自定义UI 属性动画

安卓自定义view仿小米商城购物车动画