Android鬼点子-Kotlin+DataBinding+LiveData+Lifecycle的奇妙反应

Kotlin是谷歌官方最新支持的一级语言,新版的Android Studio3.0 Beta 版已经原声支持Kotlin。昨天的新闻说J神也加入了谷歌的Kotlin组,觉得Kotlin会成为大势啊!

kotlin.jpg

LiveData 是一个数据持有者类,它持有一个值并允许观察该值。它还可以与Lifecycle绑定,与观察者的生命周期同步。简单的说就是,把数据放到LiveData里面,然后给LiveData设置监听器,当数据改变的时候,会自动调用监听器。与Lifecycle绑定,当Activity被回收之后不会被触发监听。配合单例模式,可以很轻松的实现在一处修改数据,多处activity,fragment收到通知。

DataBinding可以让你的的UI代码变得相当干净利落。它可以把页面逻辑从你的代码中提取出来。让你的代码更加专注处理其他事情。

我们把DataBinding的ViewModel设置成LiveData,在LiveData的监听器中调用DataBinding的刷新。这项在任何地方修改ViewModel,UI就会自动更新。

接下来,举个例子。我们在上面的输入框输入文本。下面的文本框会实时反映输入的内容。

tt2.gif

首先是配置环境,我使用的是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(this, R.layout.activity_main)把布局加载进来,这里要注意的是ActivityMainBinding这个类可能一开始没有生成,可以尝试rebuild一下,或者重启IDE。
获取到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没有感知到数据的改变,所以不会刷新界面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值