如何在Android中使用SurfaceView制作一个天气动画效果

发布时间:2020-11-26 17:05:30 作者:Leah
来源:亿速云 阅读:214

如何在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 的使用场景

使用 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));
 }
}

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。

推荐阅读:
  1. Python中如何制作天气查询软件
  2. 详解Android中SurfaceView画板操作

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

android surfaceview sur

上一篇:怎么在Android应用中添加一个长按删除弹功能

下一篇:利用MyEclipse如何将Console输出到文件中

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》