ViewBindingとは・・・?なんじゃんねぇ?
AndroidではLayout xmlとJavaかkotlinのソースが別々で、Layout xmlでは各Viewにidを振ります。
android:id="@+id/xxxbutton"
それをJavaかkotlinのソース側から
val button = findViewById(R.id.xxxbutton)
といった風に参照します。AndroidではkotlinだとViewBindingという方法が使えます。
参考:
公式Android developers ビューバインディング
ViewBindingのメリットは?
findViewById と ViewBindingを比較した場合のメリットは
- null の安全性が高まる。
- 無効なビュー ID によって NullPointerException が発生することがない。findViewByIdはidを間違えるとnullが返ってきます。(idはInt型であればなんでもいい)
- 型の安全性
- XML ファイル内で参照しているビューがclassに変換され、型を持ちます。バインディングクラス内のフィールドは、レイアウトファイルの型と一致します。そのため、ClassCastException が起きることがない。(無理に違う型に操作しようとすると、コンパイル時にわかる)
大きく分けて以上の2点のメリットが有ります。googleもViewBindingはfindViewByIdの後継として推奨しているようです。
ここで以前から勘違いしていたことに気がつく。
layout xmlのidですが、AndroidStudioが他のlayout xmlで使っているidと重複していると、警告を出してくるので
今まで、idは全てのlayout xmlを通してユニークでなければならないんだと思っていました。(なので、idの名前の付け方が悩ましかった・・・)
R.id.xxxと、いった具合に、R.idの下にフラットにできるのでそうなんだろうなと思っていました。
ところが、idはlayout xmlを通して重複していても何ら問題ありません。ちゃんと動きます。(じゃ、何でAndroidStudioはあんな警告出すんだ?)
オマケに同一のlayout xmlの中で重複していても、ビルドエラーにもなりません。動きます。ただ、おそらく、実行時に先に見つけた方のViewのidで動いているんだと思います。
なので・・・
ViewBindingなると、idの重複の制約がなくなって、やりやすくなった・・・と思ってましたが、それはfindViewByIdもViewBindingも、一緒でした。
ViewBindingを使えるようにするためには
ViewBindingはデフォルトでは有効になっていません。build.gradleに追記する必要があります。公式ホームページにもありますが2通りの指定方法があります。
android {
(中略)
viewBinding {
enabled = true
}
}
android {
(中略)
buildFeatures {
viewBinding true
}
}
僕はいつも上のやり方でやっています。修正してgradle同期すると使えるようになります。同期すると、layout xmlのファイル名に対応したclassができあがります。今、2つあるlayout xmlに対して
buildの下に2つのクラスができています。
ネーミングはだいたい想像が付きますが、layout xmlのファイル名はキャメルケースに変換して、その後ろにBindingが付きます。
ActivityでViewBindingを使う場合
ActivityでそれはfindViewByIdの場合は
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val txt: TextView = findViewById(R.id.helloTxt)
}
}
ViewBindingだと
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val txt = binding.helloTxt
}
}
といった書き方になります。
FlagmentでViewBindingを使う場合
Flagmentの場合はちょっと複雑です。findViewByIdの場合は,
class BlankFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_blank, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val txt: TextView = view.findViewById(R.id.helloTxt)
}
}
ViewBindingだと
class BlankFragment : Fragment() {
private var _binding: FragmentBlankBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentBlankBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val txt = binding.helloTxt
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
といった書き方になります。
で、結局、何が嬉しいの?
これだけを見るとViewBindingの方が若干、長くなっているんじゃないの?と思うかもしれませんが、上に上げた2つのメリットである、null の安全性、型の安全性を考えると使う価値はあるかと思います。
ViewBindingはキャッシュしてくれるので、findViewByIdよりも若干性能がいいという話も有ります。
簡潔かどうかという麺では
val txt: TextView = findViewById(R.id.helloTxt)
val txt = binding.helloTxt
ここだけを見ると、ViewBindingの方が簡潔であると言えます。(無理あるかなー)
ボタンのイベントリスナを設定する場合とかは
binding.button.setOnClickListener {
・・・
}
で一気に書いちゃう場合が多いです。