Kotlin學習之旅(D12)-第一個App之功能實現

Kotlin學習之旅第十二天

今天主要是Koltin編寫代碼,進行功能實現

前言

Kotlin學習之旅(D1)-學習計劃&基本語法

Kotlin學習之旅(D2)-基本語法

Kotlin學習之旅(D3)-類與繼承

Kotlin學習之旅(D4)-函數與Lambda表達式

Kotlin學習之旅(D5)-高級語法

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

Kotlin學習之旅(D11)-第一個App之項目介紹

新建項目

開發環境:

  • MacOS Mojave Version 10.14
  • Android Studio 3.2

創建NoteKeeper

  1. 打開Android Studio -> New Project -> 輸入項目名稱NoteKeeper
  2. 到Add an Activity to Mobile 的時候,選擇 Basic Activity

Snip20181021_13.png

  1. 別忘了勾上 include kotlin support
  2. 完成項目創建

管理實體類

我們要記錄筆記,那麼首先就需要有一個實體類用於記錄課程,還有一個實體類用於記錄筆記信息,這個時候我們可以創建一個 數據管理類 來統一管理這些實體類

創建NoteKeeperData

在Java文件夾下的com.blue.notekeeper文件夾(你們的應該是各自的項目名稱) 右鍵,new -> kotlin file

Snip20181021_14.png

創建一個名爲NoteKeeperData 的Kotlin File

注意:在新建文件的時候,下面有個kind選項,我們選擇的是File 而不是Class

Snip20181021_15.png

此時我們的需求是:

  1. 課程有id,有title
  2. 筆記有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"
    }
}

CourseInfoNoteInfo 都是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對象的時候,需要傳入courseIdtitle 兩個參數,但是傳參也有好幾種方式

不指定參數名,指定參數名,指定參數名但是不按順序 都是沒問題的,可以看代碼註釋

        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

我們通過這種方式來創建:

Snip20181021_16.png

然後選擇 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,看到的效果應該是這樣的:

Snip20181021_17.png

只有一個ListView用於展示模擬數據,FAB和Item都沒有點擊效果,接下來我們要往裏面加入其它功能了。

新建筆記

修改NoteListActivity

NoteListActivity加入FAB和Item的點擊事件,跳轉到MainActivityonCreate方法加上:

  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_POSITIONIPOSITION_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

我們需要處理幾個事情:

  1. 判斷進來的是新筆記還是舊筆記
  2. 新筆記不用處理
  3. 舊筆記需要把數據放到對應的位置

代碼如下:


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.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章