文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-05-31 10:15

关注

如何在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 的更新流程具体为:

// 锁定画布并获得 canvascanvas = 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 {  int x;  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)); }}

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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯