iT邦幫忙

2021 iThome 鐵人賽

DAY 23
0

遊戲示意

遊戲 小雞BB
遊戲 小雞BB

swift - tab功能分頁

遊戲頁面排完版了
接下來就建立下方藍色的分頁吧
分頁的功能在swift內叫 Tab Bar Controller
我們先把他拉進畫面
遊戲 小雞BB
此時我們會看到預設的Tab Bar 有預設了兩個畫面
遊戲 小雞BB
不需要留情~ 刪掉那兩個畫面
遊戲 小雞BB
接下來按住control後從 Tab Bar Controller 拉到 遊戲畫面
同時將遊戲畫面左邊的小箭頭拉到 Tab Bar Controller
這個小箭頭代表程式進入的第一個View
遊戲 小雞BB
然後我們點選 Tab Bar Controller 設定一下屬性
遊戲 小雞BB

屬性 對齊 設定
Image Tint System Orange Collor
Bar Tint System Teal Color
Background System Teal Color

回到 遊戲畫面 點選下方的 Bar Item
遊戲 小雞BB
設定Title為遊戲, image為 gamecontroller
遊戲 小雞BB
此時畫面多了Bar Item 因此遊戲頁面
下方白色區塊需要推高一點
點選gameFootrt的Autolayout設定
Bottom -> BottomOf Parent的設定多推40
遊戲 小雞BB
接下來在拉一個 Table View Controller頁面
來顯示遊戲紀錄
遊戲 小雞BB
設定Bar Item的Title為 遊戲紀錄
遊戲 小雞BB
然後兩大頁面完成
遊戲 小雞BB
接下來打算點選遊戲紀錄時
會開啟一個View來顯示遊戲紀錄詳情
於是我們在拉一個View Controller
遊戲 小雞BB
被設定背景為 System Yellow Color
還後新增三個Label
如圖片顯示文字
AutoLayout都是距離邊框20
這邊就不再詳細說明了
遊戲 小雞BB
下一步我們按著 control
遊戲紀錄畫面拉到 紀錄詳情畫面
然後點選show
遊戲 小雞BB
此時會建立一條 Segue
來達到換頁的效果
此時點選兩個頁面中的 Segue
並給他一個ID "showDetail"
遊戲 小雞BB
經過一系列設定
關於畫面Tab分頁的設定就都完成摟!
遊戲 小雞BB
至於 紀錄詳情頁面如何進入
下個章節再來說明

kotlin - Safe Args

kotlin 也提供了一種導航框架
來處理分頁問題
只是稍微複雜一點點
我們往下看吧
以下是使用Safe Args前
需要先知道的知識點

  1. Active
    一個頁面或一個畫面, 有自己的生命週期
  2. Fragment
    一個片段也是一個畫面, 存在於Active之中, 你可以想像Active是電視機本體,Fragment是電視機內的各個頻道
  3. NavHostFragment
    掛載Fragment的地方
  4. BottomNavigationView
    下方切換Fragment的分頁按鈕
  5. Menu
    分頁選單
  6. Navigation Graph
    分頁導航設定在這裡

我們現在想要的畫面是
下方有兩顆分頁按鈕
分別可到 遊戲畫面遊戲紀錄畫面
同時遊戲紀錄又可以點擊
跳到遊戲詳情畫面
整理一下吧

元件名稱 數量 說明
Active 1 最外層包一切
NavHostFragment 1 掛載在Active內, 用來顯示Fragment
BottomNavigationView 1 掛載在Active內, 用來切換Fragment
Navigation Graph 1 設定頁面導航
Menu 1 設定按鈕選單
Fragment 3 遊戲頁, 遊戲紀錄, 紀錄詳情

準備開始動手
首先安裝依賴
先到頂級build.gradle(Project:chick_bb)

dependencies {
    ...
    classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
    ...
}

在到應用級build.gradle(Module:chick_bb.app)

plugins {
    ...
    id 'androidx.navigation.safeargs.kotlin'
    ...
}
dependencies {
    ...
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
    ...
}

然後SYNCK 同步完成後
就可以往下摟~

Android所需步驟多又繁雜
看清楚摟
首先在res內新增一個資料夾叫menu

遊戲 小雞BB

再menu內新增一個menu File叫 menu_bottom_nav

遊戲 小雞BB

進入 menu_bottom_nav 從上方拉入兩個Menu item
設定如下

  1. 遊戲item

    屬性

    icon|@android:drawable/ic_menu_myplaces|
    id|gameFragment|
    title|遊戲|

  2. 紀錄item

    屬性

    icon|@android:drawable/ic_menu_day|
    id|historyFragment|
    title|紀錄|

遊戲 小雞BB

第一個重點來了!!
導航要順利關聯的重點在id
接下來menu, Navigation Graph, Fragmentid
全都要一樣, 才能完成導航喔
接下來回到 active_main.xml
新增NavHostFragmentBottomNavigationView

遊戲 小雞BB

在添加 NavHostFragment會跳出畫面
要你選擇 Navigation Graph
理所當然的~我們不會有
那就來新增吧

遊戲 小雞BB

點選左上的+ 並新增
命名為 nav_graph

遊戲 小雞BB

此時回產生一個nav_graph 點選OK

遊戲 小雞BB

此時進入 res/navigation/nav_graph
此畫面可建立導航地圖
點選上方的+圖示
選擇create new destination

遊戲 小雞BB

接下來就產生 Fragment 了

遊戲 小雞BB

我們先分別產生 GameFragmentHistoryFragment
此時會產生四個檔案
GameFragment.kt
HistoryFragment.kt
fragment_game.xml
fragment_history.xml
nav_graph 內會出現兩個畫面
有房子的是預設第一個顯示的 Fragment
請依序將他們兩個的id設定為 gameFragmenthistoryFragment

遊戲 小雞BB

並進入fragment_game.xmlfragment_history.xml
將最外層的 FrameLayout轉成 ConstraintLayout

遊戲 小雞BB

接下來將
fragment_game.xml 最外層 id 設定成 gameFragment
fragment_history.xml 最外層 id 設定成 historyFragment
遊戲 小雞BB

此時確認一下
menu, Navigation Graph, Fragmentid
都要一樣

此時回到active_main.xml 檢查一下
並設定 NavHostFragmentBottomNavigationView 元件

  1. nav_host_fragment(NavHostFragment)

    屬性 對齊 設定

    id| 無 |nav_host_fragment|
    name| 無 |androidx.navigation.fragment.NavHostFragment|
    layout_width| 無 |0dp|
    layout_height| 無 |0dp|
    defaultNavHost| 無 |true|
    navGraph| 無 |@navigation/nav_graph|
    Start -> StartOf| parent |0dp|
    End -> EndOf| parent |0dp|
    Top -> TopOf| parent |0dp|
    Bottom -> BottomOf | bottom_nav |0dp|

  2. bottom_nav(BottomNavigationView)

    屬性 對齊 設定

    id| 無 |bottom_nav|
    layout_width| 無 |0dp|
    layout_height| 無 |50dp|
    menu| 無 |@menu/menu_bottom_nav|
    Start -> StartOf| parent |0dp|
    End -> EndOf| parent |0dp|
    Bottom -> BottomOf | parent |0dp|

遊戲 小雞BB

畫面設定好了
接下來必須把 bottom_nav 導航的功能
綁定到 nav_host_fragment 上面
進入 MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // 綁定 bottom_nav 與 nav_host_fragment
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    findViewById<BottomNavigationView>(R.id.bottom_nav)
        .setupWithNavController(navController)
}

這樣就完成綁定摟
接下來搬移畫面與資料

進入 active_main.xml 切換到 code模式
將除了 nav_host_fragmentbottom_nav 之外的元件剪下
通通貼到 fragment_game.xml 內

遊戲 小雞BB

進入 MainActive 將動畫的方法複製到 GameFragment
這邊帶入一個 Fragment 知識點
Fragment 實際上是 active的一個片段
所以動畫程式中的 findViewById 是不能使用的
甚至在Fragment 也是沒有 view 可以直接使用的
竟然這樣 我就來順便來開啟
Android 一個很方便的功能 view binding

首先進入 build.gradle 有 .app的那個
加入

buildFeatures {
    viewBinding true
}

遊戲 小雞BB

接著進入另一個 build.gradle
於dependencies加入
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"

遊戲 小雞BB

填入後畫面右上方會出現 Sync Now 點下去讓他同步

回到 GameFragment 預設 Fragment模板上
有一些我們暫時不需要的程式
我們來將程式修改一下

package com.test.chickbb

import android.animation.Keyframe
import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
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.FragmentGameBinding


class GameFragment : Fragment() {
    // FragmentGameBinding 是自動產生的類別 用來綁定視圖
    private var _binding: FragmentGameBinding? = null
    // FragmentGameBinding 預設下有可能是null ,
    // 透過這個get 方便在正確取資料時 不用加上問號
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentGameBinding.inflate(inflater, container, false)
        // 執行動畫
        setChickAnimation()
        return binding.root
    }

    override fun onDestroy() {
        super.onDestroy()
        // 離開畫面要移除參照
        _binding = null
    }

    fun setChickAnimation() {
        // translationX
        val pvhtranslationX = PropertyValuesHolder.ofKeyframe("translationX",
            Keyframe.ofFloat(0f, 0f),
            Keyframe.ofFloat(.1f, -33f),
            Keyframe.ofFloat(.2f, -66f),
            Keyframe.ofFloat(.3f, -99f),
            Keyframe.ofFloat(.4f, -66f),
            Keyframe.ofFloat(.5f, -33f),
            Keyframe.ofFloat(.6f, 0f),
            Keyframe.ofFloat(.7f, 40f),
            Keyframe.ofFloat(.8f, 100f),
            Keyframe.ofFloat(.9f, 40f),
            Keyframe.ofFloat(1f, 0f)
        )

        // translationY
        val pvhtranslationY = PropertyValuesHolder.ofKeyframe("translationY",
            Keyframe.ofFloat(0f, 0f),
            Keyframe.ofFloat(.1f, -20f),
            Keyframe.ofFloat(.2f, 0f),
            Keyframe.ofFloat(.3f, -20f),
            Keyframe.ofFloat(.4f, 0f),
            Keyframe.ofFloat(.5f, -20f),
            Keyframe.ofFloat(.6f, 0f),
            Keyframe.ofFloat(.7f, -20f),
            Keyframe.ofFloat(.8f, 0f),
            Keyframe.ofFloat(.9f, -20f),
            Keyframe.ofFloat(1f, 0f)
        )
        // rotation
        val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation",
            Keyframe.ofFloat(0f, 10f),
            Keyframe.ofFloat(.1f, -10f),
            Keyframe.ofFloat(.2f, 10f),
            Keyframe.ofFloat(.3f, -10f),
            Keyframe.ofFloat(.4f, 10f),
            Keyframe.ofFloat(.5f, -10f),
            Keyframe.ofFloat(.6f, 10f),
            Keyframe.ofFloat(.7f, -10f),
            Keyframe.ofFloat(.8f, 10f),
            Keyframe.ofFloat(.9f, -10f),
            Keyframe.ofFloat(1f, 10f)
        )
        // scaleX
        val pvhScaledBy = PropertyValuesHolder.ofKeyframe("scaleX",
            Keyframe.ofFloat(0f, 1f),
            Keyframe.ofFloat(.1f, 1f),
            Keyframe.ofFloat(.2f, 1f),
            Keyframe.ofFloat(.3f, 1f),
            Keyframe.ofFloat(.4f, -1f),
            Keyframe.ofFloat(.5f, -1f),
            Keyframe.ofFloat(.6f, -1f),
            Keyframe.ofFloat(.7f, -1f),
            Keyframe.ofFloat(.8f, -1f),
            Keyframe.ofFloat(.9f, 1f),
            Keyframe.ofFloat(1f, 1f)
        )
        // 透過 binding 取得 ggView
        val ggView = binding.ggView

        // 設定 ggView 關鍵影格
        ObjectAnimator.ofPropertyValuesHolder(ggView,
            pvhtranslationY,
            pvhtranslationX,
            pvhRotation,
            pvhScaledBy).apply {
            duration = 4000 // 動畫持續四秒
            repeatCount = ObjectAnimator.INFINITE  // 無限重播
            start()  // 開始播放
        }
    }
}

Fragment與Active 生命徵期略有不同
要特別注意一下
取元件的方法也修改成 binding.ggView
到這邊 原本動畫應該可以執行
而且下方換頁也應該要正常摟

遊戲 小雞BB

目前Fragment 只有兩個
差最後一個
我們進入 Navigation Graph
新增一個 showDetailFragment
然後在 historyFragment 右邊有個藍點點
從 historyFragment 拉到 showDetailFragment
整個導航地圖將會變成這樣
遊戲 小雞BB
完成~ 接下來開始寫遊戲摟

這樣代表可以在 historyFragment 裡面
透過某個方法跳頁到 showDetailFragment

這樣整個tab暫時先設定完成了

差異

到tab分頁這邊開始
開始感受到Swift與Kotlin之間比較明顯的差異了

Swift整個功能是準備好的
你只要拉到畫面上 稍微設定一下就完成了

Kotlin要設定很多東西
主要是很多小細節 只要漏了
功能就會無法運作

但最終達成的效果幾乎是一樣的
只是差在開發其順不順手而已

小碎嘴時間 ヽ(゚´Д`)ノ゚

今天篇幅大爆炸!
累死我了/︿\

鐵人賽進入的23天
在一週後就不用天天發文了

怎麼突然感覺....有點空虛 ( ・◇・)?

哇賽!我找虐啊!!
自虐啊我~

算了加油加油~剩下最後一週
衝刺!


上一篇
[Day22] swift & kotlin 遊戲篇!(4) 小雞BB-遊戲製作-遊戲畫面排版
下一篇
[Day24] swift & kotlin 遊戲篇!(6) 小雞BB-遊戲製作-線條繪製與彈跳動畫
系列文
雙平台APP小遊戲開發實作! Swift & Kotlin 攜手出擊~30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言