Search code examples
androidandroid-layoutkotlinandroid-databindingandroid-jetpack

Enabling the `onClick` method in a custom FrameLayout view


I'm not able to get a onClick method to work with a custom FrameLayout view.

When I refactor a default Button view into a custom ProgressButton view containing a Button, the onClick method doesn't get called.

Custom ProgressButton view class:

class ProgressButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0,
    defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyle, defStyleRes){

    private var button: Button

    init {
        val view = LayoutInflater.from(context).inflate(R.layout.view_progress_button, this, true)
        button = view.findViewById(R.id.button)
        context.theme.obtainStyledAttributes(
            attrs,
            R.styleable.ProgressButton,
            0, 0
        ).apply {
            try {
                if (hasValue(R.styleable.ProgressButton_buttonText)) {
                    button.text = getString(R.styleable.ProgressButton_buttonText)
                }
                if (hasValue(R.styleable.ProgressButton_buttonBackground)) {
                    button.background = getDrawable(R.styleable.ProgressButton_buttonBackground)
                }
            } finally {
                recycle()
            }
        }
    }
}

ProgressButton XML Layout file: res/layout/view_progress_button.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <Button
            android:id="@+id/button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />

</FrameLayout>

Usage of ProgressButton view with android:onClick:

<com.example.ui.common.ProgressButton
        android:id="@+id/progress_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="@{() -> viewModel.onProgressButtonClick()}"
        app:buttonText="@string/test"
        app:buttonBackground="@drawable/selectable_button_blue"
/>

What's the best way to pass a click listener to a custom view using Android databinding?


Solution

  • Assuming that you have dataBinding.enabled true defined in your build.gradle. If not, check this out.

    It doesn't look like your layout is currently a databinding layout. First you'd want to convert view_progress_button.xml to a databinding layout like the following:

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
            <variable
                name="viewModel"
                type="com.your.package.YourViewModelClass" />
        </data>
    
        <FrameLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        <com.example.ui.common.ProgressButton
            android:id="@+id/progress_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> viewModel.onProgressButtonClick()}"
            app:buttonText="@string/test"
            app:buttonBackground="@drawable/selectable_button_blue"
            />
    
    </FrameLayout>
    

    There should be an option in Android Studio that lets you auto-convert your layout. Do this by hitting alt + enter (Windows) or option + enter (OSX) on the root ViewGroup in your layout. In this case you'd want to hit that combination on your FrameLayout.

    In your ProgressButton class, you'd want to do something like this:

    val binding: ViewProgressButtonBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.view_progress_button, null, false);
    

    instead of:

    val view = LayoutInflater.from(context).inflate(R.layout.view_progress_button, this, true)
    

    Using the binding object, you can do:

    binding.setViewModel(yourViewModelInstance)
    

    You can instantiate your ViewModel however, but inside your ViewModel class you should have onProgressButtonClick() defined.

    If I remember correctly, you might need to do something like this in your layout for the onClick:

    android:onClick="@{(v) -> viewModel.onProgressButtonClick()}"
    

    because the lambda expects the view as a parameter.

    Another note is that you no longer need to do findViewById since you have access to that view like so:

    binding.button