接下來當我們點擊按鈕
我們來打個API 並告知道有沒有猜對
來看看API吧
請使用POST方式傳送資料,API會把結果告訴你
URL
Method:
POST
URL Params
None
Data Params
Required:
choose=選擇的項目
balance=目前的結餘的點數
Success Response:
end even 代表走了四條橫線 odd代表三條
{"error_code":0,"error_msg":"","info":{"balance":"1200","is_win":true,"result":{"end":"even","stairs":"3","start":"left"}}}
Error Response:
{"error_code":10001,"error_msg":"Please POST 'choose' and 'balance' property","info":{"balance":"0","is_win":false,"result":{"end":"","stairs":"","start":""}}}
接下來撰寫按鈕點擊後的程式吧
首先產生一個Class來儲存分數
此時根目錄下會跑出Player.swift
然後撰寫成這樣
import UIKit
class Player: NSObject {
var point: Int = 1000
}
同樣的做法 我們還需要再一個
OrderResponse.swift
import UIKit
class OrderResponse: Decodable {
let error_code: Int
let error_msg: String
let info: OrderInfoResponse
}
struct OrderInfoResponse: Decodable { // or Decodable
let balance: String
let is_win: Bool
let result: ResultResponse
}
struct ResultResponse: Decodable { // or Decodable
let end: String
let stairs: String
let start: String
}
Decodable 是用來解析API的JSON資料用的
此時回到 ViewController.swift
撰寫order方法, 按住 control 從按鈕分別拖拉到方法上
// 按鈕
@IBOutlet weak var left_blue: UIButton!
@IBOutlet weak var right_blue: UIButton!
@IBOutlet weak var left_red: UIButton!
@IBOutlet weak var right_red: UIButton!
var player = Player()
@IBAction func choose(_ sender: UIButton) {
// 使用 URLSession 打api
let session = URLSession(configuration: .default)
var request = URLRequest(url: URL(string: "http://pinyi.ami-shake.com/gg_order.php")!)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let data = ["choose": self.getChoose(sender), "balance": String(self.player.point)]
do{
request.httpBody = try JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions())
}catch let error{
print("passer data error")
print(error)
}
session.dataTask(with: request) { data, response, error in
if let data = data {
do {
let res = try JSONDecoder().decode(OrderResponse.self, from: data)
執行動畫播放
判斷輸贏 增減分數
} catch let error {
print("error")
print(error)
}
}
}.resume()
}
fileprivate func getChoose(_ sender: UIButton) -> String {
if(sender == self.left_red){
return "left_even"
}
if(sender == self.left_blue){
return "left_odd"
}
if(sender == self.right_blue){
return "right_odd"
}
if(sender == self.right_red){
return "right_even"
}
return ""
}
接下來撰寫播放結果動畫的方法
API的 start 來決定打開雞蛋是左邊還是右邊
打開雞蛋後 判斷是否要顯示第四條線
然後讓雲朵隱藏起來
雞蛋依照線條開始跑
跑完之後更新分數
並重新讓動畫恢復播放前的狀態
enum EggWapperDirection {
case Left
case Right
}
enum HatColor {
case Red
case Blue
}
func playResult(_ eggWapperDirection: EggWapperDirection, _ hatColor: HatColor, _ isWin: Bool, _ newPoint: String)-> Void {
var hasLastLine = true
if(
(eggWapperDirection == EggWapperDirection.Right && hatColor == HatColor.Blue) ||
(eggWapperDirection == EggWapperDirection.Left && hatColor == HatColor.Red)
){
// 這種情況下只有三條線
hasLastLine = false
}
let eggshellAni = self.openEggAni(eggWapperDirection)
let cloudAni = self.displayCloud(false)
let playEggAni = self.playEggAniOnLine(eggWapperDirection, hasLastLine)
playEggAni.addCompletion({ _ in
self.player.point = newPoint
self.pointLabel.text = "Point: \(self.player.point)"
self.reSetAni()
})
cloudAni.addCompletion({ _ in
playEggAni.startAnimation()
})
displayLastLine(hasLastLine)
eggshellAni.startAnimation()
cloudAni.startAnimation()
}
func openEggAni(_ eggWapperDirection: EggWapperDirection) -> UIViewPropertyAnimator {
return UIViewPropertyAnimator(duration: 0.5, curve: .linear, animations: {
let egg: UIView! = eggWapperDirection == EggWapperDirection.Right ? self.eggshell_right : self.eggshell_left
egg.transform = CGAffineTransform(translationX: 30, y: -30).rotated(by: 30 * CGFloat.pi / 180 )
egg.alpha = 0
})
}
func displayCloud(_ isShow: Bool) -> UIViewPropertyAnimator{
let CloudAni = UIViewPropertyAnimator(duration: isShow ? 0 : 1,curve: .linear, animations: {
self.Cloud.alpha = isShow ? 1 : 0
})
return CloudAni
}
func playEggAniOnLine(_ eggWapperDirection: EggWapperDirection, _ hasLastLine: Bool) -> UIViewPropertyAnimator {
let eggWapperAni = UIViewPropertyAnimator(duration: 3, curve: .linear)
eggWapperAni.addAnimations {
UIView.animateKeyframes(withDuration: 0, delay: 0, animations: {
let eggRunLineKeyFrameOptions = self.getEggRunLineKeyFrameOptions(hasLastLine);
for option in eggRunLineKeyFrameOptions {
UIView.addKeyframe(
withRelativeStartTime: option.startTime,
relativeDuration: 0.1,
animations: {
if(eggWapperDirection == EggWapperDirection.Left){
self.eggWapperLeft.transform = CGAffineTransform(translationX: option.translationX, y: option.translationY)
} else {
self.eggWapperRight.transform = CGAffineTransform(translationX: -option.translationX, y: option.translationY)
}
})
}
})
}
return eggWapperAni
}
fileprivate func getEggRunLineKeyFrameOptions(_ hasLastLine: Bool) -> Array<KeyFrameOptionItem>{
var keyFrameOptions: Array<KeyFrameOptionItem> = []
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.0, translationX: 0, translationY: self.lineWapperHeight*0.2 + 50, rotated: 0, scaledX: 1.0))
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.1, translationX: self.lineWapperWidth, translationY: self.lineWapperHeight*0.2 + 50, rotated: 0, scaledX: 1.0))
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.2, translationX: self.lineWapperWidth, translationY: self.lineWapperHeight*0.4 + 50, rotated: 0, scaledX: 1.0))
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.3, translationX: 0, translationY: self.lineWapperHeight*0.4 + 50, rotated: 0, scaledX: 1.0))
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.4, translationX: 0, translationY: self.lineWapperHeight*0.6 + 50, rotated: 0, scaledX: 1.0))
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.5, translationX: self.lineWapperWidth, translationY: self.lineWapperHeight*0.6 + 50, rotated: 0, scaledX: 1.0))
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.6, translationX: self.lineWapperWidth, translationY: self.lineWapperHeight*0.8 + 50, rotated: 0, scaledX: 1.0))
if(hasLastLine){
// 走第四條線
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: 0, translationY: self.lineWapperHeight*0.8 + 50, rotated: 0, scaledX: 1.0))
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.8, translationX: 0, translationY: self.lineWapperHeight*1 + 10, rotated: 0, scaledX: 1.0))
} else {
keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: self.lineWapperWidth, translationY: self.lineWapperHeight*1 + 10, rotated: 0, scaledX: 1.0))
}
return keyFrameOptions
}
func displayLastLine(_ isShow: Bool) {
self.lastLineLayer?.isHidden = !isShow
self.lastLineInLineLayer?.isHidden = !isShow
}
func reSetAni() -> Void {
self.eggshell_left.transform = .identity
self.eggshell_left.alpha = 1
self.eggshell_right.transform = .identity
self.eggshell_right.alpha = 1
self.eggWapperLeft.transform = .identity
self.eggWapperRight.transform = .identity
self.displayCloud(true).startAnimation()
self.displayLastLine(true)
// 重新設定初始動畫
self.setChickAnimation()
}
看起來程式碼有點多
但實際上就是設置各種動畫
讓他依序執行~ 完成
接下來為了避免打API時
用的人一直點按鈕 再加上一個方法
func enableAllButton(_ isEnable: Bool) -> Void {
let buttonList = [self.left_red, self.left_blue , self.right_red, self.right_blue]
let disableAlpha: CGFloat = 0.5
for button in buttonList {
button?.isEnabled = isEnable
button?.alpha = isEnable ? 1 : disableAlpha
}
}
然後當玩家獲勝時 我們幫他增加分數
並給他一個讚的圖案
輸的話扣分, 並給個倒讚的圖
我們再加上一些方法
@IBOutlet weak var pointLabel: UILabel!
@IBOutlet weak var winIcon: UIImageView!
func updatePoint(_ isWin: Bool, _ newPoint: String)-> Void {
self.winIcon.isHidden = false
if isWin {
self.winIcon.image = UIImage(systemName: "hands.sparkles.fill")
} else {
self.winIcon.image = UIImage(systemName: "hand.thumbsdown")
}
self.updatePointAndDisplayInUI(Int(newPoint) ?? 0)
self.checkIsGameOver()
}
func updatePointAndDisplayInUI(_ newPoint: Int) {
self.player.point = newPoint
self.pointLabel.text = "Point: \(self.player.point)"
}
func checkIsGameOver() -> Void {
if self.player.point > 0 {
return
}
self.alertMessage("遊戲結束!", "輸了! 遊戲即將重啟")
self.updatePointAndDisplayInUI(1000)
}
func alertMessage(_ title: String,_ msg: String) -> Void {
// 顯示提示訊息
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
let okBtn = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okBtn)
self.present(alert, animated: true, completion: nil)
}
整個遊戲終於做完了
明天來處裡遊戲紀錄摟!
Kotlin也是要開始打API摟
再來複習一下API文件
請使用POST方式傳送資料,API會把結果告訴你
URL
Method:
POST
URL Params
None
Data Params
Required:
choose=選擇的項目
balance=目前的結餘的點數
Success Response:
end even 代表走了四條橫線 odd代表三條
{"error_code":0,"error_msg":"","info":{"balance":"1200","is_win":true,"result":{"end":"even","stairs":"3","start":"left"}}}
Error Response:
{"error_code":10001,"error_msg":"Please POST 'choose' and 'balance' property","info":{"balance":"0","is_win":false,"result":{"end":"","stairs":"","start":""}}}
接下來撰寫按鈕點擊後的程式吧
kotlin這邊在資料儲存與傳遞上
給出了另一種解決方案叫做 ViewModel + LiveData
其中的 ViewModel 主要就是用來管理資料並共享使用
而 LiveData 是讓資料產生 Lifecycle
從而可達到view與data之間的綁定
這種概念對寫前端的工程師來說並不陌生
前端前三大框架 Angular, Vue, React 也都是走這種設計
尤其是Angular 與 android 都是出自Google
所以兩個都是在MVVM框架下的系統
想使用 ViewModel的話 沒錯~
繼續去Gradle裡面去新增吧
添加依賴 進入build.gradle(Module:chick_bb.app)
dependencies {
... 很多東西
// ViewModel
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
... 很多東西
}
在com.test.chickbb底下新增一個package叫player
在com.test.chickbb底下新增一個package叫network
其實就是一個資料夾拉~
然後在player底下新增一個kotlin Class 叫 PlayerViewModel
然後在network底下新增一個kotlin Class 叫 OrderResponse
首先解釋一下 OrderResponse
OrderResponse 裡面要定義等等打API回來的資料
內容這樣
package com.test.chickbb.network
import com.squareup.moshi.Json
data class OrderResponse (
@Json(name = "error_code") var errorCode: String,
@Json(name = "error_msg") var errorMsg: String,
var info: OrderInfoResponse
)
data class OrderInfoResponse(
var balance: String,
@Json(name = "is_win") var isWin: Boolean,
var result: ResultResponse,
)
data class ResultResponse(
var end: String,
var stairs: String,
var start: String
)
PlayerViewModel 是儲存玩家點數
與遊戲紀錄的類別
內容這樣
package com.test.chickbb.player
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.test.chickbb.network.ResultResponse
class PlayerViewModel() : ViewModel() {
private var _currentPoint: Int
private var _point = MutableLiveData<Int>()
val point: LiveData<Int> get() = _point
val currentPoint: Int get() = _currentPoint
private var _history = mutableListOf<OrderHistory>()
val history: MutableList<OrderHistory> get() = _history
init {
Log.d("GameFragment", "GameViewModel created!")
_point.value = 1000
_currentPoint = 1000
}
override fun onCleared() {
super.onCleared()
Log.d("GameFragment", "GameViewModel destroyed!")
}
fun updatePoint(newPoint: Int) {
this._point.value = newPoint
this._currentPoint = newPoint
}
fun addHistory(choose: String, is_win: Boolean,newPoint: Int,result: ResultResponse){
this._history.add(OrderHistory(choose, is_win, this._currentPoint, result, newPoint - this._currentPoint))
}
}
class OrderHistory(
var choose: String,
var is_win: Boolean,
var point: Int,
var result: ResultResponse,
var winPoint: Int)
所謂的 LiveData 其實就是透過觀察者模式
去訂閱 LiveData 變更的事件
每當資料變更時 就將UI進行更新
而為了方便程式使用 我另外加了 currentPoint
可以直接取得當下的Point
不用通過訂閱
此時回到 GameFragment.kt
撰寫order方法
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentGameBinding.inflate(inflater, container, false)
// 為了資料共用 我們透過ViewModelProvider從Activity 取得實體
player = ViewModelProvider(requireActivity()).get(PlayerViewModel::class.java)
setChickAnimation()
bindingBaseEvent()
return binding.root
}
fun bindingBaseEvent() {
// 執行動畫
binding.root.doOnPreDraw {
// kotlin 沒有生命週期綁定 視圖佈局完成
// 所以用這邊綁定
drawGameLine()
}
// 綁定按鈕事件
binding.btnLeftBlue.setOnClickListener {
choose(EggWapperDirection.Left, HatColor.Blue)
}
// 綁定按鈕事件
binding.btnLeftRed.setOnClickListener {
choose(EggWapperDirection.Left, HatColor.Red)
}
// 綁定按鈕事件
binding.btnRightBlue.setOnClickListener {
choose(EggWapperDirection.Right, HatColor.Blue)
}
// 綁定按鈕事件
binding.btnRightRed.setOnClickListener {
choose(EggWapperDirection.Right, HatColor.Red)
}
// 訂閱 LiveData事件
player.point.observe(viewLifecycleOwner,
{ newPoint ->
binding.pointLabel.text = "Point: " + newPoint.toString()
})
}
LiveData的綁定就這樣
很簡單吧 ~ 只要值改變 就更新UI
個下來讓我們來看看choose 方法做了什麼
fun choose(eggWapperDirection: EggWapperDirection, hatColor: HatColor) {
// 禁用所有按鈕
this.enableAllButton(false)
val chooseKey = this.getChoose(eggWapperDirection, hatColor)
// 打API與更新UI, 需要丟入後台線程處理
GlobalScope.launch {
try {
// 打API
val result = MarsApi.retrofitService.order(chooseKey, player.currentPoint.toString())
// 更新UI必須回主線程
Handler(Looper.getMainLooper()).postDelayed({
// 新增遊戲紀錄
player.addHistory(chooseKey, result.info.isWin, result.info.balance.toInt(), result.info.result)
// 播放動畫結果
playResultFromResponse(result)
}, 0)
} catch (e: Exception) {
println("error:"+e.message)
// 啟動所有按鈕
// 更新UI必須回主線程
Handler(Looper.getMainLooper()).postDelayed({
enableAllButton(true)
}, 0)
}
}
}
fun getChoose(eggWapperDirection: EggWapperDirection, hatColor: HatColor): String {
if(eggWapperDirection == EggWapperDirection.Left && hatColor == HatColor.Red){
return "left_even"
}
if(eggWapperDirection == EggWapperDirection.Left && hatColor == HatColor.Blue){
return "left_odd"
}
if(eggWapperDirection == EggWapperDirection.Right && hatColor == HatColor.Blue){
return "right_odd"
}
if(eggWapperDirection == EggWapperDirection.Right && hatColor == HatColor.Red){
return "right_even"
}
return ""
}
打API這邊牽扯到三個知識點
一個一個來吧
Kotlin這邊對於Http Clinet 推薦採用第三方庫來完成
Http Clinet使用 Retrofit
JSON解析使用 Moshi
首先到gradle
dependencies {
... 很多東西
// Retrofit with Moshi Converter
implementation 'com.squareup.moshi:moshi-kotlin:1.9.3'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
... 很多東西
}
然後要開通網路權限
到AndroidManifest.xml
加上
<uses-permission android:name="android.permission.INTERNET" />
接下來應為打的api是HTTP非HTTPS
所以我們要取消安全連線的限制
在res底下新增xml資料夾
xml下新增 network_security_config
內容如下
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
然後在AndroidManifest.xml
的 application 內加上
<application
...很多很多
android:networkSecurityConfig="@xml/network_security_config"
...很多很多>
設定完長這樣
這樣才能開始寫Api的程式
在剛剛的network的資料夾內新增 kotlin Class
OrderApiService 檔案
package com.test.chickbb.network
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.*
// api的 BASE_URL
private const val BASE_URL = "http://pinyi.ami-shake.com"
// 產生moshi JSON解析
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
// 產生retrofit 並添加JSON解析器
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
// 定義API
// FormUrlEncoded代表解析對body進行 Encoded
// POST(裡面是API路徑)
// suspend是限制他只能運作在線程中
interface OrderApiService {
@FormUrlEncoded
@POST("/gg_order.php")
suspend fun order(@Field("choose") choose: String, @Field("balance") balance: String): OrderResponse
}
// 透過這個方法,達到單一實例
object OrderApi {
val retrofitService : OrderApiService by lazy {
retrofit.create(OrderApiService::class.java)
}
}
此時只要 OrderApi.retrofitService.order("left_red", player.currentPoint.toString())
就可以取得資料了!
這邊要說明的是
應為API是非同步的, 你也不知道他要執行多久
所以必須把他交給子線程執行
GlobalScope.launch {}
可以產生一個後台線程 你不用去管理他
就可以丟出一個任務給他執行
整個程式也不會因為這個任務而卡住
當子線程打完api取得資料了
這是要進行UI的變更渲染
就要把任務交回主線程來更新畫面
Handler(Looper.getMainLooper()).postDelayed({
更新UI任務()
}, 0)
比起swift, 需要了解更多知識點
接下來撰寫動畫吧
API的 start 來決定打開雞蛋是左邊還是右邊
打開雞蛋後 判斷是否要顯示第四條線
然後讓雲朵隱藏起來
雞蛋依照線條開始跑
跑完之後更新分數
並重新讓動畫恢復播放前的狀態
enum class EggWapperDirection {
Left,
Right
}
enum class HatColor {
Red,
Blue
}
fun playResultFromResponse(res: OrderResponse) {
if(res.info.result.start == "left" && res.info.result.end == "odd" ){
this.playResult(EggWapperDirection.Left, HatColor.Blue, res.info.isWin, res.info.balance)
}
if(res.info.result.start == "left" && res.info.result.end == "even" ){
this.playResult(EggWapperDirection.Left, HatColor.Red, res.info.isWin, res.info.balance)
}
if(res.info.result.start == "right" && res.info.result.end == "odd" ){
this.playResult(EggWapperDirection.Right, HatColor.Blue, res.info.isWin, res.info.balance)
}
if(res.info.result.start == "right" && res.info.result.end == "even" ){
this.playResult(EggWapperDirection.Right, HatColor.Red, res.info.isWin, res.info.balance)
}
}
fun playResult(eggWapperDirection: EggWapperDirection, hatColor: HatColor, isWin: Boolean, newPoint: String) {
var hasLastLine = true
if(
(eggWapperDirection == EggWapperDirection.Right && hatColor == HatColor.Blue) ||
(eggWapperDirection == EggWapperDirection.Left && hatColor == HatColor.Red)
){
hasLastLine = false
}
val eggshellAni = this.openEggAni(eggWapperDirection)
val cloudAni = this.displayCloud(false)
val playEggAni = this.playEggAniOnLine(eggWapperDirection, hasLastLine)
playEggAni.doOnEnd {
this.updatePoint(isWin, newPoint)
this.reSetAni()
}
cloudAni.doOnEnd {
playEggAni.apply {
duration = 4000 // 動畫持續四秒
start() // 開始播放
}
}
this.displayLastLine(hasLastLine)
eggshellAni.apply {
duration = 500
start()
}
cloudAni.apply {
duration = 1000
start()
}
}
private fun openEggAni(eggWapperDirection: EggWapperDirection): ObjectAnimator {
val eggShell = if(eggWapperDirection == EggWapperDirection.Left) binding.eggshellLeft else binding.eggshellRight
// translationX
val pvhtranslationX = PropertyValuesHolder.ofKeyframe("translationX",
Keyframe.ofFloat(0f, 0f),
Keyframe.ofFloat(1f, eggShell.width.toFloat()/2 )
)
// translationY
val pvhtranslationY = PropertyValuesHolder.ofKeyframe("translationY",
Keyframe.ofFloat(0f, 0f),
Keyframe.ofFloat(1f, -(eggShell.width.toFloat())/2)
)
// rotation
val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation",
Keyframe.ofFloat(0f, 10f),
Keyframe.ofFloat(1f, 60f)
)
// rotation
val pvhAlpha = PropertyValuesHolder.ofKeyframe("alpha",
Keyframe.ofFloat(0f, 1f),
Keyframe.ofFloat(1f, 0f)
)
// 設定 ggView 關鍵影格
val ani = ObjectAnimator.ofPropertyValuesHolder(eggShell,
pvhtranslationY,
pvhtranslationX,
pvhRotation,
pvhAlpha)
return ani
}
private fun displayCloud(isShow: Boolean): ObjectAnimator{
return ObjectAnimator.ofPropertyValuesHolder(binding.cloud, PropertyValuesHolder.ofKeyframe("alpha",
Keyframe.ofFloat(0f, binding.cloud.alpha),
Keyframe.ofFloat(1f, if(isShow) 1f else 0f)
))
}
fun playEggAniOnLine(eggWapperDirection: EggWapperDirection, hasLastLine: Boolean): ObjectAnimator {
val eggRunLineKeyFrameOptions = getEggRunLineKeyFrameOptions(hasLastLine, eggWapperDirection)
val eggView = if (eggWapperDirection === EggWapperDirection.Left) binding.eggWapperLeft else binding.eggWapperRight
return ObjectAnimator.ofPropertyValuesHolder(eggView, *eggRunLineKeyFrameOptions)
}
private fun getEggRunLineKeyFrameOptions(hasLastLine: Boolean,direction: EggWapperDirection ): Array<PropertyValuesHolder>{
val eggXOffset = if (direction === EggWapperDirection.Left) this.lineWapperWidth else -this.lineWapperWidth
// translationX
val pvhtranslationX = PropertyValuesHolder.ofKeyframe("translationX",
Keyframe.ofFloat(0f, 0f),
Keyframe.ofFloat(.1f, 0f),
Keyframe.ofFloat(.2f, eggXOffset),
Keyframe.ofFloat(.3f, eggXOffset),
Keyframe.ofFloat(.4f, 0f),
Keyframe.ofFloat(.5f, 0f),
Keyframe.ofFloat(.6f, eggXOffset),
Keyframe.ofFloat(.7f, eggXOffset),
Keyframe.ofFloat(.8f, if(hasLastLine) 0f else eggXOffset),
Keyframe.ofFloat(.9f, if(hasLastLine) 0f else eggXOffset),
Keyframe.ofFloat(1f, if(hasLastLine) 0f else eggXOffset)
)
val eggYOffset = (binding.eggLeft.height/3)
val finalYOffset = (binding.eggLeft.height/4)
val pvhtranslationY = PropertyValuesHolder.ofKeyframe("translationY",
Keyframe.ofFloat(0f, 0f),
Keyframe.ofFloat(.1f, this.lineWapperHeight*0.2f + eggYOffset),
Keyframe.ofFloat(.2f, this.lineWapperHeight*0.2f + eggYOffset),
Keyframe.ofFloat(.3f, this.lineWapperHeight*0.4f + eggYOffset),
Keyframe.ofFloat(.4f, this.lineWapperHeight*0.4f + eggYOffset),
Keyframe.ofFloat(.5f, this.lineWapperHeight*0.6f + eggYOffset),
Keyframe.ofFloat(.6f, this.lineWapperHeight*0.6f + eggYOffset),
Keyframe.ofFloat(.7f, this.lineWapperHeight*0.8f + eggYOffset),
Keyframe.ofFloat(.8f, if(hasLastLine) this.lineWapperHeight*0.8f + eggYOffset else this.lineWapperHeight*1f + finalYOffset),
Keyframe.ofFloat(.9f, this.lineWapperHeight*1f + finalYOffset),
Keyframe.ofFloat(1f, this.lineWapperHeight*1f + finalYOffset)
)
return arrayOf(pvhtranslationX,pvhtranslationY)
}
fun displayLastLine(isShow: Boolean) {
this.lastLineImageView?.alpha = if (isShow) 1f else 0f
}
fun reSetAni() {
val resetViews = listOf<View>(binding.eggshellLeft, binding.eggshellRight, binding.eggWapperRight, binding.eggWapperLeft)
for (vi in resetViews) {
vi.translationX = 0f
vi.translationY = 0f
vi.rotation = 0f
vi.alpha = 1f
}
this.displayCloud(true).apply {
duration = 0
start()
}
this.displayLastLine(true)
this.enableAllButton(true)
}
看起來程式碼有點多
但實際上就是設置各種動畫
讓他依序執行~ 完成
接下來為了避免打API時
用的人一直點按鈕 再加上一個方法
fun enableAllButton(isEnable: Boolean) {
val buttonList = listOf(binding.btnLeftRed, binding.btnLeftBlue, binding.btnRightRed, binding.btnRightBlue)
buttonList.forEach {
it.isEnabled = isEnable
it.alpha = if (isEnable) 1f else 0.5f
}
}
然後當玩家獲勝時 我們幫他增加分數
並給他一個讚的圖案
輸的話扣分, 並給個倒讚的圖
我們再加上一些方法
fun updatePoint(isWin: Boolean, newPoint: String) {
binding.winIcon.alpha = 1f
if (isWin) {
binding.winIcon.setImageResource(android.R.drawable.stat_sys_upload)
} else {
binding.winIcon.setImageResource(android.R.drawable.stat_sys_download)
}
this.updatePointAndDisplayInUI(newPoint.toInt())
}
fun updatePointAndDisplayInUI(newPoint: Int) {
if(newPoint > 0){
player.updatePoint(newPoint)
} else {
this.isGameOver()
}
}
fun isGameOver() {
this.alertMessage("遊戲結束!", "輸了! 遊戲即將重啟")
this.player.updatePoint(1000);
}
fun alertMessage (title: String,msg: String) {
AlertDialog.Builder(binding.root.context)
.setMessage(msg)
.setTitle(title)
.setPositiveButton("OK", null)
.show()
}
整個遊戲終於做完了
Kotlin 在這個章節裡面
需要知道非常多的知識點
尤其打HTTP連線 還要選擇使用的第三方庫
這點在Swift就比較統一
你也不用特別選 就一個方法
但Kotlin好處就是 可以選擇你比較習慣的用法
不同第三方庫 使用起來的方便度也是有差的
這邊採用的是官方教學文件的用法
給大家參考看看摟
哇賽~今天資訊大爆炸
內容超多的
剩下最後五天~還有程式收尾與APP上架
加油加油~終點快到摟~