应用SurfaceView实现下雨与下雪动画的结果-
前言
比来打算做一波东西稳固一下本人近期所学所得。话未几说,先看一下终究完成的结果图:
看一下父类的代码:
package com.xiasuhuei321.gank_kotlin.customview.weather import android.graphics.Canvas import android.graphics.Paint import android.graphics.PointF import com.xiasuhuei321.gank_kotlin.context import com.xiasuhuei321.gank_kotlin.extension.getScreenWidth import java.util.* /** * Created by xiasuhuei321 on 2017/9/5. * author:luo * e-mail:xiasuhuei321@163.com * * desc: All shape's parent class.It describes a shape will have * what feature.It's draw flows are: * 1.Outside the class init some value such as the start and the * end point. * 2.Invoke draw(Canvas) method, in this method, there are still * two flows: * 1) Get random value to init paint, this will affect the shape * draw style. * 2) When the shape is not used, invoke init method, and when it * is not used invoke drawWhenInUse(Canvas) method. It should be * override by user and to implement draw itself. * */ abstract class WeatherShape(val start: PointF, val end: PointF) { open var TAG = "WeatherShape" /** * 可否是正在被运用的状态 */ var isInUse = false /** * 可否是随机刷新的Shape */ var isRandom = false /** * 着落的速度,特指垂直标的目的,子类可以实现本人水平标的目的的速度 */ var speed = 0.05f /** * shape的宽度 */ var width = 5f var shapeAlpha = 100 var paint = Paint().apply { strokeWidth = width isAntiAlias = true alpha = alpha } // 总共着落的工夫 var lastTime = 0L // 原始x坐标位置 var originX = 0f /** * 依据本人的法则盘算加快度,要是是匀速直接 return 0 */ abstract fun getAcceleration(): Float /** * 绘制本身,这里在Shape是非运用的时候进行一些初始化操纵 */ open fun draw(canvas: Canvas) { if (!isInUse) { lastTime += randomPre() initStyle() isInUse = true } else { drawWhenInUse(canvas) } } /** * Shape在运用的时候调取此办法 */ abstract fun drawWhenInUse(canvas: Canvas) /** * 初始化Shape格调 */ 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) } /** * Empty body, this will be invoke in initStyle * method.If current initStyle method can satisfy your need * but you still add something, by override this method * will be a good idea to solve the problem. */ open fun wtc(random:Random): Unit { } abstract fun randomSpeed(random: Random): Float /** * 猎取一个随机的提早量,让shape在竖屏上有一个初始的偏移 */ 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.weather import android.graphics.* import com.xiasuhuei321.gank_kotlin.context import com.xiasuhuei321.gank_kotlin.extension.getScreenHeight import java.util.* /** * Created by xiasuhuei321 on 2017/9/5. * author:luo * e-mail:xiasuhuei321@163.com */ 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()
。这些个函数调取流程是这样的:
其中draw(canvas)
是父类的办法,对供外部调取的办法,在isInUse标识位为false时对Shape进行初始化操纵,概括的就是调取initStyle()
办法,而wtc()
则会在initStyle()
办法的最后调取。要是你有什么想要追加的初始化,可以通过这个函数实现。而drawWhenInUse(canvas)
办法则是需要实现动态绘制的函数了。我这里就是在wtc()
函数中进行了一些初始化操纵,而且依据圆的特性从新盘算了start、end和圆心。
接下来,就看看我们到底是怎么把这些洋溢个性(口胡)的雪绘制到屏幕上:
package com.xiasuhuei321.gank_kotlin.customview.weather import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.PixelFormat import android.graphics.PorterDuff import android.util.AttributeSet import android.view.SurfaceHolder import android.view.SurfaceView import com.xiasuhuei321.gank_kotlin.extension.LogUtil import java.lang.Exception /** * Created by xiasuhuei321 on 2017/9/5. * author:luo * e-mail:xiasuhuei321@163.com */ class 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.weather import android.graphics.Canvas import android.graphics.PointF import com.xiasuhuei321.gank_kotlin.context import com.xiasuhuei321.gank_kotlin.extension.getScreenWidth /** * Created by xiasuhuei321 on 2017/9/7. * author:luo * e-mail:xiasuhuei321@163.com */ class WeatherShapePool { val constantRain = ArrayList() val randomRain = ArrayList () val constantSnow = ArrayList () val randomSnow = ArrayList () 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,后来感觉这玩意宛如不消实现的这么复杂……
总之,这玩意,会者不难,我的代码也非尽如人意,要是我有任何纰漏或者你有什么好的意见,都可以提出,邮件或者是在文章下评价最好。
以上就是本文的全部内容,但愿对大家的学习有所帮忙,更多相干内容请关注百分百源码网!