自定义 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 动画更流畅?的主要内容,如果未能解决你的问题,请参考以下文章