代码重构的意义
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 } }
说到给代码做个“大整容”,老程序员们肯定不陌生!这就好比是穿旧了但不过时的衣服,随着需求一变再变、项目层层升级,代码居然也能堆成山,看着复杂得很,改起来也是不胜其烦!咋办?重新理一理代码,让它变得简单清爽点、容易读懂也好搞些改动。这样做可不止能提高下代码的质量、降低些出错的风险,以后有啥变化也不怕!
平时码字的时候,总得大修大补。有次周末约朋友见面,就当是个编程工作坊,顺便把代码梳理好,能塞进基类的地方就尽量塞进去。这么做虽然少了点灵活性,但对子子孙孙们来说可是方便多了,就比如咱们现在聊的天气问题。
继承带来的多态
咱们把通用的动作放到基类里,这样宝宝们(也就是子类)就知道怎么搞定这种事,然后就能专注于自己那部分工作。这叫做继承,还能让咱们拥有叫作多态性的神奇功能!程序运行时,看看对象属于哪个子类,直接调用那些方法,就能满足各种需求!
来,把常用代码放基本类里面,下雨和下雪就能自动定做特殊功能!这样还能继承,以后要搞更多天气效果,直接往上添就行。
属性设计与随机特性
我们来看看这个程序,这里面有两个主要的因素,isInUse和isRandom。你们可能已经留意到了,isRandom是关于形状乱不乱的,或者说是在x轴上的分布。每个小水珠和雪花经过了随机生成,这样整个画面就变得更加生动有趣!
我们把控制方法简单化了~坐标系不用特意放盒子里,就让它随意记录形状。虽然有点自由度的损失,但结合实际需求和成本考虑,只要功能做出来就好了。
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() } }
Shape内部设计与初始化
我们这儿有两个不同的shape分类:一个叫做常量组,另一个叫做随机组。initStyle嘛就是给那个随机组设计的初始设置的小助手;然后那个 wt c(),这可是个待命的快速启动器,只要 init style 执行完了,它就立马接力去完成后续工作!
想弄个动态绘图?那就在 drawWhenInUse 里搞掂呗!就是稍微调调圆心这些东西,然后借用爸爸 draw(canvas)的功能就好。
Snow类实现与圆心计算
我常玩那个Snow游戏主要就是找个圆圆心儿,瞎画点儿啥玩意儿之类的。至于那个叫getAcceleration的,以前是用来算加速度让东西动起来的,结果后来发现不用这功能了,所以我就把它改成直接返回0。
理解啦吗?wtc()嘿~它可是我们做基本调整的神奇小助手!搞定大前提,它就能立马帮您算得出start、end和圆心等参数!到了后期, drawWhenInUse这个小伙伴负责动态画图,通常就在wtc()把东西都准备好了以后再出来为大家服务。
线程控制与绘制逻辑
你知道吗?我們的代碼裡面還有個不小的步驟,就是設置這個叫” logic pipeline”的玩意兒!只需要在 init {}那里手動搞定,然後把它啟動起來就行了。這麼理解,它的主要任務就是檢查 canRun 的狀態,如果 surface 已經建好了,就繼續干活;要是還沒弄好,那就在原地等著就對了。
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) { } } }
画图这事儿,主要是靠draw(canvas)来搞定哒~关键还要把握好时间!不过,Kotlin对并发操作好像有点麻烦,那我们就用老本行Java,那些wait、notify的方法正好能用上!
WeatherShapePool容器设计
那个SnowShapePool,其实就是个大篮子,什么形状都能放进去。刚开始想把回收和再利用这些资源弄得有趣点儿,所以就叫它Pool,就跟小池塘似的。后来发现其实没那么复杂不用琢磨太多。
嘿老铁们,现在这个项目有点小不足!不管你想说啥,直接发给我邮件或私信就好。你每提个意见都是在帮我们的代码更上一层楼!
评论0