Edited at

ViewModel+LiveData+Databindingを使ってみる

キーボードが表示されているときだけViewを非表示にするというサンプルです。


ソースコード


設定


build.gradle

//略

dataBinding {
enabled = true
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$rootProject.kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
implementation 'androidx.core:core-ktx:1.1.0-alpha05'

//lifecycle
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha02'
}


dataBindingを有効にして

androidx.lifecycle:lifecycle-extensionsをインポートしています。

androidxを使わない場合は

android.arch.lifecycle:extensionsをインポートします。


AndroidManifest.xml

    <activity

android:name="com.ringored.view.StartActivity"
android:windowSoftInputMode="adjustResize"
>

windowSoftInputMode="adjustResize"にすることでキーボードが表示される度にキーボードの領域を除いた画面サイズで再描画します。


ViewModel


MainViewModel.kt

class MainViewModel(application: Application) : AndroidViewModel(application) {

val isKeyboardShowing: MutableLiveData<Boolean> by lazy {
MutableLiveData<Boolean>()
}
}

AndroidViewModelを継承した自作のViewModelに

isKeyboardShowingというBooleanのMutableLiveDataを作成します。

あとでこのisKeyboardShowingをレイアウトからobserve(観察)します


View


MainActivity.kt

class StartActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
showLogin()
}
}
fun showLogin(){
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, MainFragment(), TAG_OF_FRAGMENT)
.commit()
}
}


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</FrameLayout>

ActivityはFragmentをTransactionしているだけ

Activityに描画する場合は以下のFragmentをActivityに置き換えます。


MainFragment.kt


const val TAG_OF_FRAGMENT = "MainFragment"

class MainFragment : Fragment() {
private lateinit var binding: FragmentMainBinding
private var parent: ViewGroup? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
binding.lifecycleOwner = this
binding.viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
setKeyboardStatusObserver()
return binding.root
}

//keyboardの状態を監視してliveDataに通知
private fun setKeyboardStatusObserver() {
parent = activity!!.fragment_container as ViewGroup
parent?.viewTreeObserver?.addOnGlobalLayoutListener {
parent?.let {
//rootViewの高さを比較して200px以上あればimeが表示されているとみなす
binding.viewModel?.isKeyboardShowing?.postValue(it.rootView.height - it.height > 200)
}
}
}
}


bindingの型はxmlの名前をキャメルケースにして後ろにBindingをつけたものがビルド時に自動生成されます。

fragment_main-> FragmentMainBinding

うまくインポートできない場合はBuild->Rebuild Projectしてください。

binding.lifecycleOwner = this

lifecycleOwnerを自分のフラグメントに設定します。

これしないと動きません。

binding.viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

ViewModelProvidersからViewModelを取得してbindingにセットします。

parent?.viewTreeObserver?.addOnGlobalLayoutListener {

parent?.let {
//rootViewの高さを比較して200px以上あればimeが表示されているとみなす
binding.viewModel?.isKeyboardShowing?.postValue(it.rootView.height - it.height > 200)
}
}

viewが再描画される度にviewModelのisKeyboardShowingを更新します。


fragment_main.xml

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">

<data>
<import type="android.view.View"/>
<variable name="viewModel"
type="jp.co.synchrolife.vm.start.login.LoginViewModel" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isKeyboardShowing ? View.GONE: View.VISIBLE}"
app:srcCompat="@drawable/fruit_apple"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:hint="パスワード"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</layout>


全体をlayoutタグで囲み

View.GONE,VISIBLEを使用するためにandroid.view.Viewをインポートします

MainViewModelをobserveするためvariableにパッケージを追加します。

android:visibility="@{viewModel.isKeyboardShowing ? View.GONE: View.VISIBLE}" 

これでLiveData(isKeyboardShowing)が更新される度、GONE VISIBLEを切り替えてくるようになりました。