广告
返回顶部
首页 > 资讯 > 精选 >如何使用SurfaceView实现下雨与下雪动画效果
  • 431
分享到

如何使用SurfaceView实现下雨与下雪动画效果

surfaceview 2023-05-30 21:05:21 431人浏览 独家记忆
摘要

这篇文章将为大家详细讲解有关如何使用SurfaceView实现下雨与下雪动画效果,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。先看一下最终完成的效果图:下雨.gif这里比较懒……第二个图片中还是降雨……不

这篇文章将为大家详细讲解有关如何使用SurfaceView实现下雨与下雪动画效果,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

先看一下最终完成的效果图:

如何使用SurfaceView实现下雨与下雪动画效果
下雨.gif

这里比较懒……第二个图片中还是降雨……不过这不是关键点……

如何使用SurfaceView实现下雨与下雪动画效果

下雪.gif

录制的mp4,转成了gif。第一个gif设置了帧率,所以看起来可能掉帧比较严重,但是实际上并不会,因为这里我也注意了1s要绘制60帧的问题。阅读本文需要一些基本的View知识和会一些基础Kotlin语法。说实话,就知识点来说,跟Kotlin是没多大关系的,只要懂基本的语法就可以了。

理清思路

在动手前先要理一下思路,从以下几个方面来分析一下该采用什么方案来实现这个效果:

  • 工作线程:首先要想到的是:这个下雨的效果需要通过不停的绘制来实现,如果在主线程做这个操作,很有可能会阻塞主线程,导致ANR或者异常卡顿。所以需要一个能在子线程进行绘制的View,毫无疑问SurfaceView可以满足这个需求。

  • 如何实现:分析一下一颗雨滴的实现。首先,简单的效果其实可以用画线的方式代替。并不是每个人都有写轮眼,动态视力那么好的,一旦动起来谁还知道他是条线还是雨滴……当然了,canvas绘制的api有很多,并不一定非要用这种方式来实现。所以在在设计类的时候我们将draw的方法设置成可以让子类复写就可以了,你不满意我的实现?没问题,我给你改的自由~

  • 下落的实现:让雨滴动起来,有两种方式,一种是纯按坐标来绘制,另外一种是利用属性动画,自己重写估值器,动态改变y值。最终我还是采用了前一种方案,后一种属性动画的方案我为什么放弃了呢?原因是:这里的绘制的方式是靠外部不断的触发绘制事件来实现动态绘制的,很显然第一种方式更加符合这里的情况。

以上就是我初期的一些关于实现的思考了,接下来是代码实现分析。

代码实现分析

先放代码结构图:

如何使用SurfaceView实现下雨与下雪动画效果

代码结构

WeatherShape所有天气的父类,Rain和Snow是两个具体实现类。

看一下父类的代码:

package com.xiasuhuei321.gank_kotlin.customview.weatherimport Android.graphics.Canvasimport android.graphics.Paintimport android.graphics.PointFimport com.xiasuhuei321.gank_kotlin.contextimport com.xiasuhuei321.gank_kotlin.extension.getScreenWidthimport java.util.*abstract class WeatherShape(val start: PointF, val end: PointF) { open var TAG = "WeatherShape"  var isInUse = false  var isRandom = false  var speed = 0.05f  var width = 5f var shapeAlpha = 100 var paint = Paint().apply {  strokeWidth = width  isAntiAlias = true  alpha = alpha } // 总共下落的时间 var lastTime = 0L // 原始x坐标位置 var originX = 0f  abstract fun getAcceleration(): Float  open fun draw(canvas: Canvas) {  if (!isInUse) {   lastTime += randomPre()   initStyle()   isInUse = true  } else {   drawWhenInUse(canvas)  } }  abstract fun drawWhenInUse(canvas: Canvas)  open fun initStyle() {  val random = Random()  // 获取随机透明度  shapeAlpha = random.nextInt(155) + 50  // 获得起点x偏移  val translateX = random.nextInt(10).toFloat() + 5  if (!isRandom) {   start.x = translateX + originX   end.x = translateX + originX  } else {   // 如果是随机Shape,将x坐标随机范围扩大到整个屏幕的宽度   val randomWidth = random.nextInt(context.getScreenWidth())   start.x = randomWidth.toFloat()   end.x = randomWidth.toFloat()  }  speed = randomSpeed(random)  // 初始化length的工作留给之后对应的子类去实现  // 初始化color也留给子类去实现  paint.apply {   alpha = shapeAlpha   strokeWidth = width   isAntiAlias = true  }  // 如果有什么想要做的,刚好可以在追加上完成,就使用这个函数  wtc(random) }  open fun wtc(random:Random): Unit { } abstract fun randomSpeed(random: Random): Float  open fun randomPre(): Long {  val random = Random()  val pre = random.nextInt(1000).toLong()  return pre }}

说起这个代码,恩,还是经历过一番重构的……周六去找同学玩的路上顺便重构了一下,将一些可以放到基类中的操作都抽取到了基类中。这样虽然灵活不足,但是子类可以很方便的通过继承实现一个需要类似功能的东西,就比如这里的下雨和下雪。顺便吐槽一下……我注释的风格不太好,中英混搭……如果你仔细观察,可以看到gif中的雨点或者雪花形态可能都有一些些的不一样,是的,每一滴雨和雪花,都经过了一些随机的转变。

里面比较重要的两个属性是isInUse和isRandom,本来想用一个容器来作为Shape的管理类,统一管理,但是这样肯定会让使用和复用的流程更加复杂。最后还是决定用简单一点的方法,Shape内部保存一个使用状态和是否是随机的。isRandoma表示这个Shape是否是随机的,随机在目前的代码中会体现在Shape的x坐标上。如果随机标识是true,那么x坐标将是0 ~ ScreenWidth中的任意值。那么不是随机的呢?在我的实现中,同一类Shape将会被分为两类,一类常量组。会拥有相对固定的x值,但是也会有10~15px的随机偏移。另一类就是随机组,x值全屏自己随机,这样就尽量让屏幕各处都有雨滴(雪花)但会有疏密之别。initStyle就是这一随机的过程,有兴趣可以看看实现~

start和end是Shape的左上角点和右下角点,如果你对于Cavans的api有了解,就应该知道通过对start和end的转换和计算,可以绘制出大部分的形状。

接下来看一下具体实现的Snow类:

package com.xiasuhuei321.gank_kotlin.customview.weatherimport android.graphics.*import com.xiasuhuei321.gank_kotlin.contextimport com.xiasuhuei321.gank_kotlin.extension.getScreenHeightimport java.util.*class Snow(start: PointF, end: PointF) : WeatherShape(start, end) {  var center = calcCenter()  var radius = 10f override fun getAcceleration(): Float {  return 0f } override fun drawWhenInUse(canvas: Canvas) {  // 通过圆心与半径确定圆的位置及大小  val distance = speed * lastTime  center.y += distance  start.y += distance  end.y += distance  lastTime += 16  canvas.drawCircle(center.x, center.y, radius, paint)  if (end.y > context.getScreenHeight()) clear() } fun calcCenter(): PointF {  val center = PointF(0f, 0f)  center.x = (start.x + end.x) / 2f  center.y = (start.y + end.y) / 2f  return center } override fun randomSpeed(random: Random): Float {  // 获取随机速度0.005 ~ 0.01  return (random.nextInt(5) + 5) / 1000f } override fun wtc(random: Random) {  // 设置颜色渐变  val shader = RadialGradient(center.x, center.y, radius,    Color.parseColor("#FFFFFF"), Color.parseColor("#D1D1D1"),    Shader.TileMode.CLAMP)  // 外部设置的起始点其实并不对,先计算出半径  radius = random.nextInt(10) + 15f  // 根据半径计算start end  end.x = start.x + radius  end.y = start.y + radius  // 计算圆心  calcCenter()  paint.apply {   setShader(shader)  } } fun clear() {  isInUse = false  lastTime = 0  start.y = -radius * 2  end.y = 0f  center = calcCenter() }}

这个类只要理解了圆心的计算和绘制,基本也就没什么东西了。首先排除干扰项,getAcceleration这玩意在设计之初是用来通过加速度计算路程的,后来发现……算了,还是匀速吧……于是都return 0f了。这里wtc()函数和drawWhenInUse可能会看的你一脸懵逼,什么函数名,drawWhenInUse倒是见名知意,这wtc()是什么玩意?这里wtc是相当于一种追加初始化,完全状态的函数名应该是wantToChange() 。这些个函数调用流程是这样的:

如何使用SurfaceView实现下雨与下雪动画效果
流程图

其中draw(canvas)是父类的方法,对供外部调用的方法,在isInUse标识位为false时对Shape进行初始化操作,具体的就是调用initStyle()方法,而wtc()则会在initStyle()方法的最后调用。如果你有什么想要追加的初始化,可以通过这个函数实现。而drawWhenInUse(canvas)方法则是需要实现动态绘制的函数了。我这里就是在wtc()函数中进行了一些初始化操作,并且根据圆的特性重新计算了start、end和圆心。

接下来,就看看我们到底是怎么把这些充满个性(口胡)的雪绘制到屏幕上:

package com.xiasuhuei321.gank_kotlin.customview.weatherimport android.content.Contextimport android.graphics.Canvasimport android.graphics.Colorimport android.graphics.PixelFORMatimport android.graphics.PorterDuffimport android.util.AttributeSetimport android.view.SurfaceHolderimport android.view.SurfaceViewimport com.xiasuhuei321.gank_kotlin.extension.LogUtilimport java.lang.Exceptionclass WeatherView(context: Context, attributeSet: AttributeSet?, defaultStyle: Int) :  SurfaceView(context, attributeSet, defaultStyle), SurfaceHolder.Callback { private val TAG = "WeatherView" constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0) constructor(context: Context) : this(context, null, 0) // 低级并发,Kotlin中支持的不是很好,所以用一下黑科技 val lock = Object() var type = Weather.RAIN var weatherShapePool = WeatherShapePool() @Volatile var canRun = false @Volatile var threadQuit = false var thread = Thread {  while (!threadQuit) {   if (!canRun) {    synchronized(lock) {     try {      LogUtil.i(TAG, "条件尚不充足,阻塞中...")      lock.wait()     } catch (e: Exception) {     }    }   }   val startTime = System.currentTimeMillis()   try {    // 正式开始表演    val canvas = holder.lockCanvas()    if (canvas != null) {     canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)     draw(canvas, type, startTime)    }    holder.unlockCanvasAndPost(canvas)    val drawTime = System.currentTimeMillis() - startTime    // 平均16ms一帧才能有顺畅的感觉    if (drawTime < 16) {     Thread.sleep(16 - drawTime)    }   } catch (e: Exception) {//    e.printStackTrace()   }  } }.apply { name = "WeatherThread" } override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {  // surface发生了变化//  canRun = true } override fun surfaceDestroyed(holder: SurfaceHolder?) {  // 在这里释放资源  canRun = false  LogUtil.i(TAG, "surfaceDestroyed") } override fun surfaceCreated(holder: SurfaceHolder?) {  threadQuit = false  canRun = true  try {   // 如果没有执行wait的话,这里notify会抛异常   synchronized(lock) {    lock.notify()   }  } catch (e: Exception) {   e.printStackTrace()  } } init {  LogUtil.i(TAG, "init开始")  holder.addCallback(this)  holder.setFormat(PixelFormat.RGBA_8888)//  initData()  setZOrderOnTop(true)//  setZOrderMediaOverlay(true)  thread.start() } private fun draw(canvas: Canvas, type: Weather, startTime: Long) {  // type什么的先放一边,先实现一个  weatherShapePool.drawSnow(canvas) } enum class Weather {  RAIN,  SNOW } fun onDestroy() {  threadQuit = true  canRun = true  try {   synchronized(lock) {    lock.notify()   }  } catch (e: Exception) {  } }}

init{}是kotlin中提供给我们用于初始化的代码块,在init进行了一些初始化操作并让线程start了。看一下线程中执行的代码,首先会判断一个叫做canRun的标识,这个标识会在surface被创建的时候置为true,否则将会通过一个对象让这个线程等待。而在surface被创建后,则会调用notify方法让线程重新开始工作。之后是进行绘制的工作,绘制前后会有一个计时的动作,计算时间是否小于16ms,如果不足,则让thread sleep 补足插值。因为16ms一帧的绘制速度就足够了,不需要绘制太快浪费资源。

这里可以看到我创建了一个Java的Object对象,主要是因为Kotlin本身对于一些并发原语支持的并不好。Kotlin中任何对象都是继承与Any,Any并没有wait、notify等方法,所以这里用了黑科技……创建了Java对象……

代码中关键代码绘制调用了WeatherShapePool的drawRain(canvas)方法,最后在看一下这个类:

package com.xiasuhuei321.gank_kotlin.customview.weatherimport android.graphics.Canvasimport android.graphics.PointFimport com.xiasuhuei321.gank_kotlin.contextimport com.xiasuhuei321.gank_kotlin.extension.getScreenWidthclass WeatherShapePool { val constantRain = ArrayList<Rain>() val randomRain = ArrayList<Rain>() val constantSnow = ArrayList<Snow>() val randomSnow = ArrayList<Snow>() init {  // 初始化  initData()  initSnow() } private fun initData() {  val space = context.getScreenWidth() / 20  var currentSpace = 0f  // 将其均匀的分布在屏幕x方向上  for (i in 0..19) {   val rain = Rain(PointF(currentSpace, 0f), PointF(currentSpace, 0f))   rain.originLength = 20f   rain.originX = currentSpace   constantRain.add(rain)   currentSpace += space  }  for (j in 0..9) {   val rain = Rain(PointF(0f, 0f), PointF(0f, 0f))   rain.isRandom = true   rain.originLength = 20f   randomRain.add(rain)  } } fun drawRain(canvas: Canvas) {  for (r in constantRain) {   r.draw(canvas)  }  for (r in randomRain) {   r.draw(canvas)  } } private fun initSnow(){  val space = context.getScreenWidth() / 20  var currentSpace = 0f  // 将其均匀的分布在屏幕x方向上  for (i in 0..19) {   val snow = Snow(PointF(currentSpace, 0f), PointF(currentSpace, 0f))   snow.originX = currentSpace   snow.radius = 20f   constantSnow.add(snow)   currentSpace += space  }  for (j in 0..19) {   val snow = Snow(PointF(0f, 0f), PointF(0f, 0f))   snow.isRandom = true   snow.radius = 20f   randomSnow.add(snow)  } } fun drawSnow(canvas: Canvas){  for(r in constantSnow){   r.draw(canvas)  }  for (r in randomSnow){   r.draw(canvas)  } }}

这个类还是比较简单的,只是一个单纯的容器,至于叫Pool……因为刚开始自己想的是自己管理回收复用之类的,所以起了个名叫Pool,后来感觉这玩意好像不用实现的这么复杂……

关于“如何使用SurfaceView实现下雨与下雪动画效果”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

--结束END--

本文标题: 如何使用SurfaceView实现下雨与下雪动画效果

本文链接: https://www.lsjlt.com/news/221103.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何使用SurfaceView实现下雨与下雪动画效果
    这篇文章将为大家详细讲解有关如何使用SurfaceView实现下雨与下雪动画效果,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。先看一下最终完成的效果图:下雨.gif这里比较懒……第二个图片中还是降雨……不...
    99+
    2023-05-30
    surfaceview
  • 如何使用Android实现红包雨动画效果
    这篇文章主要介绍如何使用Android实现红包雨动画效果,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!红包雨关于实现上面红包雨效果步骤如下:创建一个红包实体类public class RedPac...
    99+
    2023-05-30
    android
  • Python如何实现代码雨动画效果
    本篇内容介绍了“Python如何实现代码雨动画效果”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!代码如下import sysimp...
    99+
    2023-07-04
  • CSS3 如何实现图形下落动画效果
    这篇文章主要讲解了“CSS3 如何实现图形下落动画效果”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“CSS3 如何实现图形下落动画效果”吧!先看效果实现代码<div clas...
    99+
    2023-06-08
  • 使用javascript如何实现一个下雪特效
    使用javascript如何实现一个下雪特效?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。JavaScript的作用是什么1、能够嵌入动态文本于HTML页面。2、对浏览器事件做...
    99+
    2023-06-14
  • 怎么使用css3实现动画下拉菜单效果
    这篇文章给大家分享的是有关怎么使用css3实现动画下拉菜单效果的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。.site-navigation ul ul{min-width: 1...
    99+
    2022-10-19
  • 使用canvas怎么实现一个下雨效果的示例
    本篇文章为大家展示了使用canvas怎么实现一个下雨效果的示例,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。<!doctype html><html lang=...
    99+
    2023-06-09
  • 如何在Android中使用SurfaceView制作一个天气动画效果
    如何在Android中使用SurfaceView制作一个天气动画效果?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。首先是最终实现的效果图:初识 SurfaceV...
    99+
    2023-05-31
    android surfaceview sur
  • 如何使用CSS3实现动画效果
    这篇文章主要为大家展示了“如何使用CSS3实现动画效果”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何使用CSS3实现动画效果”这篇文章吧。浏览器支持Inte...
    99+
    2022-10-19
  • 微信小程序如何实现CSS3动画下拉菜单效果
    这篇文章主要介绍了微信小程序如何实现CSS3动画下拉菜单效果,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。微信小程序没有自带的下拉菜单组件,...
    99+
    2022-10-19
  • 怎么在Android中使用ScrollView实现一个下拉弹回动画效果
    本篇文章给大家分享的是有关怎么在Android中使用ScrollView实现一个下拉弹回动画效果,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Android是什么Android...
    99+
    2023-05-30
    android scrollview
  • 如何使用CSS 、JS实现浪漫流星雨动画
    小编给大家分享一下如何使用CSS 、JS实现浪漫流星雨动画,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!   HTML   ...
    99+
    2022-10-19
  • 如何使用HTML5实现炫丽的流星雨效果
    这期内容当中小编将会给大家带来有关如何使用HTML5实现炫丽的流星雨效果,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。 徐家破镜昏如雾。半面...
    99+
    2022-10-19
  • vue如何实现下拉框二级联动效果
    这篇文章主要介绍vue如何实现下拉框二级联动效果,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1、实现效果2、后端返回的数据格式"list": [   &n...
    99+
    2023-06-25
  • JavaScript动画原理之如何使用js进行动画效果的实现
    目录前言1.动画原理2.动画函数的封装3.给不同元素添加定时器4.缓动动画原理5.给动画添加回调函数6.动画函数的使用写在最后前言 动画对于我们来说都不陌生,css里面就有很多动画,...
    99+
    2023-05-14
    js动画效果有哪些 js动画效果代码案例 javascript动画教程
  • 如何使用CSS3实现超炫的Loading动画效果
    今天小编给大家分享一下如何使用CSS3实现超炫的Loading动画效果的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,...
    99+
    2022-10-19
  • 如何使用CSS实现outline切换的动画效果
    这篇文章主要介绍“如何使用CSS实现outline切换的动画效果”,在日常操作中,相信很多人在如何使用CSS实现outline切换的动画效果问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望...
    99+
    2022-10-19
  • 如何使用CSS实现酷炫充电动画效果
    这篇文章给大家分享的是有关如何使用CSS实现酷炫充电动画效果的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。css是什么意思css是一种用来表现HTML或XML等文件样式的计算机语言,主要是用来设计网页的样式,使网...
    99+
    2023-06-14
  • css如何实现元素显示与隐藏动画效果
    这篇文章主要讲解了“css如何实现元素显示与隐藏动画效果”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“css如何实现元素显示与隐藏动画效果”吧! ...
    99+
    2022-10-19
  • ajax如何实现三级联动下拉菜单效果
    这篇文章给大家分享的是有关ajax如何实现三级联动下拉菜单效果的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。ajax写三级联动,先写一个文件类吧,以后用的时候直接调用即可;来找一...
    99+
    2022-10-19
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作