How to handle button clicks using the XML onClick within Fragments

前端 未结 18 1508
旧时难觅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:13

    ButterKnife is probably the best solution for the clutter problem. It uses annotation processors to generate the so called "old method" boilerplate code.

    But the onClick method can still be used, with a custom inflator.

    How to use

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup cnt, Bundle state) {
        inflater = FragmentInflatorFactory.inflatorFor(inflater, this);
        return inflater.inflate(R.layout.fragment_main, cnt, false);
    }
    

    Implementation

    public class FragmentInflatorFactory implements LayoutInflater.Factory {
    
        private static final int[] sWantedAttrs = { android.R.attr.onClick };
    
        private static final Method sOnCreateViewMethod;
        static {
            // We could duplicate its functionallity.. or just ignore its a protected method.
            try {
                Method method = LayoutInflater.class.getDeclaredMethod(
                        "onCreateView", String.class, AttributeSet.class);
                method.setAccessible(true);
                sOnCreateViewMethod = method;
            } catch (NoSuchMethodException e) {
                // Public API: Should not happen.
                throw new RuntimeException(e);
            }
        }
    
        private final LayoutInflater mInflator;
        private final Object mFragment;
    
        public FragmentInflatorFactory(LayoutInflater delegate, Object fragment) {
            if (delegate == null || fragment == null) {
                throw new NullPointerException();
            }
            mInflator = delegate;
            mFragment = fragment;
        }
    
        public static LayoutInflater inflatorFor(LayoutInflater original, Object fragment) {
            LayoutInflater inflator = original.cloneInContext(original.getContext());
            FragmentInflatorFactory factory = new FragmentInflatorFactory(inflator, fragment);
            inflator.setFactory(factory);
            return inflator;
        }
    
        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            if ("fragment".equals(name)) {
                // Let the Activity ("private factory") handle it
                return null;
            }
    
            View view = null;
    
            if (name.indexOf('.') == -1) {
                try {
                    view = (View) sOnCreateViewMethod.invoke(mInflator, name, attrs);
                } catch (IllegalAccessException e) {
                    throw new AssertionError(e);
                } catch (InvocationTargetException e) {
                    if (e.getCause() instanceof ClassNotFoundException) {
                        return null;
                    }
                    throw new RuntimeException(e);
                }
            } else {
                try {
                    view = mInflator.createView(name, null, attrs);
                } catch (ClassNotFoundException e) {
                    return null;
                }
            }
    
            TypedArray a = context.obtainStyledAttributes(attrs, sWantedAttrs);
            String methodName = a.getString(0);
            a.recycle();
    
            if (methodName != null) {
                view.setOnClickListener(new FragmentClickListener(mFragment, methodName));
            }
            return view;
        }
    
        private static class FragmentClickListener implements OnClickListener {
    
            private final Object mFragment;
            private final String mMethodName;
            private Method mMethod;
    
            public FragmentClickListener(Object fragment, String methodName) {
                mFragment = fragment;
                mMethodName = methodName;
            }
    
            @Override
            public void onClick(View v) {
                if (mMethod == null) {
                    Class<?> clazz = mFragment.getClass();
                    try {
                        mMethod = clazz.getMethod(mMethodName, View.class);
                    } catch (NoSuchMethodException e) {
                        throw new IllegalStateException(
                                "Cannot find public method " + mMethodName + "(View) on "
                                        + clazz + " for onClick");
                    }
                }
    
                try {
                    mMethod.invoke(mFragment, v);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new AssertionError(e);
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 02:13

    This has been working for me:(Android studio)

     @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    
            View rootView = inflater.inflate(R.layout.update_credential, container, false);
            Button bt_login = (Button) rootView.findViewById(R.id.btnSend);
    
            bt_login.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
    
                    System.out.println("Hi its me");
    
    
                }// end onClick
            });
    
            return rootView;
    
        }// end onCreateView
    
    0 讨论(0)
  • 2020-11-22 02:20

    This is another way:

    1.Create a BaseFragment like this:

    public abstract class BaseFragment extends Fragment implements OnClickListener
    

    2.Use

    public class FragmentA extends BaseFragment 
    

    instead of

    public class FragmentA extends Fragment
    

    3.In your activity:

    public class MainActivity extends ActionBarActivity implements OnClickListener
    

    and

    BaseFragment fragment = new FragmentA;
    
    public void onClick(View v){
        fragment.onClick(v);
    }
    

    Hope it helps.

    0 讨论(0)
  • 2020-11-22 02:20

    In my use case, I have 50 odd ImageViews I needed to hook into a single onClick method. My solution is to loop over the views inside the fragment and set the same onclick listener on each:

        final View.OnClickListener imageOnClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                chosenImage = ((ImageButton)v).getDrawable();
            }
        };
    
        ViewGroup root = (ViewGroup) getView().findViewById(R.id.imagesParentView);
        int childViewCount = root.getChildCount();
        for (int i=0; i < childViewCount; i++){
            View image = root.getChildAt(i);
            if (image instanceof ImageButton) {
                ((ImageButton)image).setOnClickListener(imageOnClickListener);
            }
        }
    
    0 讨论(0)
  • 2020-11-22 02:20

    You can define a callback as an attribute of your XML layout. The article Custom XML Attributes For Your Custom Android Widgets will show you how to do it for a custom widget. Credit goes to Kevin Dion :)

    I'm investigating whether I can add styleable attributes to the base Fragment class.

    The basic idea is to have the same functionality that View implements when dealing with the onClick callback.

    0 讨论(0)
  • 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 <T: Any> 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:

    <layout>
      <data>
         <import type="com.example.CustomOnClick"/>
      </data>
    
      <Button
        android:onClick='@{(v) -> CustomOnClick.onClick(v, "myClickMethod")}'
      </Button>
    </layout>
    

    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)
    0 讨论(0)
提交回复
热议问题