Kotlin是谷歌官方最新支援的一級語言,新版的Android Studio3.0 Beta 版已經原聲支援Kotlin。昨天的新聞說J神也加入了谷歌的Kotlin組,覺得Kotlin會成為大勢啊!
LiveData 是一個資料持有者類,它持有一個值並允許觀察該值。它還可以與Lifecycle繫結,與觀察者的生命週期同步。簡單的說就是,把資料放到LiveData裡面,然後給LiveData設定監聽器,當資料改變的時候,會自動呼叫監聽器。與Lifecycle繫結,當Activity被回收之後不會被觸發監聽。配合單例模式,可以很輕鬆的實現在一處修改資料,多處activity,fragment收到通知。
DataBinding可以讓你的的UI程式碼變得相當乾淨利落。它可以把頁面邏輯從你的程式碼中提取出來。讓你的程式碼更加專注處理其他事情。
我們把DataBinding的ViewModel設定成LiveData,在LiveData的監聽器中呼叫DataBinding的重新整理。這項在任何地方修改ViewModel,UI就會自動更新。
接下來,舉個例子。我們在上面的輸入框輸入文字。下面的文字框會實時反映輸入的內容。
首先是配置環境,我使用的是AndroidStudio3.0 Beta1
Project的gradle如下:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.1.3-2'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-beta1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
ext.arch_version = "1.0.0-alpha7"
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://maven.google.com' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}複製程式碼
Module的gradle如下:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.greendami.gdm"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dataBinding {
enabled = true
}
}
dependencies {
kapt 'com.android.databinding:compiler:3.0.0-beta1'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.0.1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:0.5'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
compile "android.arch.persistence.room:runtime:$arch_version"
compile "android.arch.lifecycle:runtime:$arch_version"
compile "android.arch.lifecycle:extensions:$arch_version"
annotationProcessor "android.arch.persistence.room:compiler:$arch_version"
annotationProcessor "android.arch.lifecycle:compiler:$arch_version"
}
kapt {
generateStubs = true
}複製程式碼
比較重要的是dataBinding {enabled = true}開啟databing。
下面開始擼程式碼。
首先是ViewModel類,也是一個LiveData類。
package com.greendami.gdm
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
/**
* Created by GreendaMi on 2017/8/10.
*/
class SearchViewModel : ViewModel() {
val show_string = MutableLiveData<String>()
val input_string = MutableLiveData<String>()
}複製程式碼
這個類裡面只有2個欄位,分別對應介面上的輸入框的內容,和文字框的顯示內容。
然後是佈局檔案acticity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<import type="com.greendami.gdm.SearchViewModel" />
<variable
name="model"
type="SearchViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.greendami.gdm.MainActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/input"
android:inputType="textNoSuggestions"
android:imeOptions="actionSearch"
tools:text="google"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{model.show_string.value}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>複製程式碼
首先最頂層的佈局是layout,然後下面第一個是data域,是宣告資料的地方。後面就是正常的佈局。我們首先宣告瞭一個SearchViewMode型別的物件model。然後再TextView中進行繫結android:text="@{model.show_string.value}"。這裡的.value是LiveData的取值的方式。
然後是MainActivity.kt
package com.greendami.gdm
import android.arch.lifecycle.LifecycleActivity
import android.arch.lifecycle.Observer
import android.databinding.DataBindingUtil
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import com.greendami.gdm.databinding.ActivityMainBinding
class MainActivity : LifecycleActivity() {
var searchViewModel: SearchViewModel = SearchViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
searchViewModel.show_string.value = "等待輸入"
binding.model = searchViewModel
binding.input.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
binding.model?.show_string?.value = p0.toString()
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
})
binding.model?.show_string?.observe( this@MainActivity, Observer{
Log.e("TAG",binding.model?.show_string?.value)
Log.e("TAG",binding.hasPendingBindings().toString())
binding.invalidateAll()
})
}
}複製程式碼
說明一下var binding = DataBindingUtil.setContentView
獲取到binding物件之後,就可以給物件繫結ViewModel了,也就是程式碼中的searchViewModel。
我們使用binding物件.控制元件ID來拿到控制元件物件。比如:
binding.input.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
binding.model?.show_string?.value = p0.toString()
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
})複製程式碼
在監聽器中修改LiveData的值。
因為我們設定了LiveData的觀察者
binding.model?.show_string?.observe( this@MainActivity, Observer{
Log.e("TAG",binding.model?.show_string?.value)
Log.e("TAG",binding.hasPendingBindings().toString())
binding.invalidateAll()
})複製程式碼
當資料修改後這裡就會被呼叫。這裡的第一個引數this,是LifecycleActivity,所以這個監聽器繫結了MainActvity的生命週期。這裡使用了binding.invalidateAll(),而沒有使用binding.executePendingBindings()是因為這裡改變LiveData的value後,binding.hasPendingBindings()返回false,也就是說,databinding沒有感知到資料的改變,所以不會重新整理介面。