所有分类
  • 所有分类
  • 后端开发
代码重构:让代码焕然一新,轻松应对需求变化

代码重构:让代码焕然一新,轻松应对需求变化

WeatherShape所有天气的父类,Rain和Snow是两个具体实现类。这样虽然灵活不足,但是子类可以很方便的通过继承实现一个需要类似功能的东西,就比如这里的下雨和下雪。接下来,就看看我们到底是怎么把这些充满个性(口胡)的雪绘制到屏幕上

代码重构:让代码焕然一新,轻松应对需求变化

代码重构的意义

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,就跟小池塘似的。后来发现其实没那么复杂不用琢磨太多。

嘿老铁们,现在这个项目有点小不足!不管你想说啥,直接发给我邮件或私信就好。你每提个意见都是在帮我们的代码更上一层楼!

原文链接:https://www.icz.com/technicalinformation/web/javascript/2024/04/13247.html,转载请注明出处~~~
0

评论0

请先
注意:请收藏好网址www.icz.com,防止失联!站内免费资源持续上传中…!赞助我们
显示验证码
没有账号?注册  忘记密码?