[Day23] swift & kotlin 游戏篇!(5) 小鸡BB-游戏制作-Tab功能分页

游戏示意

游戏 小鸡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天
在一周後就不用天天发文了

怎麽突然感觉....有点空虚 ( ・◇・)?

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

算了加油加油~剩下最後一周
冲刺!


<<:  Day 16:Next 布景客制化 - 让副标题显示於标题内

>>:  Day29-机器学习(3) Kmeans

Day16 专有名词介绍 I

当广告跑一阵子後,你可以会对於名词有点困惑,它们各别代表什麽意思呢? 今天就来做比较深入的了解,这样...

[Day26] 电脑有秘密档案不想被发现吗? 教你用图片伪装秘密档案!

大家都怎麽藏电脑里的秘密档案呢? 最多人用的方法应该是设隐藏资料夹吧! 但是这个方法已经深植人心,改...

灵异现象 - Windows SMB 用 Domain name 能通,用 IP 不能通

「灵异现象」 Windows SMB 用 Domain name 能通,用 IP 不能通 灵异现象系...

【没钱买ps,PyQt自己写】Day 19 - 使用 QProgressBar,制作进度条的功能

看完这篇文章你会得到的成果图 前言 这篇我们要来学一个新的东西 QProgressBar! QPro...

InnoDB的表格空间-Part1(区、段、区的分类、段的结构)

透过前面的内容大家知道表格空间是一个抽象的概念,对系统表格空间来说,对应着档案系统中一个或多个档案;...