接下来当我们点击按钮
我们来打个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上架
加油加油~终点快到搂~
<<: Flutter体验 Day 25-SharedPreferences
Portainer介绍 有没有觉得每次在玩转Docke的时候都在用指令很不方便,当container...
开 api 规格是个有趣的事情,从栏位命名、资料阶层设计、易读性、合理性、方便性都是需要考量的点,对...
GCP IAP 今天再来了解一下什麽事IAP?他的全名即是dentity-Aware Proxy简称...
挑战目标: MockNative Camp 今天继续来制作我们的Footer, 目标 前两天我们已经...
范本(Template) 范本是放HTML档案的资料夹,Template engine(范本引擎)会...