亲宝软件园·资讯

展开

Android用SurfaceView实现下雨天气效果 Android利用SurfaceView实现下雨的天气动画效果

Melodyxxx 人气:0
想了解Android利用SurfaceView实现下雨的天气动画效果的相关内容吗,Melodyxxx在本文为您仔细讲解Android用SurfaceView实现下雨天气效果的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:android,surfaceview,android,下雨动画效果,android的surfaceview,下面大家一起来学习吧。

首先是最终实现的效果图:

先分析一下雨滴的实现:

  1. 每个雨滴其实就是一条线,通过 canvas.drawLine() 绘制
  2. 线(雨滴)的长度、宽度、下落速度、透明度以及位置都是在一定范围内随机生成
  3. 每 draw 一次然后改变雨滴的位置然后重绘即可实现雨滴的下落效果

分析完了,那么可以直接写一个类直接继承 View ,然后重写 onDraw() 吗?可以看到效果图中的雨滴的下落速度很快,那么意味着每一帧都要调用 onDraw() 一次使其重新绘制一次,假如你的 onDraw() 方法里面的渲染代码稍微有点费时,而 View 的 onDraw() 方法调用是在 UI 线程中,那么绘制出来的效果就不会那么流畅,甚至还会阻塞 UI 线程,所以为了更流畅的效果并且不阻塞 UI 线程,我们这里使用 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 线程中的,绘制线程需要我们自己手动创建。

具体可看官方文档:https://developer.android.google.cn/reference/android/view/SurfaceView.html

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

今后要想实现不同的天气类型,只需要继承 BaseType 类重写相关方法即可。

源码下载:点击这里

总结

以上就是这篇文章的全部内容了,希望本文的内容对各位Android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。

加载全部内容

相关教程
猜你喜欢
用户评论