19

I'd like to use try out the ViewBinding with custom view, for example:

MainActivity <=> layout_main.xml
MyCustomView <=> layout_my_custom_view.xml

layout_main.xml

<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.example.myapplication.MyCustomView
            android:id="@+id/custom_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
</FrameLayout>

layout_my_custom_view.xml

<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/line1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Line1" />

        <View
            android:id="@+id/divider"
            android:layout_width="match_parent"
            android:layout_height="2dp"
            android:background="#2389bb" />

        <TextView
            android:id="@+id/line2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Line2" />
</LinearLayout>

MainActivity

class MainActivity : AppCompatActivity() {

    private lateinit var binding: LayoutMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = LayoutMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.customView.line1.text = "Hello"
        binding.customView.line2.text = "World"
    }
}

In my MainActivity, I can use the binding to find MyCustomView but I can't further find @id/line1 and @id/line2 in MyCustomView. In this case, is it possible to use ViewBinding only or do I have to use findViewById or Kotlin synthetic ??

Thanks in advance.

Z.J Hung
  • 475
  • 1
  • 5
  • 12

3 Answers3

41

ViewDataBinding.inflate doesn't generate of child view accessor inside custom view.

thus, you can't touch line1(TextView) via only use ViewDataBinding.

If you don't want using findViewById or kotlin synthetic, MyCustomView also needs to apply ViewDataBinding. try as below.

CustomView

class MyCustomView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
    private val binding =
        CustomLayoutBinding.inflate(LayoutInflater.from(context), this, true)

    val line1
        get() = binding.line1

    val line2
        get() = binding.line2
}

MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
    setContentView(binding.root)

    with(binding.customView) {
        line1.text = "Hello"
        line2.text = "World"
    }
}
Ethan Choi
  • 2,339
  • 18
  • 28
  • Great. So in your opinion, which one do you suggest? Apply ViewBinding in MyCustomView and expose child views as properties? Or use `findViewById` or `kotlin synthetic`? – Z.J Hung Feb 27 '20 at 03:28
  • @Z.JHung That depends on your needs. If exist `MyCustomViewModel` for `MyCustomView`, `ViewDataBinding` is more effective, otherwise it's better to use `kotlin synthetic`. – Ethan Choi Feb 27 '20 at 05:12
  • 1
    could you also make the val binding in CustomView public, and then access line1 or line2 in MainActivity in the following way: binding.customView.binding.line1? Or is it a bad practice to do it this way? – Praveen P. Nov 29 '20 at 15:23
  • Can someone confirm or correct my assumption here: attachToRoot = true, when using as view inside a normal layout/view (e.g. fragment) and since we will set it to attachToRoot = false inside the onCreateViewHolder within an Adapter (if we use it as itemView) it does not matter if we have set it to true here – Javatar Jun 23 '21 at 13:45
5

Another approach is to return the CustomView binding object.

class CustomView constructor(context: Context, attrs: AttributeSet?) : 
    ConstraintLayout(context, attrs){
                
        private val _binding: CustomViewBinding = CustomViewBinding.inflate(
                        LayoutInflater.from(context), this, true)
                
                val binding get() = _binding    
        }

And then in your Activity or Fragment:

binding.customView.binding.line1?.text = "Hello"
binding.customView.binding.line2?.text = "World"
Igor Fridman
  • 1,267
  • 1
  • 16
  • 30
0

I believe you can have setter in your custom view. Since ViewBinding generates binding class for your main layout, it should return you the CustomView class. Thus, you can use the setter you just wrote for changing the texts.

  • Sounds like I have to make MyCustomView a more wrapped class. I have to add `setLine1Text()`, `setLine2Text()`, `setLine1Visibility()`, `setLine2Visibility()`,... etc. I might give it a try when custom view is not holding too many sub views. – Z.J Hung Feb 27 '20 at 03:10
  • Or, for second thought, you can even use `attrs.xml` file to set the attributes. That way, it would be more like a view instead of wrapper class. – Arda Kucukoz Feb 27 '20 at 03:30