真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何使用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫(huà)效果

這篇文章將為大家詳細(xì)講解有關(guān)如何使用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫(huà)效果,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

成都創(chuàng)新互聯(lián)客戶(hù)idc服務(wù)中心,提供成都移動(dòng)機(jī)房托管、成都服務(wù)器、成都主機(jī)托管、成都雙線(xiàn)服務(wù)器等業(yè)務(wù)的一站式服務(wù)。通過(guò)各地的服務(wù)中心,我們向成都用戶(hù)提供優(yōu)質(zhì)廉價(jià)的產(chǎn)品以及開(kāi)放、透明、穩(wěn)定、高性?xún)r(jià)比的服務(wù),資深網(wǎng)絡(luò)工程師在機(jī)房提供7*24小時(shí)標(biāo)準(zhǔn)級(jí)技術(shù)保障。

先看一下最終完成的效果圖:

如何使用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫(huà)效果
下雨.gif

這里比較懶……第二個(gè)圖片中還是降雨……不過(guò)這不是關(guān)鍵點(diǎn)……

如何使用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫(huà)效果

下雪.gif

錄制的mp4,轉(zhuǎn)成了gif。第一個(gè)gif設(shè)置了幀率,所以看起來(lái)可能掉幀比較嚴(yán)重,但是實(shí)際上并不會(huì),因?yàn)檫@里我也注意了1s要繪制60幀的問(wèn)題。閱讀本文需要一些基本的View知識(shí)和會(huì)一些基礎(chǔ)Kotlin語(yǔ)法。說(shuō)實(shí)話(huà),就知識(shí)點(diǎn)來(lái)說(shuō),跟Kotlin是沒(méi)多大關(guān)系的,只要懂基本的語(yǔ)法就可以了。

理清思路

在動(dòng)手前先要理一下思路,從以下幾個(gè)方面來(lái)分析一下該采用什么方案來(lái)實(shí)現(xiàn)這個(gè)效果:

  • 工作線(xiàn)程:首先要想到的是:這個(gè)下雨的效果需要通過(guò)不停的繪制來(lái)實(shí)現(xiàn),如果在主線(xiàn)程做這個(gè)操作,很有可能會(huì)阻塞主線(xiàn)程,導(dǎo)致ANR或者異常卡頓。所以需要一個(gè)能在子線(xiàn)程進(jìn)行繪制的View,毫無(wú)疑問(wèn)SurfaceView可以滿(mǎn)足這個(gè)需求。

  • 如何實(shí)現(xiàn):分析一下一顆雨滴的實(shí)現(xiàn)。首先,簡(jiǎn)單的效果其實(shí)可以用畫(huà)線(xiàn)的方式代替。并不是每個(gè)人都有寫(xiě)輪眼,動(dòng)態(tài)視力那么好的,一旦動(dòng)起來(lái)誰(shuí)還知道他是條線(xiàn)還是雨滴……當(dāng)然了,Canvas繪制的API有很多,并不一定非要用這種方式來(lái)實(shí)現(xiàn)。所以在在設(shè)計(jì)類(lèi)的時(shí)候我們將draw的方法設(shè)置成可以讓子類(lèi)復(fù)寫(xiě)就可以了,你不滿(mǎn)意我的實(shí)現(xiàn)?沒(méi)問(wèn)題,我給你改的自由~

  • 下落的實(shí)現(xiàn):讓雨滴動(dòng)起來(lái),有兩種方式,一種是純按坐標(biāo)來(lái)繪制,另外一種是利用屬性動(dòng)畫(huà),自己重寫(xiě)估值器,動(dòng)態(tài)改變y值。最終我還是采用了前一種方案,后一種屬性動(dòng)畫(huà)的方案我為什么放棄了呢?原因是:這里的繪制的方式是靠外部不斷的觸發(fā)繪制事件來(lái)實(shí)現(xiàn)動(dòng)態(tài)繪制的,很顯然第一種方式更加符合這里的情況。

以上就是我初期的一些關(guān)于實(shí)現(xiàn)的思考了,接下來(lái)是代碼實(shí)現(xiàn)分析。

代碼實(shí)現(xiàn)分析

先放代碼結(jié)構(gòu)圖:

如何使用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫(huà)效果

代碼結(jié)構(gòu)

WeatherShape所有天氣的父類(lèi),Rain和Snow是兩個(gè)具體實(shí)現(xiàn)類(lèi)。

看一下父類(lèi)的代碼:

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"

 /**
  * 是否是正在被使用的狀態(tài)
  */
 var isInUse = false

 /**
  * 是否是隨機(jī)刷新的Shape
  */
 var isRandom = false

 /**
  * 下落的速度,特指垂直方向,子類(lèi)可以實(shí)現(xiàn)自己水平方向的速度
  */
 var speed = 0.05f

 /**
  * shape的寬度
  */
 var width = 5f

 var shapeAlpha = 100

 var paint = Paint().apply {
  strokeWidth = width
  isAntiAlias = true
  alpha = alpha
 }

 // 總共下落的時(shí)間
 var lastTime = 0L
 // 原始x坐標(biāo)位置
 var originX = 0f

 /**
  * 根據(jù)自己的規(guī)則計(jì)算加速度,如果是勻速直接 return 0
  */
 abstract fun getAcceleration(): Float

 /**
  * 繪制自身,這里在Shape是非使用的時(shí)候進(jìn)行一些初始化操作
  */
 open fun draw(canvas: Canvas) {
  if (!isInUse) {
   lastTime += randomPre()
   initStyle()
   isInUse = true
  } else {
   drawWhenInUse(canvas)
  }
 }

 /**
  * Shape在使用的時(shí)候調(diào)用此方法
  */
 abstract fun drawWhenInUse(canvas: Canvas)

 /**
  * 初始化Shape風(fēng)格
  */
 open fun initStyle() {
  val random = Random()
  // 獲取隨機(jī)透明度
  shapeAlpha = random.nextInt(155) + 50
  // 獲得起點(diǎn)x偏移
  val translateX = random.nextInt(10).toFloat() + 5
  if (!isRandom) {
   start.x = translateX + originX
   end.x = translateX + originX
  } else {
   // 如果是隨機(jī)Shape,將x坐標(biāo)隨機(jī)范圍擴(kuò)大到整個(gè)屏幕的寬度
   val randomWidth = random.nextInt(context.getScreenWidth())
   start.x = randomWidth.toFloat()
   end.x = randomWidth.toFloat()
  }
  speed = randomSpeed(random)
  // 初始化length的工作留給之后對(duì)應(yīng)的子類(lèi)去實(shí)現(xiàn)
  // 初始化color也留給子類(lèi)去實(shí)現(xiàn)
  paint.apply {
   alpha = shapeAlpha
   strokeWidth = width
   isAntiAlias = true
  }
  // 如果有什么想要做的,剛好可以在追加上完成,就使用這個(gè)函數(shù)
  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

 /**
  * 獲取一個(gè)隨機(jī)的提前量,讓shape在豎屏上有一個(gè)初始的偏移
  */
 open fun randomPre(): Long {
  val random = Random()
  val pre = random.nextInt(1000).toLong()
  return pre
 }
}

說(shuō)起這個(gè)代碼,恩,還是經(jīng)歷過(guò)一番重構(gòu)的……周六去找同學(xué)玩的路上順便重構(gòu)了一下,將一些可以放到基類(lèi)中的操作都抽取到了基類(lèi)中。這樣雖然靈活不足,但是子類(lèi)可以很方便的通過(guò)繼承實(shí)現(xiàn)一個(gè)需要類(lèi)似功能的東西,就比如這里的下雨和下雪。順便吐槽一下……我注釋的風(fēng)格不太好,中英混搭……如果你仔細(xì)觀(guān)察,可以看到gif中的雨點(diǎn)或者雪花形態(tài)可能都有一些些的不一樣,是的,每一滴雨和雪花,都經(jīng)過(guò)了一些隨機(jī)的轉(zhuǎn)變。

里面比較重要的兩個(gè)屬性是isInUse和isRandom,本來(lái)想用一個(gè)容器來(lái)作為Shape的管理類(lèi),統(tǒng)一管理,但是這樣肯定會(huì)讓使用和復(fù)用的流程更加復(fù)雜。最后還是決定用簡(jiǎn)單一點(diǎn)的方法,Shape內(nèi)部保存一個(gè)使用狀態(tài)和是否是隨機(jī)的。isRandoma表示這個(gè)Shape是否是隨機(jī)的,隨機(jī)在目前的代碼中會(huì)體現(xiàn)在Shape的x坐標(biāo)上。如果隨機(jī)標(biāo)識(shí)是true,那么x坐標(biāo)將是0 ~ ScreenWidth中的任意值。那么不是隨機(jī)的呢?在我的實(shí)現(xiàn)中,同一類(lèi)Shape將會(huì)被分為兩類(lèi),一類(lèi)常量組。會(huì)擁有相對(duì)固定的x值,但是也會(huì)有10~15px的隨機(jī)偏移。另一類(lèi)就是隨機(jī)組,x值全屏自己隨機(jī),這樣就盡量讓屏幕各處都有雨滴(雪花)但會(huì)有疏密之別。initStyle就是這一隨機(jī)的過(guò)程,有興趣可以看看實(shí)現(xiàn)~

start和end是Shape的左上角點(diǎn)和右下角點(diǎn),如果你對(duì)于Cavans的api有了解,就應(yīng)該知道通過(guò)對(duì)start和end的轉(zhuǎn)換和計(jì)算,可以繪制出大部分的形狀。

接下來(lái)看一下具體實(shí)現(xiàn)的Snow類(lèi):

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) {

 /**
  * 圓心,用戶(hù)可以改變這個(gè)值
  */
 var center = calcCenter()

 /**
  * 半徑
  */
 var radius = 10f

 override fun getAcceleration(): Float {
  return 0f
 }

 override fun drawWhenInUse(canvas: Canvas) {
  // 通過(guò)圓心與半徑確定圓的位置及大小
  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 {
  // 獲取隨機(jī)速度0.005 ~ 0.01
  return (random.nextInt(5) + 5) / 1000f
 }

 override fun wtc(random: Random) {
  // 設(shè)置顏色漸變
  val shader = RadialGradient(center.x, center.y, radius,
    Color.parseColor("#FFFFFF"), Color.parseColor("#D1D1D1"),
    Shader.TileMode.CLAMP)
  // 外部設(shè)置的起始點(diǎn)其實(shí)并不對(duì),先計(jì)算出半徑
  radius = random.nextInt(10) + 15f
  // 根據(jù)半徑計(jì)算start end
  end.x = start.x + radius
  end.y = start.y + radius
  // 計(jì)算圓心
  calcCenter()

  paint.apply {
   setShader(shader)
  }
 }

 fun clear() {
  isInUse = false
  lastTime = 0
  start.y = -radius * 2
  end.y = 0f

  center = calcCenter()
 }
}

這個(gè)類(lèi)只要理解了圓心的計(jì)算和繪制,基本也就沒(méi)什么東西了。首先排除干擾項(xiàng),getAcceleration這玩意在設(shè)計(jì)之初是用來(lái)通過(guò)加速度計(jì)算路程的,后來(lái)發(fā)現(xiàn)……算了,還是勻速吧……于是都return 0f了。這里wtc()函數(shù)和drawWhenInUse可能會(huì)看的你一臉懵逼,什么函數(shù)名,drawWhenInUse倒是見(jiàn)名知意,這wtc()是什么玩意?這里wtc是相當(dāng)于一種追加初始化,完全狀態(tài)的函數(shù)名應(yīng)該是wantToChange() 。這些個(gè)函數(shù)調(diào)用流程是這樣的:

如何使用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫(huà)效果
流程圖

其中draw(canvas)是父類(lèi)的方法,對(duì)供外部調(diào)用的方法,在isInUse標(biāo)識(shí)位為false時(shí)對(duì)Shape進(jìn)行初始化操作,具體的就是調(diào)用initStyle()方法,而wtc()則會(huì)在initStyle()方法的最后調(diào)用。如果你有什么想要追加的初始化,可以通過(guò)這個(gè)函數(shù)實(shí)現(xiàn)。而drawWhenInUse(canvas)方法則是需要實(shí)現(xiàn)動(dòng)態(tài)繪制的函數(shù)了。我這里就是在wtc()函數(shù)中進(jìn)行了一些初始化操作,并且根據(jù)圓的特性重新計(jì)算了start、end和圓心。

接下來(lái),就看看我們到底是怎么把這些充滿(mǎn)個(gè)性(口胡)的雪繪制到屏幕上:

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)

 // 低級(jí)并發(fā),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 {
    // 正式開(kāi)始表演
    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一幀才能有順暢的感覺(jué)
    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發(fā)生了變化
//  canRun = true

 }

 override fun surfaceDestroyed(holder: SurfaceHolder?) {
  // 在這里釋放資源
  canRun = false
  LogUtil.i(TAG, "surfaceDestroyed")
 }

 override fun surfaceCreated(holder: SurfaceHolder?) {
  threadQuit = false
  canRun = true
  try {
   // 如果沒(méi)有執(zhí)行wait的話(huà),這里notify會(huì)拋異常
   synchronized(lock) {
    lock.notify()
   }
  } catch (e: Exception) {
   e.printStackTrace()
  }
 }

 init {
  LogUtil.i(TAG, "init開(kāi)始")
  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什么的先放一邊,先實(shí)現(xiàn)一個(gè)
  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進(jìn)行了一些初始化操作并讓線(xiàn)程start了??匆幌戮€(xiàn)程中執(zhí)行的代碼,首先會(huì)判斷一個(gè)叫做canRun的標(biāo)識(shí),這個(gè)標(biāo)識(shí)會(huì)在surface被創(chuàng)建的時(shí)候置為true,否則將會(huì)通過(guò)一個(gè)對(duì)象讓這個(gè)線(xiàn)程等待。而在surface被創(chuàng)建后,則會(huì)調(diào)用notify方法讓線(xiàn)程重新開(kāi)始工作。之后是進(jìn)行繪制的工作,繪制前后會(huì)有一個(gè)計(jì)時(shí)的動(dòng)作,計(jì)算時(shí)間是否小于16ms,如果不足,則讓thread sleep 補(bǔ)足插值。因?yàn)?6ms一幀的繪制速度就足夠了,不需要繪制太快浪費(fèi)資源。

這里可以看到我創(chuàng)建了一個(gè)Java的Object對(duì)象,主要是因?yàn)镵otlin本身對(duì)于一些并發(fā)原語(yǔ)支持的并不好。Kotlin中任何對(duì)象都是繼承與Any,Any并沒(méi)有wait、notify等方法,所以這里用了黑科技……創(chuàng)建了Java對(duì)象……

代碼中關(guān)鍵代碼繪制調(diào)用了WeatherShapePool的drawRain(canvas)方法,最后在看一下這個(gè)類(lèi):

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)
  }
 }
}

這個(gè)類(lèi)還是比較簡(jiǎn)單的,只是一個(gè)單純的容器,至于叫Pool……因?yàn)閯傞_(kāi)始自己想的是自己管理回收復(fù)用之類(lèi)的,所以起了個(gè)名叫Pool,后來(lái)感覺(jué)這玩意好像不用實(shí)現(xiàn)的這么復(fù)雜……

關(guān)于“如何使用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫(huà)效果”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。


網(wǎng)頁(yè)標(biāo)題:如何使用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫(huà)效果
網(wǎng)站路徑:http://weahome.cn/article/igsojs.html

其他資訊

在線(xiàn)咨詢(xún)

微信咨詢(xún)

電話(huà)咨詢(xún)

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部