【Kotlin 1.3】Android程式初體驗 - Kotlin篇
話說,全世界的手機系統應該都被Android跟iOS佔光光了吧,不過最近又跑出來了一個鴻蒙系統,也許未來有可能成為第三大的手機系統吧?至於為什麼要學安卓呢?因為之前有在學一點點的Flutter,發現還是要兩個平臺都要了解一些些,才知道Flutter到底在做什麼?加上本來就是兩個不同的系統,在設定上一定有所差異,加上聽說Kotlin跟Swift很相像,所以就來學學看了。因為本身對iOS比較熟一點,所以後面會以iOS的名詞來做不專業的說明。話不多說,這裡就用跟JetBrains合作的Android Studio,其實Kotlin也是JetBrains出品的。安裝的過程就不多做說明了,現在馬上就來試試看吧。
做一個長這樣的APP
IDE環境
新增Android專案
- 可以選擇專案一開始的樣子,一般都是會選「Empty Activity」,其中「Activity」就有點像iOS的「ViewControler」,然後可以選擇語言,這裡我們當然是選「Kotlin」,最小API版本就選Anroid 5.0 (API 21)
編譯環境
- 這個就不多說了,有圖有真象
新增Android手機模擬器
- 這個就跟Xcode在新增模擬器的方法就不太一樣了,因為Android手機的廠商太多了,所以Android手機模擬器可以有很高度的客制化,這裡就不多做說明,不過在選擇x86_Image_API的時候要記得下載該image檔,才能做新增。另外值得一提的是,iOS模擬器是直接以x86-CPU去做的,但是Android模擬器是真的以Qemu去模擬手機硬體的。
執行Android程式
- 在這裡可以選擇實機或模擬器來執行
APP的第一頁
建立資源檔
- 在「res/values/strings.xml」的內容內,都是可以重複利用的文字,有點像iOS的plist檔,其中「app_name」就是APP的名字
<resources>
<string name="app_name">Android_HelloWorld</string>
<string name="select_box">請選擇其中一個</string>
<string name="input_name">請輸入玩家姓名</string>
<string name="select_scissors">剪刀</string>
<string name="select_rock">石頭</string>
<string name="select_paper">布</string>
<string name="start_game">猜拳</string>
<string name="label_name">姓名</string>
<string name="label_winner">勝利者</string>
<string name="label_me">我方出拳</string>
<string name="label_pc">電腦出拳</string>
<string name="title_activity_sub">SubActivity</string>
<string name="nextPage">下一頁</string>
<string name="nextPageTitle">第二頁</string>
<string name="goBackButton">回上一頁</string>
</resources>
畫面程式組合
- 一般檔案的長相基本上是「OOxx.kt + xx_oo.xml」為一組頁面,在iOS中就是像「OO.swift + OO.xib」的組合,在目前的Android寫法,還沒有像Storyboard的方式出現。在這裡使用的是「ConstraintLayout」,Layout產生的方式跟iOS的不太一樣,其中主要說明的是「android:id="@+id/?????"」的這個屬性,它就是這個元件的代號,可以把它想成是HTML的id,是個唯一值識別碼,另外「android:text="@string/?????"」的"@string"就是引用「res/values/strings.xml」內的文字mapping,只要改strings.xml,這裡的內容也會跟著改變。
第一頁的長相
- 在這裡雖然跟可以用拉的去放元件,但總覺得沒有Xcode的好用,也許直接key比較快?先求有再求好吧。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:layout_width="match_parent" android:layout_height="match_parent">
<TextView
android:id="@+id/select_box"
android:text="@string/select_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/input_name"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"/>
<EditText
android:id="@+id/input_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:ems="10"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:hint="@string/input_name"/>
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/select_box"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/select_scissors"
android:text="@string/select_scissors"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"/>
<RadioButton
android:id="@+id/select_rock"
android:text="@string/select_rock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<RadioButton
android:id="@+id/select_paper"
android:text="@string/select_paper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</RadioGroup>
<Button
android:id="@+id/start_game"
android:text="@string/start_game"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/radioGroup"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"/>
<TextView
android:id="@+id/label_name"
android:text="@string/label_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/start_game"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"/>
<TextView
android:id="@+id/label_winner"
android:text="@string/label_winner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/start_game"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@+id/label_name"/>
<TextView
android:id="@+id/label_me"
android:text="@string/label_me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/start_game"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@+id/label_winner"/>
<TextView
android:id="@+id/label_pc"
android:text="@string/label_pc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="@+id/start_game"
app:layout_constraintStart_toEndOf="@+id/label_me"/>
<Button
android:id="@+id/nextPage"
android:text="@string/nextPage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</android.support.constraint.ConstraintLayout>
onCreate()
- onCreate()可以類比成iOS的viewDidLoad(),是程式的進入點,只執行一次,細節可以參考一下Android的生命週期,其中比較重要的是「setContentView(R.layout.main_activity)」就是載入「main_activity.xml」畫面檔到這個「Activity」內使用。另外EditText?這個「?」就表示它是「可以」是null值的意思,跟Swift的表示法相當。
class MainActivity : AppCompatActivity() {
private var inputName: EditText? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
}
}
R.java
- R.java是一個很重要的檔,在「res」底下的檔案全部都會「自動編譯」在裡面,可以看看裡面都是些int值,代表的是各個資源檔的唯一ID值,利用「R.OO.XX」之類的程式,就可以讀取這些資源了,這個真的滿好用的,點點點就寫完了,但沒事別亂動它。在iOS也有學它的framework,叫做「R.swift」。
/* AUTO-GENERATED FILE. DO NOT MODIFY.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package com.example.android_helloworld;
public final class R {
public static final class anim {
public static final int abc_fade_in=0x7f010000;
public static final int abc_fade_out=0x7f010001;
public static final int abc_grow_fade_in_from_bottom=0x7f010002;
...
}
}
findViewById()
- 這個動作好像是一定會有的?利用findViewById()去讓該元件初始化,然後進行接下來的操作。不過要一個一個寫好麻煩啊,為什麼不用類似R.java的寫法呢?或者像@IBOutlet用拉的呢?XD
class MainActivity : AppCompatActivity() {
/// 初始化設定元件
private fun initElements() {
inputName = findViewById(R.id.input_name)
radioGroup = findViewById(R.id.radioGroup)
startGame = findViewById(R.id.start_game)
nextPage = findViewById(R.id.nextPage)
labelName = findViewById<TextView>(R.id.label_name)
labelMe = findViewById<TextView>(R.id.label_me)
labelWinner = findViewById<TextView>(R.id.label_winner)
labelPC = findViewById<TextView>(R.id.label_pc)
}
}
setOnClickListener()
- 利用setOnClickListener()設定按鍵按下的反應,在iOS應該類似叫.addTarget()。另外要說明的是「?:」這個運算子,它有點像Swift的 「guard let else」。
class MainActivity : AppCompatActivity() {
/// 設定猜拳按鈕按下的動作 (沒有寫名字就跳提示)
private fun setButtonListener() {
val startGame = startGame ?: return
val nextPage = nextPage ?: return
startGame.setOnClickListener { moraResult() }
nextPage.setOnClickListener { nextPage() }
}
}
intent
- intent在這裡的功能就是「跳到下一頁」,順便把「把包Bundle內的東西」一起帶過去,它比較像iOS的Segue,startActivityForResult()在iOS比較像present()。然後有回傳值的函式是長這樣,「fun bundleMaker(): Bundle」
class MainActivity : AppCompatActivity() {
/// 跳到下一頁 (帶值)
private fun nextPage() {
val bundle = bundleMaker()
val intent = Intent(this, SubActivity::class.java)
bundle.putString("Winner", labelWinner?.text.toString())
bundle.putString("UserName", inputName?.text.toString())
intent.putExtras(bundle)
startActivityForResult(intent, 1)
}
/// 產生要帶去下一頁的值
private fun bundleMaker(): Bundle {
val bundle = Bundle()
bundle.putString("Winner", labelWinner?.text.toString())
bundle.putString("UserName", inputName?.text.toString())
return bundle
}
}
Kotlin的Switch case
- Kotlin的Switch case叫做when,功能很強大,除了有「Switch case」的功能之外,還可以有「if-else if」的能力,真是太厲害了啊。
class MainActivity : AppCompatActivity() {
/// 我方出拳的結果 (勾選值)
private fun moraResultForMe(): Int {
val selectScissors = findViewById<RadioButton>(R.id.select_scissors)
val selectRock = findViewById<RadioButton>(R.id.select_rock)
val selectPaper = findViewById<RadioButton>(R.id.select_paper)
var answer = 0
when {
selectScissors.isChecked -> answer = 0
selectRock.isChecked -> answer = 1
selectPaper.isChecked -> answer = 2
}
return answer
}
}
onActivityResult()
- onActivityResult()的功能就是從上一頁回到這一頁會發生的事,可以帶值回來,有點像iOS的Unwind Segue。
class MainActivity : AppCompatActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
getReturnValueForNextPage(requestCode, resultCode, data)
}
}
APP的第二頁
新增第二頁
- 新增第二頁,也就是加上一個新的Activity,在iOS中就是加上一個新的ViewController,比較要注意的是,除了會產生「OOxx.kt + xx_oo.xml」的組合之外,還會在「AndroidManifest.xml」中加上「<activity android:name=".OOXXActivity"></activity>」,用來表示這一頁是有被Build的,如果沒有加的話,就會讀不到這頁了喲。AndroidManifest.xml這個檔非常的重要,有空大家可以去查一查喲。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android_helloworld">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".SubActivity">
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
取得上一頁傳來的值
- 這裡呢,也是利用intent (iOS: Segue)來取得數值包(extras),另外「let」的功能就自己看一下囉。
class SubActivity : AppCompatActivity() {
/// 取得上一頁傳過來的數值
private fun getValueForPreviousPage(): String? {
var title: String? = null
intent?.extras.let {
title = it?.getString("Winner", "什麼都沒有")
}
return title
}
}
setResult()
- 把值傳到上一頁也是利用intent帶值回去,然後利用setResult()把值傳回去,然後在上一頁的「onActivityResult()」就會收到了喲。
class SubActivity : AppCompatActivity() {
/// 回傳數值到上一頁
private fun returnValueForPreviousPage(subValue: String) {
val bundle = Bundle()
val intent = Intent()
bundle.putString("SubValue", subValue)
intent.putExtras(bundle)
setResult(Activity.RESULT_OK, intent)
}
}
範例程式碼下載
後記
- 其實Kotlin的語法雖然跟Swift有點像,應該說Swift有去參考Kotlin吧?但是還是有滿大的差別的,還是要先看看kotlin語法才是,它是另一種JVM程式語言,也許將來它會越來越強大呢?其實我也是一知半解的,騙吃騙吃啦。