TWMA重製by AndoidStudio with kotlin

TWMA重製by AndoidStudio with kotlin

主要架構

Android Studio推薦使用kotlin做為開發語言
因此在本專案中,以kotlin為主

整體框架上

  • 透過Compose來描述整個app的UI(目前是把一個頁面的UI寫在一個.kt檔中)
  • 並利用類別來處理資料,整體資料的操作方法都被存放在WordList中
  • 在資料儲存的方面,透過SQL lite來做處理,專門去紀錄WordList的變動語資料

預計實現效果

介面相關
頁面切換
背景切換
布局切換

資料相關
單字的新增、刪除
單字的顯示
單字測試

系統相關
等級紀錄
金幣紀錄
抽獎系統

Compose系統

概述

Compose的使用就是在函數前面加上@compose標籤
每當一個Compose發生改動的時候,會自動摧毀並重建,達成刷新UI的效果

而最初的Compose會被放置在主函數下的setContent中啟用

tips 每當螢幕旋轉時,整個android app的生命週期會將UI給摧毀掉
因此若要保存Compose下的數據,需要使用by remember的方式

各種可組合項

實現UI介面效果上
在Compose中,可以利用下面列出的可組合項

Surface:

簡單來說,會將所有的元件放在這個平面上,且透過這個平面來處理modifier可以不用再單個元件上,添加太繁瑣的修飾

從字面上來理解,是一個平面,在 Material Design 設計準則中也同樣如此,我們可以將很多的元件擺放在這個平面之上,我們可以設定這個平面的邊框,圓角,顏色等等。 接下來,我們來用 Surface 組件做出一些不同的效果。

而 Surface 主要負責整個組件的形狀,陰影,背景等等,Surface 可以幫助我們更好的解耦一些程式碼,而不必在單一元件上添加很多的 Modifier 修飾符方法。
by https://jetpackcompose.cn/docs/layout/surface/

Row:

一種水平排列的組件
並且可以透過Arrangement來調整排列的方式

組件能夠將裡面的子項依照從左到右的方向水平排列。
by https://jetpackcompose.cn/docs/layout/row

Column:

一種垂直排列的組件
同樣可以透過Arrangement來調整排列的方式

example

column的模板程式,關於一個column的實現

@Composable
inline fun Column(
    modifier: Modifier? = Modifier,    //modifier 可以修改這個column的一些顯示上的參數
    verticalArrangement: Arrangement.Vertical? = Arrangement.Top,    //垂直上的對齊方式 靠上
    horizontalAlignment: Alignment.Horizontal? = Alignment.Start,    //水平上的對齊方式 靠右
    content: (@Composable @ExtensionFunctionType ColumnScope.() -> Unit)?
): Unit

tips Lazy Column
是一種類似Column的元件,但自帶懶加載功能
對於需要滑動列表,來檢視大量資料的地方可以使用
ref https://developer.android.com/jetpack/compose/lists?hl=zh-tw

Box

布局方式為九官格的定位方法
也可依次按照順序推疊

有用的子元件

Text 用來顯示字串
Icon 用來顯示圖標
Button 用於按鍵

Compose靜態預覽

在compose中可以透過在函式前面加上@preview關鍵字,並在函數內呼叫其他可組合項,來查看實際上的呈現結果

能夠節省每次都開虛擬機確認UI布局的時間,缺點是不能夠進行互動
因此如果是用代碼操控了可組合項的顯示、動畫,就沒辦法用這招預覽

//預覽 關燈模式
/*@Preview(
    showBackground = true,
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    name = "Dark"
)*/
@Preview(    //開燈模式
    showBackground = true,    //是否顯示背景
    uiMode = Configuration.UI_MODE_NIGHT_NO,    //設定當前的模式
    name = "light",    //該預覽的名稱
)
@Composable    //就算是預覽也要記得添加可組合向的修飾符
fun PreviewWordInput() {
    TWMATheme {
        WordInput(goToWordPage = {}, wordList = WordList())
    }
}

通用參數描述

modifier修飾符

可以添加用於修飾元件的描述,添加順序將會對效果造成影響

//語法範例
@compose
fun Show() {
    //在modifier後面可以用.來銜接不同的修飾
    Row(modifier = Modifier.fillMaxHeight()
       .alpha(0.75f)
       )
}

常見的修飾符包括

  • fillMaxHeight() 填充到父元件的高度
  • fillMaxWidth() 填充到父元件的寬度
  • fillMaxSize() 填充到父元件的大小
  • alpha(float) 調整透明度
  • setSize(int, int) 設定大小
  • padding(int) 留下特定寬度的邊框

針對於不同的命名空間,會有額外的修飾符選項
例如在RowSpace、ColumnSpace中
存在 weight(float) 的修飾符,可以定義該子元件在該row 或是 column下佔的空間權重

tips 使用weight後 setSize會失效

tips 各元件詳細的參數
請查詢jetpack compose的手冊
或是你好 Compose
ref1 https://developer.android.com/jetpack?hl=zh-tw
ref2 https://jetpackcompose.cn/docs/

狀態提升

將當前這個Compose存儲的狀態變數,拉到他的父組件上
並統一由最上層的父組件來管理狀態變數

當子組件需要改動父組件紀錄狀態的變數時,使用呼叫子組件時傳入的函數,以call back的方法來改動父組件的狀態變數

@compose
fun farther() {
    var state by remember{
        mutableStateOf(false)
    }
    
    child(()->{state = !state})
}

//將需要使用的狀態變數state提升到farther
//子組件利用changeState函數call back改變state
@compose
fun child(changeState: ()->Unit) {
    changeState()
}

tips 在compose中處理變數,每當compose重組,變數的資料就會被清掉
因此我們需要用by remember的方式來讓app保留這個變數
而添加mutableStateOf則是讓compose能夠監聽這個變數
一旦變數發生變化,該compose變化重組

透過navigation導航,可以實現android app中,當按下手機返回鍵,會回到上個頁面的效果

返回的感覺上類似stack(我自己覺得啦
整個navigation的設計上,也遵循著狀態提升的原則,如下

@Composable
fun AppNaviGation() {
    //透過navController
    //可以處理compose之間的切換
    //用於指定可以在可組合項之間切換的目的地
    
    val navController = rememberNavController()

    //而切換的時候
    //則會由navHost在目的地重組
    NavHost(navController = navController,
        startDestination = "WordInput") {
        //startDestination為最初始的位置

        //導航的地址註冊為一個String
        composable("WordInput") {
            WordInput(goToWordPage = {navController.navigate(route = "WordPage")},
                wordList = wordList)  //前往單字倉庫
        }
        composable("WordPage") {
            WordPage(rebackToWordInput = {navController.navigate(route = "WordInput")}, wordList = wordList) //回到單字輸入頁
        }

    }
}

kotlin的簡單語法

基礎語法-變數

//變數賦值
var variable: String    //var代表這個變數可以改動
val variable1: String    //val代表這個變數不能改動

var variable2: String? //?代表這個變數可能為空
    //切換回來的方法是
    //在使用前判斷if(variable2 != Unit)

var variable2: String = "This is a String"    //可以這樣初始化
var variable3 = "This is easy way"    //簡化 推薦使用該方法

函數

//函數宣告
//fun functionName(paraName: paraType): returnType{}
fun Fun1(parameter1: Int, para2: Int):Int {
    
    /*
     * function body
     */
    
    return 1
}

//可以設定預設值
fun Fun2(parameter1 = 1, para2 = 2):Int {}

//呼叫Fun1
var ans = Fun1(4, 5)

//指定parameter1參數接收的值,並讓para2用預設的值
ans = Fun2(parameter1 = 5)

OOP-類別

在kotlin中,類別有主建構子以及次建構子

執行順序是:

  • 主 constructor
  • 變數
  • init block 們
  • 最後才是第二個 constructor block 內的內容,第三個 constructor block 內的內容…依此類推

//使用主建構函數 直接在類別名稱後面加上contructor
class Word constructor(_data:String, _id:Int) {
    var word:String
    var id:Int
    
    //初始化block
    //可以有多個
    init {
        this.word = _data
        this.id = _id
    }
    
    //次建構函數 這裡的回傳是this(即主建構函數)
    //可以有多個存在
    constructor(): this(_data:String, _id:Int) {
        this.word = _data + "contrsucted by 1"
    }
    
    //利用傳入的參數defaultID 創建這個類別實體
    constructor(defaultID: Int): this(_data:String, _id:Int) {
        this.word = ""
        this.id = defaultID
    }
}

資料庫

利用SQL lite來實現輕量化的資料庫,需要

  • Database:資料庫
  • Dao:定義可以執行的操作
  • Entity:定義資料實體

Database

資料庫,透過@Database標籤

//資料庫
//entities定義的列表 表示當前資料庫中有幾個不同的資料實體(每個用一張表去紀錄
@Database(entities = [WordE::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao    //把DAO添加在這裡
}

Dao

參考SQL的語法,by https://www.fooish.com/sql/syntax.html
具體定義可以在表中進行的操作

@Dao
interface UserDao {

    //插入單字
    @Insert
    fun insertWord(wordE: WordE?): Long?    //回傳插入位置的id(類似第幾列的感覺)

    //查詢特定單字
    @Query("SELECT * FROM wordE WHERE id = :wordID")
    fun queryWord(wordID: Long): WordE?    //根據傳入的wordID在id的欄位中,找到特定的資料並取出

    //取出全部資料
    @Query("SELECT * FROM wordE")
    fun queryAll(): List<WordE>    //取出全部的資料,回傳一個資料實體形成的表格

    //example沒有實際作用
    @Delete
    fun delete(wordE: WordE?): Int

    //從id的欄位找到wordID並將其刪除
    @Query("DELETE FROM wordE WHERE id = :wordId")
    fun delete(wordId: Long): Int
}

Entity

資料實體,定義了存放在資料庫中的資料長相
類似於定義了一張表中,單個行存放的資料型態、名稱

必須有一個PrimaryKey,功能類似列的編號(第一列、第二列、第三…)

//每個entity相當於一張表
//類似定義一個表格中 一整列中每一行的欄位(名稱、儲存型態)
@Entity(tableName = "wordE")
data class WordE(
    //主鍵 用於在資料庫中識別不同的資料列
    @PrimaryKey(autoGenerate = true)    //自動分配key值 初始為1
    val id: Long?,

    //定義該行的名稱 以及儲存的類別型態
    @ColumnInfo(name = "foreignData") val foreignData: String?,
    @ColumnInfo(name = "localData") val localData: String?,
    @ColumnInfo(name = "extraInf") val extraInf: String?,
    @ColumnInfo(name = "correctRate") val correctRate: Double?
)

ref

How to view compose preview in landscape mode


TWMA重製by AndoidStudio with kotlin
https://z-hwa.github.io/webHome/[object Object]/App-develope/TWMA重製by-AndoidStudio-with-kotlin/
作者
crown tako
發布於
2023年10月22日
許可協議