How to handle button clicks using the XML onClick within Fragments

前端 未结 18 1512
旧时难觅i
旧时难觅i 2020-11-22 01:41

Pre-Honeycomb (Android 3), each Activity was registered to handle button clicks via the onClick tag in a Layout\'s XML:

android:onClick=\"m         


        
18条回答
  •  灰色年华
    2020-11-22 02:20

    Though I've spotted some nice answers relying on data binding, I didn't see any going to the full extent with that approach -- in the sense of enabling fragment resolution while allowing for fragment-free layout definitions in XML's.

    So assuming data binding is enabled, here's a generic solution I can propose; A bit long but it definitely works (with some caveats):

    Step 1: Custom OnClick Implementation

    This will run a fragment-aware search through contexts associated with the tapped-on view (e.g. button):

    
    // CustomOnClick.kt
    
    @file:JvmName("CustomOnClick")
    
    package com.example
    
    import android.app.Activity
    import android.content.Context
    import android.content.ContextWrapper
    import android.view.View
    import androidx.fragment.app.Fragment
    import androidx.fragment.app.FragmentActivity
    import java.lang.reflect.Method
    
    fun onClick(view: View, methodName: String) {
        resolveOnClickInvocation(view, methodName)?.invoke(view)
    }
    
    private data class OnClickInvocation(val obj: Any, val method: Method) {
        fun invoke(view: View) {
            method.invoke(obj, view)
        }
    }
    
    private fun resolveOnClickInvocation(view: View, methodName: String): OnClickInvocation? =
        searchContexts(view) { context ->
            var invocation: OnClickInvocation? = null
            if (context is Activity) {
                val activity = context as? FragmentActivity
                        ?: throw IllegalStateException("A non-FragmentActivity is not supported (looking up an onClick handler of $view)")
    
                invocation = getTopFragment(activity)?.let { fragment ->
                    resolveInvocation(fragment, methodName)
                }?: resolveInvocation(context, methodName)
            }
            invocation
        }
    
    private fun getTopFragment(activity: FragmentActivity): Fragment? {
        val fragments = activity.supportFragmentManager.fragments
        return if (fragments.isEmpty()) null else fragments.last()
    }
    
    private fun resolveInvocation(target: Any, methodName: String): OnClickInvocation? =
        try {
            val method = target.javaClass.getMethod(methodName, View::class.java)
            OnClickInvocation(target, method)
        } catch (e: NoSuchMethodException) {
            null
        }
    
    private fun  searchContexts(view: View, matcher: (context: Context) -> T?): T? {
        var context = view.context
        while (context != null && context is ContextWrapper) {
            val result = matcher(context)
            if (result == null) {
                context = context.baseContext
            } else {
                return result
            }
        }
        return null
    }
    
    

    Note: loosely based on the original Android implementation (see https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#3025)

    Step 2: Declarative application in layout files

    Then, in data-binding aware XML's:

    
      
         
      
    
      

    Caveats

    • Assumes a 'modern' FragmentActivity based implementation
    • Can only lookup method of "top-most" (i.e. last) fragment in stack (though that can be fixed, if need be)

提交回复
热议问题