3

How does one accept methods as values, in attributes? Like in the onClick attribute for a View:

<Button android:onClick="onClickMethod"/>

How to define custom attributes that accept methods?

I know we use <declare-styleable> in resources, but how do we make it accept methods?

Alterecho
  • 665
  • 10
  • 25

2 Answers2

6

Android uses reflection to find the name of the method and invoke it. You can see an example in the source starting at line 4209 https://github.com/android/platform_frameworks_base/blob/master/core%2Fjava%2Fandroid%2Fview%2FView.java#L4209

             case R.styleable.View_onClick:
                if (context.isRestricted()) {
                    throw new IllegalStateException("The android:onClick attribute cannot "
                            + "be used within a restricted context");
                }

                final String handlerName = a.getString(attr);
                if (handlerName != null) {
                    setOnClickListener(new DeclaredOnClickListener(this, handlerName));
                }
                break;

If the method name isn't null, it creates a new DeclareOnClickListener() class and initializes it with the method name.

The DeclareOnClickListener() class is defined at line 4435 https://github.com/android/platform_frameworks_base/blob/master/core%2Fjava%2Fandroid%2Fview%2FView.java#L4435

Eli Connelly
  • 459
  • 3
  • 8
  • I see. But what is the format of android:onClick? If i try to use reference format for my attr, the ide complains that the symbol cannot be resolved: `app:myOnClick="onClickMethod"` – Alterecho Apr 13 '16 at 01:21
  • 1
    Your declaration of `myOnClick` should look like this ``. You use a string because that's what gets used to look up the method name. – Eli Connelly Apr 13 '16 at 18:14
  • 1
    It works! But how do they autocomplete the method for onClick? – Alterecho Apr 17 '16 at 10:52
1

I solved this problem with: BindingAdapter and Lambda


1️⃣ ➖ implement CustomView with lambda getter function onItemClick

class CustomView(
      context: Context,
      attrs: AttributeSet
) : View(context, attrs) {

fun onItemClick(block: () -> Unit) {
    block()  |or|  block.invoke()
}

2️⃣ ➖ add data binding and kapt in build.gradle(:app)

plugins {
   id 'kotlin-kapt'
}

android {
    ...
    buildFeatures {
        dataBinding = true
    }
    ...
}

3️⃣ ➖ implement BindingAdapter

@BindingAdapter("onItemClick")
fun CustomView.onItemClick(block: () -> Unit) {
    this.onItemClick(block)
}

4️⃣ ➖ use BindingAdapter

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

<data>

    <variable
        name="activityMain"
        type="com.veldan.MainActivity" />
</data>

<com.veldan.CustomView
        android:id="@+id/recycle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        onItemClick="@{() -> activityMain.onItemClick()}" />

</layout>

5️⃣ ➖ activity binding

class MainActivity : AppCompatActivity() {

private val TAG = this::class.simpleName

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater).also {
        it.activityMain = this
        setContentView(it.root)
    }
}

fun onItemClick() {
    Log.i(TAG, "onItemClick: ")
}
Dharman
  • 30,962
  • 25
  • 85
  • 135