最後一個功能是遊戲紀錄
修改一下Player.swift
import UIKit
class Player: NSObject {
var point: Int = 1000
var history: Array<OrderHistory> = []
func addHistory(_ choose: String, _ is_win: Bool, _ newPoint: Int, _ result: ResultResponse){
self.history.append(OrderHistory(choose, is_win, point, result, newPoint - point))
}
}
class OrderHistory: NSObject {
let choose: String
let result: ResultResponse
let is_win: Bool
let point: Int
let winPoint: Int
init(_ choose: String, _ is_win: Bool, _ point: Int, _ result: ResultResponse, _ winPoint: Int) {
self.choose = choose
self.is_win = is_win
self.point = point
self.result = result
self.winPoint = winPoint
}
}
然後回到 ViewController.swift
在播放動畫時呼叫 player.addHistory
示意圖中有提到, 遊戲紀錄要寫在第二個分頁
因此我們來新增遊戲紀錄頁面的類別
首先來到這個畫面
請分別產生這兩個檔案
然後設定到UI的Class中
到 紀錄的Cell上 設定Id 成 "Cell"
程式碼分別如這樣
import UIKit
class HistoryViewControllerTableViewController: UITableViewController {
var history: Array<OrderHistory> = []
override func viewDidLoad() {
super.viewDidLoad()
// 綁定Cell
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
// 回傳要幾個table區域
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// 總共有幾個項目
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.history.count
}
// 設定每一條table的顯示內容
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 剛剛在 設定的ID "Cell"
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
// 設定顯示結果
var newText = "\(indexPath.row+1). 結果:"
let currentHistory = self.history[indexPath.row]
if currentHistory.is_win {
newText += "贏 +\(currentHistory.winPoint)"
} else {
newText += "輸 \(currentHistory.winPoint)"
}
cell.textLabel?.text = newText
return cell
}
// 回傳 table 的 Title
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "遊戲紀錄"
}
// 當cell變點擊時, 要做的事情
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 利用Segue開畫面 showDetal 前面設定的跳頁id
performSegue(withIdentifier: "showDetail", sender: nil)
}
// 跳頁前可以做一些事情
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 判斷如果是去 HistoryDetailViewController
// 就把資料帶過去
if segue.destination is HistoryDetailViewController {
let vc = segue.destination as? HistoryDetailViewController
let selectedRowPath = self.tableView.indexPathForSelectedRow
if let selectedRow = selectedRowPath?.row {
let currentHistory = history[selectedRow]
vc?.history = currentHistory
}
}
}
}
import UIKit
class HistoryDetailViewController: UIViewController {
var history: OrderHistory?
@IBOutlet weak var winPointView: UILabel!
@IBOutlet weak var resultView: UILabel!
@IBOutlet weak var chooseView: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// 設定畫面顯示
if let historyData = self.history {
print(historyData.choose)
chooseView.text?.append(" \(self.parseChoose(historyData.choose))")
resultView.text?.append(" \(historyData.is_win ? "贏" : "輸")")
winPointView.text?.append(" \(historyData.winPoint)")
if !historyData.is_win {
view.backgroundColor = UIColor.gray
}
}
}
func parseChoose(_ chooseKey: String) -> String {
var choose = ""
switch chooseKey {
case "left_odd":
choose = "左藍"
case "left_even":
choose = "左紅"
case "right_odd":
choose = "左藍"
case "right_even":
choose = "左藍"
default:
choose = ""
}
return choose
}
}
最後在ViewController.swift 動畫播放完後
執行更新紀錄的方法
完成~
func updateHistoryPage() -> Void {
if let tableView = self.tabBarController?.viewControllers?[1] as? HistoryViewControllerTableViewController {
tableView.history = self.player.history
tableView.tableView.reloadData()
}
}
經過了那麼多天的努力
最後我們來看看ViewController.swift 完整的樣子
import UIKit
enum EggWapperDirection {
case Left
case Right
}
enum HatColor {
case Red
case Blue
}
struct KeyFrameOptionItem {
let startTime: Double // 動畫開始時間
let translationX: CGFloat // 左右位移
let translationY: CGFloat // 上下跳動
let rotated: CGFloat // 上下角度
let scaledX: CGFloat // 水平翻轉
init(startTime: Double, translationX: CGFloat, translationY: CGFloat, rotated: CGFloat, scaledX: CGFloat) {
let oneDegree = CGFloat.pi / 180 // 透過pi轉換rotated角度
self.startTime = startTime
self.translationX = translationX
self.rotated = rotated * oneDegree
self.translationY = translationY
self.scaledX = scaledX
}
}
class ViewController: UIViewController {
// 按鈕
@IBOutlet weak var left_blue: UIButton!
@IBOutlet weak var right_blue: UIButton!
@IBOutlet weak var left_red: UIButton!
@IBOutlet weak var right_red: UIButton!
@IBOutlet weak var pointLabel: UILabel!
@IBOutlet weak var winIcon: UIImageView!
//小雞 圖片 參照
@IBOutlet weak var ggImg: UIImageView!
@IBOutlet weak var eggshell_right: UIImageView!
@IBOutlet weak var eggshell_left: UIImageView!
@IBOutlet weak var Cloud: UIImageView!
// 遊戲畫面
@IBOutlet weak var gameWapper: UIView!
@IBOutlet weak var lineWapper: UIView!
@IBOutlet weak var eggWapperRight: UIView!
@IBOutlet weak var eggWapperLeft: UIView!
@IBOutlet weak var hat_blue: UIImageView!
@IBOutlet weak var hat_red: UIImageView!
var lastLineLayer: CAShapeLayer? = nil
var lastLineInLineLayer: CAShapeLayer? = nil
var lineWapperHeight: CGFloat = 0
var lineWapperWidth: CGFloat = 0
//小雞 動畫參數
var chickKeyFrameOptions: Array<KeyFrameOptionItem> = []
var chooseList: Set<UIButton> = Set<UIButton>()
var player = Player()
override func viewDidLoad() {
super.viewDidLoad()
self.setChickKeyFrameOption()
self.drawGameLine()
}
override func viewDidAppear(_ animated: Bool) {
print("viewDidDisappear")
self.reSetAni()
}
@IBAction func choose(_ sender: UIButton) {
self.enableAllButton(false)
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)
DispatchQueue.main.async {
self.player.addHistory(self.getChoose(sender), res.info.is_win, Int(res.info.balance) ?? 0, res.info.result)
self.playResultFromResponse(res)
self.updateHistoryPage()
}
} catch let error {
print("error")
print(error)
}
}
}.resume()
}
func updateHistoryPage() -> Void {
if let tableView = self.tabBarController?.viewControllers?[1] as? HistoryViewControllerTableViewController {
tableView.history = self.player.history
tableView.tableView.reloadData()
}
}
func playResultFromResponse(_ res: OrderResponse) -> Void {
if(res.info.result.start == "left" && res.info.result.end == "odd" ){
self.playResult(EggWapperDirection.Left, HatColor.Blue, res.info.is_win, res.info.balance)
}
if(res.info.result.start == "left" && res.info.result.end == "even" ){
self.playResult(EggWapperDirection.Left, HatColor.Red, res.info.is_win, res.info.balance)
}
if(res.info.result.start == "right" && res.info.result.end == "odd" ){
self.playResult(EggWapperDirection.Right, HatColor.Blue, res.info.is_win, res.info.balance)
}
if(res.info.result.start == "right" && res.info.result.end == "even" ){
self.playResult(EggWapperDirection.Right, HatColor.Red, res.info.is_win, res.info.balance)
}
}
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
}
}
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()
self.enableAllButton(true)
}
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.updatePoint(isWin, newPoint)
self.reSetAni()
})
cloudAni.addCompletion({ _ in
playEggAni.startAnimation()
})
displayLastLine(hasLastLine)
eggshellAni.startAnimation()
cloudAni.startAnimation()
}
func updatePoint(_ isWin: Bool, _ newPoint: String)-> Void {
self.winIcon.isHidden = false
if isWin {
// AudioServicesPlaySystemSound(1325)
self.winIcon.image = UIImage(systemName: "hands.sparkles.fill")
} else {
// ㄆAudioServicesPlaySystemSound(1324)
self.winIcon.image = UIImage(systemName: "hand.thumbsdown")
}
self.updatePointAndDisplayInUI(Int(newPoint) ?? 0)
self.checkIsGameOver()
}
func checkIsGameOver() -> Void {
if self.player.point > 0 {
return
}
self.alertMessage("遊戲結束!", "輸了! 遊戲即將重啟")
self.updatePointAndDisplayInUI(1000)
}
func updatePointAndDisplayInUI(_ newPoint: Int) {
self.player.point = newPoint
self.pointLabel.text = "Point: \(self.player.point)"
}
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)
}
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 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
}
func setChickAnimation() {
print("setChickAnimation")
UIView.animateKeyframes(
withDuration: 4,
delay: 0.0,
options: [.repeat, .calculationModeLinear],
animations: {
for option in self.chickKeyFrameOptions {
UIView.addKeyframe(
withRelativeStartTime: option.startTime,
relativeDuration: 0.1,
animations: {
self.ggImg.transform = CGAffineTransform(translationX: option.translationX, y: option.translationY)
.rotated(by: option.rotated)
.scaledBy(x: option.scaledX, y: 1.0)
self.eggshell_right.transform = CGAffineTransform(translationX: 0, y: option.translationY < 0 ? -10 : 0)
self.eggshell_left.transform = CGAffineTransform(translationX: 0, y: option.translationY == 0 ? -10 : 0)
}
)
}
},
completion: nil
)
}
func drawGameLine() {
// 如果已經初始化過 就不再重繪
if(self.lineWapperHeight > 0){
return
}
// 高度用帽子最小的Y - 雞蛋最大的Y
self.lineWapperHeight = hat_blue.frame.minY - eggWapperLeft.frame.maxY
// 寬度用兩個雞蛋中間的X計算
self.lineWapperWidth = eggWapperRight.frame.midX - eggWapperLeft.frame.midX
// 左邊柱子
drawLineWithBoderInLineWapper(
startPoint: CGPoint(x: eggWapperLeft.frame.midX, y: (eggWapperLeft.frame.maxY - 20)),
endPoint: CGPoint(x: hat_blue.frame.midX, y: (hat_blue.frame.minY - 10)),
lineWidth: 20,
rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
boderWidth: 8,
boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
)
// 右邊柱子
drawLineWithBoderInLineWapper(
startPoint: CGPoint(x: eggWapperRight.frame.midX, y: (eggWapperRight.frame.maxY - 20)),
endPoint: CGPoint(x: hat_red.frame.midX, y: (hat_red.frame.minY - 10)),
lineWidth: 20,
rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
boderWidth: 8,
boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
)
// 中間橫線
for yPoint in [0.2, 0.4, 0.6, 0.8] as Array<CGFloat> {
drawLineWithBoderInLineWapper(
startPoint: CGPoint(x: eggWapperLeft.frame.midX, y: (eggWapperLeft.frame.maxY + self.lineWapperHeight * yPoint)),
endPoint: CGPoint(x: eggWapperRight.frame.midX, y: (eggWapperRight.frame.maxY + self.lineWapperHeight * yPoint)),
lineWidth: 20,
rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
boderWidth: 8,
boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
)
}
}
func drawLineWithBoderInLineWapper(startPoint: CGPoint, endPoint: CGPoint, lineWidth: CGFloat, rectColor: UIColor, boderWidth: CGFloat, boderColor: UIColor) {
let chickmarkLayer = CAShapeLayer()
if(startPoint.y == endPoint.y){
// 畫橫線
chickmarkLayer.path = UIBezierPath(rect: CGRect(
x: startPoint.x + lineWidth/2,
y: startPoint.y - lineWidth/2,
width: endPoint.x - startPoint.x - lineWidth,
height: lineWidth
)).cgPath
}else {
// 畫直線
chickmarkLayer.path = UIBezierPath(roundedRect: CGRect(
x: startPoint.x - lineWidth/2,
y: startPoint.y,
width: lineWidth,
height: endPoint.y - startPoint.y
), cornerRadius: lineWidth/2).cgPath
}
chickmarkLayer.fillColor = boderColor.cgColor
let chickmarkLayer_inLine = CAShapeLayer()
let boderInLineWidth = lineWidth - boderWidth
if(startPoint.y == endPoint.y){
// 畫橫線
chickmarkLayer_inLine.path = UIBezierPath(rect: CGRect(
x: startPoint.x ,
y: startPoint.y - boderInLineWidth/2,
width: endPoint.x - startPoint.x + boderWidth / 2,
height: boderInLineWidth
)).cgPath
} else {
// 畫直線
chickmarkLayer_inLine.path = UIBezierPath(roundedRect: CGRect(
x: startPoint.x - boderInLineWidth/2,
y: startPoint.y + boderWidth/2 ,
width: boderInLineWidth,
height: (endPoint.y - startPoint.y - boderWidth)
), cornerRadius: boderInLineWidth/2).cgPath
}
chickmarkLayer_inLine.fillColor = rectColor.cgColor
lineWapper.layer.addSublayer(chickmarkLayer)
lineWapper.layer.addSublayer(chickmarkLayer_inLine)
let lastLineStartPoint = CGPoint(x: eggWapperLeft.frame.midX, y: (eggWapperLeft.frame.maxY + self.lineWapperHeight * 0.8))
if(lastLineStartPoint.equalTo(startPoint)){
self.lastLineLayer = chickmarkLayer
self.lastLineInLineLayer = chickmarkLayer_inLine
}
}
func displayLastLine(_ isShow: Bool) {
self.lastLineLayer?.isHidden = !isShow
self.lastLineInLineLayer?.isHidden = !isShow
}
func displayCloud(_ isShow: Bool) -> UIViewPropertyAnimator{
let cloudAni = UIViewPropertyAnimator(duration: isShow ? 0 : 1,curve: .linear, animations: {
self.cloud.alpha = isShow ? 1 : 0
})
return cloudAni
}
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
}
fileprivate func setChickKeyFrameOption() {
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.0, translationX: -17.0, translationY: 0.0, rotated: 10, scaledX: 1.0))
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.1, translationX: -33.0, translationY: -10.0, rotated: -10, scaledX: 1.0))
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.2, translationX: -50.0, translationY: 0, rotated: 10, scaledX: 1.0))
// 以上向左邊跳到底後轉身往回走
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.3, translationX: -33.0, translationY: -10.0, rotated: -10, scaledX: -1.0))
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.4, translationX: -27.0, translationY: 0.0, rotated: 10, scaledX: -1.0))
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.5, translationX: 0.0, translationY: -10.0, rotated: -10, scaledX: -1.0))
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.6, translationX: 30.0, translationY: 0.0, rotated: 10, scaledX: -1.0))
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: 50.0, translationY: -10.0, rotated: -10, scaledX: -1.0))
//跳到最右邊後 轉身往原點走
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.8, translationX: 20.0, translationY: 0.0, rotated: 10, scaledX: 1.0))
self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.9, translationX: 0.0, translationY: -10.0, rotated: -10, scaledX: 1.0))
}
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 ""
}
}
整個遊戲撰寫完成!
在Kotlin這邊遊戲紀錄頁面要如何取得紀錄呢?
我們回到HistoryFragment.kt
新增 TextView 與 Rcycler View
textView(TextView)
屬性 | 對齊 | 設定 |
---|
layout_width| 無 |match_parent|
layout_height| 無 |wrap_content|
background| 無 |@color/gray_400|
paddingStart| 無 |5dp|
paddingTop| 無 |10dp|
paddingEnd| 無 |10dp|
paddingBottom| 無 |5dp|
text| 無 |遊戲紀錄|
textColor| 無 |#424242|
textSize| 無 |20dp|
Start -> StartOf| parent |0dp|
End -> EndOf| parent |0dp|
Top -> TopOf| parent |0dp|
recycler_view(RcyclerView)
屬性 | 對齊 | 設定 |
---|
layout_width| 無 |0d|
layout_height| 無 |0dp|
paddingStart| 無 |10dp|
paddingEnd| 無 |10dp|
scrollbars| 無 |vertical|
layoutManager| 無 |LinearLayoutManager|
Start -> StartOf| parent |0dp|
End -> EndOf| parent |0dp|
Top -> BottomOf| textView |0dp|
Bottom -> BottomOf| parent |0dp|
設定好畫面後, 就給RcyclerView設定一個資料來源
RcyclerView會接受一個Adapter
來處理每一個Cell資料顯示的內容
而每個Cell也是一個 layout
所以我們先在res/layout底下新增一個 Layout Resource File
命名為 list_item.xml
內容這樣
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/editTextBackground">
<TextView
android:id="@+id/item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textColor="@color/black"
android:textSize="20sp" />
</LinearLayout>
然後在 com.test.chickbb
專案資料夾 底下新增一個package adapter
裡面新增一個 kotlin class 叫 ItemAdapter
package com.test.chickbb.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.test.chickbb.R
import com.test.chickbb.player.OrderHistory
class ItemAdapter(private val dataset: List<OrderHistory>): RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
private lateinit var mListener: onItemClickListener
class ItemViewHolder(private val view: View, listener: onItemClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
// 取得 view
val textView: TextView = view.findViewById(R.id.item_title)
init {
view.setOnClickListener {
listener.onItemClick(adapterPosition)
}
}
override fun onClick(v: View?) {
TODO("Not yet implemented")
}
}
// 定義點擊事件 用 interface 由使用者實作
interface onItemClickListener {
fun onItemClick(position: Int)
}
fun setOnItemClickListener(listener: onItemClickListener){
mListener = listener
}
// 設定使用的Layout
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout, mListener)
}
// 設定顯示樣式
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = "${position+1}. 結果${if (item.is_win) "贏 +" else "輸"}${item.winPoint}"
}
// 設定有幾個cell
override fun getItemCount() = dataset.size
}
接下來我們預計點擊Cell
會把遊戲結果帶到 ShowDetailFragment
首先到 fragment_show_detail.xml
設定layout 這邊很單純
就三個textView 而已
我直接用Code表示
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorPressedHighlight"
tools:context=".ShowDetailFragment">
<TextView
android:id="@+id/detail_choose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:text="選擇:"
android:textColor="#5D4037"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/detail_isWin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:text="結果:"
android:textColor="#5D4037"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_choose" />
<TextView
android:id="@+id/detail_winPoint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:text="得分:"
android:textColor="#5D4037"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_isWin" />
</androidx.constraintlayout.widget.ConstraintLayout>
再來我們到ShowDetailFragment.kt
設定會傳入三個參數 用來顯示詳細結果
package com.test.chickbb
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.test.chickbb.databinding.FragmentShowDetailBinding
private const val ARG_PARAM1 = "choose"
private const val ARG_PARAM2 = "isWin"
private const val ARG_PARAM3 = "winPoint"
class ShowDetailFragment : Fragment() {
private var _binding: FragmentShowDetailBinding? = null
private val binding get() = _binding!!
private var choose: String? = null
private var isWin: Boolean? = null
private var winPoint: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
choose = it.getString(ARG_PARAM1)
isWin = it.getBoolean(ARG_PARAM2)
winPoint = it.getString(ARG_PARAM3)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentShowDetailBinding.inflate(inflater, container, false)
binding.detailChoose.text = binding.detailChoose.text.toString() + parseChoose(choose!!)
binding.detailIsWin.text = binding.detailIsWin.text.toString() + if (isWin!!) "贏" else "輸"
binding.detailWinPoint.text = binding.detailWinPoint.text.toString() + winPoint
return binding.root
}
companion object {
@JvmStatic
fun newInstance(param1: String, param2: Boolean, param3: String) =
ShowDetailFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putBoolean(ARG_PARAM2, param2)
putString(ARG_PARAM3, param3)
}
}
}
fun parseChoose(chooseKey: String):String {
var choose = ""
when (chooseKey) {
"left_odd" -> choose = "左藍"
"left_even" -> choose = "左紅"
"right_odd" -> choose = "左藍"
"right_even" -> choose = "左藍"
}
return choose
}
}
接下來就要設定導航了
回到 nav_graph
對 showDetailFragment 設定這三個參數
我們回到HistoryFragment.kt
package com.test.chickbb
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import com.test.chickbb.adapter.ItemAdapter
import com.test.chickbb.databinding.FragmentHistoryBinding
import com.test.chickbb.player.OrderHistory
import com.test.chickbb.player.PlayerViewModel
class HistoryFragment : Fragment() {
private var _binding: FragmentHistoryBinding? = null
private val binding get() = _binding!!
// lateinit 為延後初始化
private lateinit var player: PlayerViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentHistoryBinding.inflate(inflater, container, false)
val recyclerView = binding.recyclerView
// 從Activity取得共用的ViewModel
player = ViewModelProvider(requireActivity()).get(PlayerViewModel::class.java)
// 先確認裡面有資料
if(!player.history.isNullOrEmpty()) {
// 轉成 history List
val listHistorys = player.history?.toList()!!
// 產生出 Adapter 實體
val adapter = ItemAdapter(listHistorys)
// 將 Adapter 設定給 RecyclerView
recyclerView.adapter = adapter
// 實作 點擊時要做的事
adapter.setOnItemClickListener(object : ItemAdapter.onItemClickListener{
override fun onItemClick(position: Int) {
// 被點到的對象
val currentOrder = listHistorys[position]
// 使用 Directions跳轉頁面
val action = HistoryFragmentDirections.actionHistoryFragmentToShowDetailFragment(currentOrder.choose, currentOrder.is_win, currentOrder.winPoint.toString())
// 跳頁摟
binding.root.findNavController().navigate(action)
}
})
recyclerView.setHasFixedSize(true)
}
return binding.root
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
好了~所有功能完成
呼~
在Swift內 頁面與頁面間互傳資料
語法上比較簡單
但對於程式設計來說 用 as? 某個頁面Class這種方式
感覺比較隨意 個人不太喜歡
而Kotlin這邊採用 ViewModelProvider 可以取得單一實例的Service
這樣方法個人更喜歡一點
而Kotlin 這邊採用的MVVM架構
我也覺得很好用喔
嗯嗯...程式碼好多 頭好炸
本來也想把Kotlin的完整程式貼出來
但仔細看了看好像不是很必要
頭腦炸裂的程式開發結束了!
明天開始來準備上架摟~