Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ViewBindingで手軽にView操作

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for nichiyoshi nichiyoshi
February 12, 2020

 ViewBindingで手軽にView操作

Avatar for nichiyoshi

nichiyoshi

February 12, 2020
Tweet

More Decks by nichiyoshi

Other Decks in Programming

Transcript

  1. の前に、(iOSエンジニアの方もいるので ) まず前提 - Androidでは(今の所は、基本的に)xmlファイルでレイアウトを定義 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/text_main_activity" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </FrameLayout>
  2. これまでのView操作 findViewById class MainActivity : AppCompatActivity(R.layout.main_activity) { private lateinit var

    textView1: TextView private lateinit var textView2: TextView private lateinit var textView3: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) textView1 = findViewById(R.id.text_view1) textView2 = findViewById(R.id.text_view2) textView3 = findViewById(R.id.text_view3) textView1.text = "hoge" textView2.text = "fuga" textView3.text = "foo" } }
  3. これまでのView操作 findViewById class MainActivity : AppCompatActivity(R.layout.main_activity) { private lateinit var

    textView1: TextView private lateinit var textView2: TextView private lateinit var textView3: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) textView1 = findViewById(R.id.text_view1) textView2 = findViewById(R.id.text_view2) textView3 = findViewById(R.id.text_view3) textView1.text = "hoge" textView2.text = "fuga" textView3.text = "foo" } } 冗長
  4. これまでのView操作 findViewById class MainActivity : AppCompatActivity(R.layout.main_activity) { private lateinit var

    textView1: TextView private lateinit var textView2: TextView private lateinit var textView3: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // This will cause IllegalStateException: textView1 must not be null textView1 = findViewById(R.id.text_of_different_layout) textView1.text = "hoge" } } 他のレイアウトファイ ルも指定できる
  5. これまでのView操作 findViewById class MainActivity : AppCompatActivity(R.layout.main_activity) { private lateinit var

    textView1: TextView private lateinit var textView2: TextView private lateinit var textView3: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // This will throw IllegalStateException: textView1 must not be null textView1 = findViewById(R.id.text_of_different_layout) textView1.text = "hoge" } } 他のレイアウトファイ ルも指定できる null安全でない
  6. これまでのView操作 Kotlin Android Extension import kotlinx.android.synthetic.main.main_activity.* class MainActivity : AppCompatActivity(R.layout.main_activity)

    { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) text_view1.text = "hoge" text_view2.text = "fuga" text_view3.text = "foo" } }
  7. これまでのView操作 Kotlin Android Extension import kotlinx.android.synthetic.main.main_activity.* class MainActivity : AppCompatActivity(R.layout.main_activity)

    { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) text_view1.text = "hoge" text_view2.text = "fuga" text_view3.text = "foo" } } スネークケース
  8. これまでのView操作 Kotlin Android Extension import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_fragment.* class MainActivity

    : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) text_view1.text = "hoge" text_view2.text = "fuga" text_view3.text = "foo" // This will throw llegalStateException: textView1 must not be null text_of_different_layout.text = "huga" } } 他のレイアウトファイ ルも指定できる
  9. これまでのView操作 Kotlin Android Extension import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_fragment.* class MainActivity

    : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) text_view1.text = "hoge" text_view2.text = "fuga" text_view3.text = "foo" // This will throw llegalStateException: textView1 must not be null text_of_different_layout.text = "huga" } } null安全でない 他のレイアウトファイ ルも指定できる
  10. これまでのView操作 Data Binding <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data>

    <variable name="user" type="com.example.nichiyoshi.viewbindingsample.User" /> </data> <LinearLayout android:orientation="vertical" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:text="@{user.name}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/text1" import androidx.databinding.DataBindingUtil import com.example.nichiyoshi.viewbindingsample.databinding.MainActivityBinding class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<MainActivityBinding>(this, R.layout.main_activity) binding.user = User("TARO") binding.text1.text = "hoge" } }
  11. これまでのView操作 Data Binding <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data>

    <variable name="user" type="com.example.nichiyoshi.viewbindingsample.User" /> </data> <LinearLayout android:orientation="vertical" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:text="@{user.name}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/text1" import androidx.databinding.DataBindingUtil import com.example.nichiyoshi.viewbindingsample.databinding.MainActivityBinding class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<MainActivityBinding>(this, R.layout.main_activity) binding.user = User("TARO") binding.text1.text = "hoge" } } ちょっと大変 コンパイルに 時間かかる
  12. 仕組み - レイアウトファイルを作成すると、自動で ViewDataBindingを継承したクラスが作成さ れる - main_activity.xml => MainActivityBinding -

    生成されたコードは、ビルド後に以下で確認 できる - ~/app/build/generated/data_binding_base_ class_source_out/
  13. 仕組み // Generated by data binding compiler. Do not edit!

    package com.example.nichiyoshi.viewbindingsample.databinding; import ... public abstract class MainActivityBinding extends ViewDataBinding { @NonNull public final FrameLayout container; @NonNull public final TextView textView1; protected MainActivityBinding(Object _bindingComponent, View _root, int _localFieldCount, FrameLayout container, TextView textView1) { super(_bindingComponent, _root, _localFieldCount); this.container = container; this.textView1 = textView1; } @NonNull public static MainActivityBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot) { return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent()); } - レイアウトファイルを作成すると、自動で ViewDataBindingを継承したクラスが作成さ れる - ビルドしなくても参照できる - main_activity.xml => MainActivityBinding - 生成されたコードは、ビルド後に以下で確認 できる - ~/app/build/generated/data_binding_base_ class_source_out/
  14. 実装方法 準備 - Android Studio 3.6 Canary 11 以降をインストール -

    gradle pluginのバージョンを合わせる - Android Studio 3.6 Canary 11 の場合(プロジェクトレベルの build.gradle) - classpath 'com.android.tools.build:gradle:3.6.0-rc02' - モジュールのbuild.gradleに以下を追加 - android { - viewBinding.enabled = true - } - sync
  15. 実装方法 Activityの場合 import com.example.nichiyoshi.viewbindingsample.databinding.MainActi vityBinding class MainActivity : AppCompatActivity() {

    private lateinit var binding: MainActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) setContentView(binding.root) } override fun onStart() { super.onStart() binding.textView1.text = "hoge" } }
  16. 実装方法 Activityの場合 import com.example.nichiyoshi.viewbindingsample.databinding.MainActi vityBinding class MainActivity : AppCompatActivity() {

    private lateinit var binding: MainActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) setContentView(binding.root) } override fun onStart() { super.onStart() binding.textView1.text = "hoge" } } もし参照エラーになったら Invalidate caches and restart で解決するかも
  17. 実装方法 Activityの場合 import com.example.nichiyoshi.viewbindingsample.databinding.MainActi vityBinding class MainActivity : AppCompatActivity() {

    private lateinit var binding: MainActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) setContentView(binding.root) } override fun onStart() { super.onStart() binding.textView1.text = "hoge" } } - レイアウトファイルはこれまで通り - 簡単 - Null安全 - bindingインスタンスからViewを取得し ている限り、常に同一のレイアウトファ イルであることを保証 - Type安全
  18. 実装方法 Fragmentの場合 1 class MainFragment : Fragment() { private var

    mainFragmentBinding: MainFragmentBinding? = null private val binding get() = mainFragmentBinding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mainFragmentBinding = MainFragmentBinding.inflate(inflater) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.text1.text = "hoge" } override fun onDestroyView() { super.onDestroyView() mainFragmentBinding = null } }
  19. 実装方法 Fragmentの場合 1 class MainFragment : Fragment() { private var

    mainFragmentBinding: MainFragmentBinding? = null private val binding get() = mainFragmentBinding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mainFragmentBinding = MainFragmentBinding.inflate(inflater) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.text1.text = "hoge" } override fun onDestroyView() { super.onDestroyView() mainFragmentBinding = null } } Viewのライフサイクルよりも Fragmentのライフサイクルの方が長い => Viewのライフサイクルが終わる時 (onDestroyView)に mainFragmentBinding の参照をリリースして、 ViewTreeのメモリリークを防ぐ
  20. 実装方法 Fragmentの場合 1 class MainFragment : Fragment() { // private

    var mainFragmentBinding: MainFragmentBinding? = null // private val binding get() = mainFragmentBinding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val binding = MainFragmentBinding.inflate(inflater) binding.text1.text = "hoge" return binding.root } // override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // super.onViewCreated(view, savedInstanceState) // binding.text1.text = "hoge" // } // override fun onDestroyView() { // super.onDestroyView() // mainFragmentBinding = null // } } 不要なら、bindingインスタンスをフィールドで持たない方が安全!!
  21. 実装方法 Fragmentの場合 2 (コンストラクタでレイアウト指定) class MainFragment : Fragment(R.layout.main_fragment) { private

    var mainFragmentBinding: MainFragmentBinding? = null private val binding get() = mainFragmentBinding!! override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mainFragmentBinding = MainFragmentBinding.bind(view) } override fun onStart() { super.onStart() binding.text1.text = "hoge" } override fun onDestroyView() { super.onDestroyView() mainFragmentBinding = null } } inflateメソッドではなくbindメソッドを使う