Kotlin學習之旅第十二天
今天主要是Koltin編寫代碼,進行功能實現
前言
Kotlin學習之旅(D6)-Kotlin Idioms part 1
Kotlin學習之旅(D7)-Kotlin Idioms part 2
Kotlin學習之旅(D8)-From Java to Kotlin
Kotlin學習之旅(D9)-Android Extensions
Kotlin學習之旅(D10)- Unit Test with Kotlin
新建項目
開發環境:
- MacOS Mojave Version 10.14
- Android Studio 3.2
創建NoteKeeper
- 打開Android Studio -> New Project -> 輸入項目名稱NoteKeeper
- 到Add an Activity to Mobile 的時候,選擇
Basic Activity
- 別忘了勾上
include kotlin support
- 完成項目創建
管理實體類
我們要記錄筆記,那麼首先就需要有一個實體類用於記錄課程,還有一個實體類用於記錄筆記信息,這個時候我們可以創建一個 數據管理類 來統一管理這些實體類
創建NoteKeeperData
在Java文件夾下的com.blue.notekeeper文件夾(你們的應該是各自的項目名稱) 右鍵,new -> kotlin file
創建一個名爲NoteKeeperData
的Kotlin File
注意:在新建文件的時候,下面有個kind選項,我們選擇的是File 而不是Class
此時我們的需求是:
- 課程有id,有title
- 筆記有title,text,還需要課程的信息
所以在NoteKeeperData裏面編寫代碼:
/**
* author : BlueLzy
* e-mail : [email protected]
* date : 2018/10/20 09:58
* desc :
*/
data class CourseInfo (val courseId: String, val title: String) {
override fun toString(): String {
return title
}
}
data class NoteInfo(var course: CourseInfo? = null, var title: String? = null, var text: String? = null){
override fun toString(): String {
return "課程: $course \n課題: $title \n筆記: $text"
}
}
CourseInfo
和 NoteInfo
都是Data Class,然後重寫toString()方法,返回我們需要的信息和指定的格式
知識點:Data Class , 字符串模板,構造函數,默認參數,空安全
數據管理類
有個實體管理類,我們就可以開始加入模擬數據了
首先創建一個Kotlin Class -> DataManager
定義存儲數據類型
我們使用HashMap來存儲課程(CourseInfo),使用ArrayList來存儲筆記(NoteInfo),因此定義兩個val
val courses = HashMap<String, CourseInfo>() // use courseId to find CourseInfo class
val notes = ArrayList<NoteInfo>() // Arraylist for NoteInfo
加入模擬的課程數據
我們通過courses.set() 方法加入數據
這裏有好幾種方式,還記得我們的CourseInfo類嗎
class CourseInfo (val courseId: String, val title: String)
我們實例化一個CourseInfo對象的時候,需要傳入courseId
和 title
兩個參數,但是傳參也有好幾種方式
不指定參數名,指定參數名,指定參數名但是不按順序 都是沒問題的,可以看代碼註釋
var course = CourseInfo("android_intent", "使用Intent進行Activity跳轉")
courses.set(course.courseId,course)
// 指定參數名
course = CourseInfo(courseId = "android_async", title = "Android中的異步編程")
courses.set(course.courseId, course)
// title 作爲第一個參數
course = CourseInfo(title = "Java 基礎", courseId = "java_lang")
courses.set(course.courseId, course)
// 不指定參數名
course = CourseInfo("java_core", "Java 核心編程")
courses.set(course.courseId, course)
加入模擬的筆記數據
var course = courses["android_intent"]!!
var note = NoteInfo(course, "我是Intent筆記",
"Intent裏面可以傳值,通過這種方式和其他頁面交互")
notes.add(note)
note = NoteInfo(course, "Intent 的方式",
"除了普通的Intent,還有PendingIntents")
notes.add(note)
course = courses["android_async"]!!
note = NoteInfo(course, "Service 的使用",
"Service 主要是運行在哪個線程?")
notes.add(note)
note = NoteInfo(course, "Service 的類型",
"前臺Service和後臺Service")
notes.add(note)
course = courses["java_lang"]!!
note = NoteInfo(course, "集合",
"List,Array,Map等")
notes.add(note)
note = NoteInfo(course, "面向對象",
"是不是這樣我們就不怕找不到對象了?")
notes.add(note)
course = courses["java_core"]!!
note = NoteInfo(course, "編譯選項",
"-jar 不兼容 -cp 命令")
notes.add(note)
note = NoteInfo(course, "序列化",
"記得包含 SerialVersionUID ")
notes.add(note)
DataManager完整代碼
package com.blue.notekeeper
/**
* author : BlueLzy
* e-mail : [email protected]
* date : 2018/10/20 10:00
* desc : Manage the data
*/
object DataManager {
val courses = HashMap<String, CourseInfo>() // use courseId to find CourseInfo class
val notes = ArrayList<NoteInfo>() // Arraylist for NoteInfo
// 構造函數,進行私有屬性的初始化,通常是代碼塊
init {
initializeCourses()
initializeNotes()
}
// 私有的初始化方法
private fun initializeCourses() {
var course = CourseInfo("android_intent", "使用Intent進行Activity跳轉")
courses.set(course.courseId,course)
// 指定參數名
course = CourseInfo(courseId = "android_async", title = "Android中的異步編程")
courses.set(course.courseId, course)
// title 作爲第一個參數
course = CourseInfo(title = "Java 基礎", courseId = "java_lang")
courses.set(course.courseId, course)
// 不指定參數名
course = CourseInfo("java_core", "Java 核心編程")
courses.set(course.courseId, course)
}
// 初始化Notes數據
private fun initializeNotes(){
var course = courses["android_intent"]!!
var note = NoteInfo(course, "我是Intent筆記",
"Intent裏面可以傳值,通過這種方式和其他頁面交互")
notes.add(note)
note = NoteInfo(course, "Intent 的方式",
"除了普通的Intent,還有PendingIntents")
notes.add(note)
course = courses["android_async"]!!
note = NoteInfo(course, "Service 的使用",
"Service 主要是運行在哪個線程?")
notes.add(note)
note = NoteInfo(course, "Service 的類型",
"前臺Service和後臺Service")
notes.add(note)
course = courses["java_lang"]!!
note = NoteInfo(course, "集合",
"List,Array,Map等")
notes.add(note)
note = NoteInfo(course, "面向對象",
"是不是這樣我們就不怕找不到對象了?")
notes.add(note)
course = courses["java_core"]!!
note = NoteInfo(course, "編譯選項",
"-jar 不兼容 -cp 命令")
notes.add(note)
note = NoteInfo(course, "序列化",
"記得包含 SerialVersionUID ")
notes.add(note)
}
}
數據展示頁-NoteListActivity
在完成了所需要的實體類和模擬數據之後,我們終於可以進入到界面的編寫階段了,首先創建NoteListActivity
我們通過這種方式來創建:
然後選擇 Empty Activity
就可以了,Android Studio會自動幫我們生成
- 繼承自CompatActivity的NoteListActivity文件
- activity_note_list.xml文件
OK,讓我們進入activity_note_list.xml,把代碼改成:
activity_note_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
tools:context=".NoteListActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_note_list" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_add_white_24dp" />
</android.support.design.widget.CoordinatorLayout>
然後再創建一個名爲: content_note_list
的xml文件,代碼爲:
content_note_list.xml
<?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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main">
<Spinner
android:id="@+id/spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/textNoteTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="@string/note_title_hint"
android:inputType="textMultiLine"
app:layout_constraintEnd_toEndOf="@+id/spinner"
app:layout_constraintStart_toStartOf="@+id/spinner"
app:layout_constraintTop_toBottomOf="@+id/spinner" />
<EditText
android:id="@+id/textNoteText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="@string/note_text_hint"
android:inputType="textMultiLine"
app:layout_constraintEnd_toEndOf="@+id/spinner"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/spinner"
app:layout_constraintTop_toBottomOf="@+id/textNoteTitle" />
</android.support.constraint.ConstraintLayout>
然後,編寫NoteListActivity
的代碼
class NoteListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_note_list)
setSupportActionBar(toolbar)
listNotes.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1,
DataManager.notes)
}
}
最後,修改我們的AndroidMainfest.xml文件,讓NoteListActivity成爲啓動項
<activity
android:name=".MainActivity"
android:label="@string/edit_note"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".NoteListActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
一切就緒,此時運行App,看到的效果應該是這樣的:
只有一個ListView用於展示模擬數據,FAB和Item都沒有點擊效果,接下來我們要往裏面加入其它功能了。
新建筆記
修改NoteListActivity
在NoteListActivity
加入FAB和Item的點擊事件,跳轉到MainActivity
在onCreate
方法加上:
fab.setOnClickListener { view ->
val activityIntent = Intent(this, MainActivity::class.java)
startActivity(activityIntent)
}
listNotes.setOnItemClickListener{ parent, view, position, id ->
val activityIntent = Intent(this, MainActivity::class.java)
activityIntent.putExtra(NOTE_POSITIONI, position)
startActivity(activityIntent)
}
創建名爲Constants
的Kotlin File
NOTE_POSITIONI
和 POSITION_NOT_SET
是無關界面的常量,因此我們需要創建一個文件來保存,這樣可以方便管理,代碼的結構也會比較清晰。
加入以下代碼:
const val NOTE_POSITIONI = "NOTE_POSITIONI"
const val POSITION_NOT_SET = -1
到目前爲止,數據列表頁的工作就完成了,接下來就是跳轉後的編輯頁面的工作
修改MainActivity
我們現在回到MainActivity,把MainActivity當成是新建和編輯筆記的頁面。首先修改佈局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>
然後修改content_main
<?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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main">
<Spinner
android:id="@+id/spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/textNoteTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="@string/note_title_hint"
android:inputType="textMultiLine"
app:layout_constraintEnd_toEndOf="@+id/spinner"
app:layout_constraintStart_toStartOf="@+id/spinner"
app:layout_constraintTop_toBottomOf="@+id/spinner" />
<EditText
android:id="@+id/textNoteText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="@string/note_text_hint"
android:inputType="textMultiLine"
app:layout_constraintEnd_toEndOf="@+id/spinner"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/spinner"
app:layout_constraintTop_toBottomOf="@+id/textNoteTitle" />
</android.support.constraint.ConstraintLayout>
由於佈局不是我們的重點,所以在這裏編寫和調整的過程就先忽略,大家只要能看懂大概是什麼意思就可以了
OK,回到MainActivity
我們需要處理幾個事情:
- 判斷進來的是新筆記還是舊筆記
- 新筆記不用處理
- 舊筆記需要把數據放到對應的位置
代碼如下:
class MainActivity : AppCompatActivity() {
private var notePosition = POSITION_NOT_SET // 定義變量記錄當前位置
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val coursesAdapter = ArrayAdapter<CourseInfo>(this,
android.R.layout.simple_spinner_item,
DataManager.courses.values.toList())
coursesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = coursesAdapter
notePosition = savedInstanceState?.getInt(NOTE_POSITIONI, POSITION_NOT_SET) ?:
intent.getIntExtra(NOTE_POSITIONI, POSITION_NOT_SET)
// 判斷是否點擊進來的Item,是的話填充數據
if (notePosition != POSITION_NOT_SET)
displayNote()
else {
DataManager.notes.add(NoteInfo())
notePosition = DataManager.notes.lastIndex
}
}
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState?.putInt(NOTE_POSITIONI, notePosition)
}
private fun displayNote() {
// 設置 textView 填充內容
val note = DataManager.notes[notePosition]
textNoteTitle.setText(note.title)
textNoteText.setText(note.text)
// 設置 spinner
val coursePosition = DataManager.courses.values.indexOf(note.course)
spinner.setSelection(coursePosition)
}
override fun onPause() {
super.onPause()
saveNote()
}
private fun saveNote() {
val note = DataManager.notes[notePosition]
note.title = textNoteTitle.text.toString()
note.text = textNoteText.text.toString()
note.course = spinner.selectedItem as CourseInfo
}
}
我們除了處理新建/編輯筆記兩種情況,還做了一點特殊處理
- 通過onPause()保存數據
- 通過onSaveInstanceState()保存狀態
OK,這個時候再運行一下App,效果應該是:
- 進入App通過列表形式展示模擬數據
- 點擊FAB進入一個新的頁面,填入數據後返回,在列表頁可以看到新增筆記
- 點擊列表中的任意一項,進入編輯頁面,筆記數據在對應位置展示出來
總結
其實到這裏我們就已經完成了大部分的功能了,明天我們主要的任務就是在ActionBar上加入一個Next按鈕,可以查看下一條筆記,這個功能有利於提高用戶體驗,這種交互也是Android官方所提倡的,通過按鈕或者手勢來進行交互。
Day 12 - Learn Kotlin Trip, Completed.