您好,登录后才能下订单哦!
如何在Android中使用SurfaceView制作一个天气动画效果?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
首先是最终实现的效果图:
初识 SurfaceView
SurfaceView 直接继承自 View,View 必须在 UI 线程中绘制,而 SurfaceView 不同于 View,它可以在非 UI 线程中绘制并显示在界面上,这意味着你可以自己新开一个线程,然后把绘制渲染的代码放在该线程中。
Surface 是 Z 轴排序的,SurfaceView 的 Z 轴位置小于它的宿主 Window,代表它总是在自己所在 Window 的后面,既然在后面,那么是怎么显示的呢?SurfaceView 在其 Window 中打出一个“孔”(其实就是在其宿主 Window 上设置了一块透明区域来使其能够显示),意味着他的兄弟节点的 View 会覆盖它,例如你可以在 SurfaceView 上方放置按钮,文本等控件。
要想访问下面的 Surface ,可以通过 Android 提供给我们的 SurfaceHolder 接口。可以调用 SurfaceView 的 getHolder()
来获取。
SurfaceView 是有生命周期的,我们必须在它生命周期期间进行执行绘制代码,所以我们需要监听 SurfaceView 的状态(例如创建以及销毁),这里 Android 为我们提供了 SurfaceHolder.Callback
这个接口来可以让我们方便的监听 SurfaceView 的状态。
那么下面看下 SurfaceHolder.Callback
接口
public interface Callback { // SurfaceView 创建时调用(SurfaceView的窗口可见时) public void surfaceCreated(SurfaceHolder holder); // SurfaceView 改变时调用 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height); // SurfaceView 销毁时调用(SurfaceView的窗口不可见时) public void surfaceDestroyed(SurfaceHolder holder); }
我们的绘制代码需要在 surfaceCreated 和 surfaceDestroyed 之间执行,否则无效,SurfaceHolder.Callback
的回调方法是执行在 UI 线程中的,绘制线程需要我们自己手动创建。
View 和 SurfaceView 的使用场景
View 适合那些与用户交互并且渲染时间不是很长的控件,因为 View 的绘制和用户交互都处在 UI 线程中。
SurfaceView 适合迅速的更新界面或者渲染时间比较长以至于影响到用户体验的场景。
使用 SurfaceView(实现)
这里我们和自定义 View 类似,写一个类 DynamicWeatherView 继承自 SurfaceView,然后为了监听 SurfaceView 的状态,所以我们还需要实现 SurfaceHolder.Callback
接口来监听 SurfaceView 的状态,接口的回调具体时机上面也已经介绍过了。
public class DynamicWeatherView extends SurfaceView implements SurfaceHolder.Callback{ public DynamicWeatherView(Context context) { this(context, null); } public DynamicWeatherView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DynamicWeatherView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } // SurfaceView 创建时调用(可见) @Override public void surfaceCreated(SurfaceHolder holder) { } // SurfaceView 销毁时调用(不可见) @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } }
上面也提到了,控制 Surface 我们需要 SurfaceHolder 对象,调用 SurfaceView 的 getHolder()
即可获得,然后为这个 SurfaceHolder 添加一个 SurfaceHolder.Callback
回调,这里就是 DynamicWeatherView 当前对象
private SurfaceHolder mHolder;
mHolder = getHolder(); mHolder.addCallback(this); mHolder.setFormat(PixelFormat.TRANSPARENT);
然后实现我们的绘制线程:
private class DrawThread extends Thread { // 用来停止线程的标记 private boolean isRunning = false; public void setRunning(boolean running) { isRunning = running; } @Override public void run() { Canvas canvas; // 无限循环绘制 while (isRunning) { if (mType != null && mViewWidth != 0 && mViewHeight != 0) { canvas = mHolder.lockCanvas(); if (canvas != null) { mType.onDraw(canvas); if (isRunning) { mHolder.unlockCanvasAndPost(canvas); } else { // 停止线程 break; } SystemClock.sleep(1); } } } } }
从上面的代码可以看出 SurfaceView 的更新流程具体为:
// 锁定画布并获得 canvas canvas = mHolder.lockCanvas(); // 在 canvas 上进行绘制 mType.onDraw(canvas); // 解除锁定并提交更改 mHolder.unlockCanvasAndPost(canvas);
绘制线程代码量不多,因为具体的绘制代码在 mType.onDraw(canvas)
中,mType 是我们自己定义的一个接口,代表一种天气类型:
public interface WeatherType { void onDraw(Canvas canvas); void onSizeChanged(Context context, int w, int h); }
这样要想实现不同的天气类型,只要实现这个接口重写 onDraw 和 onSizeChanged 方法即可,这里我们实现的是下雨的效果,所以实现了一个 RainTypeImpl 类:
public class RainTypeImpl extends BaseType { // 背景 private Drawable mBackground; // 雨滴集合 private ArrayList<RainHolder> mRains; // 画笔 private Paint mPaint; public RainTypeImpl(Context context, DynamicWeatherView dynamicWeatherView) { super(context, dynamicWeatherView); init(); } private void init() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.WHITE); // 这里雨滴的宽度统一为3 mPaint.setStrokeWidth(3); mRains = new ArrayList<>(); } @Override public void generate() { mBackground = getContext().getResources().getDrawable(R.drawable.rain_sky_night); mBackground.setBounds(0, 0, getWidth(), getHeight()); for (int i = 0; i < 60; i++) { RainHolder rain = new RainHolder( getRandom(1, getWidth()), getRandom(1, getHeight()), getRandom(dp2px(9), dp2px(15)), getRandom(dp2px(5), dp2px(9)), getRandom(20, 100) ); mRains.add(rain); } } private RainHolder r; @Override public void onDraw(Canvas canvas) { clearCanvas(canvas); // 画背景 mBackground.draw(canvas); // 画出集合中的雨点 for (int i = 0; i < mRains.size(); i++) { r = mRains.get(i); mPaint.setAlpha(r.a); canvas.drawLine(r.x, r.y, r.x, r.y + r.l, mPaint); } // 将集合中的点按自己的速度偏移 for (int i = 0; i < mRains.size(); i++) { r = mRains.get(i); r.y += r.s; if (r.y > getHeight()) { r.y = -r.l; } } } private class RainHolder { /** * 雨点 x 轴坐标 */ int x; /** * 雨点 y 轴坐标 */ int y; /** * 雨点长度 */ int l; /** * 雨点移动速度 */ int s; /** * 雨点透明度 */ int a; public RainHolder(int x, int y, int l, int s, int a) { this.x = x; this.y = y; this.l = l; this.s = s; this.a = a; } } }
代码不难,基本都有注释,RainHolder 对象代表一个雨滴,每绘制一次然后改变雨滴的位置,然后准备下一次绘制,来实现雨滴的移动。
BaseType 类是我们的一个抽象基类,实现了 DynamicWeatherView.WeatherType
接口,内部有一些公共方法,具体可以看 Demo 中的代码。
最后我们的 Activity 代码:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DynamicWeatherView mDynamicWeatherView = (DynamicWeatherView) findViewById(R.id.dynamic_weather_view); mDynamicWeatherView.setType(new RainTypeImpl(this, mDynamicWeatherView)); } }
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。