首页 文章

如何使用片段中的XML onClick处理按钮单击

提问于
浏览
396

Pre-Honeycomb(Android 3),每个Activity都已注册,可通过Layout的XML中的 onClick 标记处理按钮点击:

android:onClick="myClickMethod"

在该方法中,您可以使用 view.getId() 和switch语句来执行按钮逻辑 .

随着Honeycomb的推出,我将这些活动分解为碎片,可以在许多不同的活动中重复使用 . 按钮的大多数行为都是与Activity无关的,我希望代码驻留在Fragments文件中,而不使用为每个按钮注册 OnClickListener 的旧(pre 1.6)方法 .

final Button button = (Button) findViewById(R.id.button_id);
button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        // Perform action on click
    }
});

问题是,当我的布局被夸大时,它仍然是接收按钮点击的托管活动,而不是单个碎片 . 对两者都有好的方法

  • 注册片段以接收按钮点击?

  • 将活动中的点击事件传递给他们所属的片段?

17 回答

  • 2

    如果您使用android:Onclick =“”在xml中注册,则回调将被提供给您的片段所属的上下文中受尊重的Activity(getActivity()) . 如果在Activity中找不到这样的方法,那么系统将抛出异常 .

  • 1

    最佳解决方案IMHO:

    在片段中:

    protected void addClick(int id) {
        try {
            getView().findViewById(id).setOnClickListener(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void onClick(View v) {
        if (v.getId()==R.id.myButton) {
            onMyButtonClick(v);
        }
    }
    

    然后在Fragment的onViewStateRestored中:

    addClick(R.id.myButton);
    
  • 5

    我认为问题是视图仍然是活动,而不是片段 . 片段没有自己的任何独立视图,并附加到父活动视图 . 这就是为什么事件最终在Activity中,而不是片段 . 不幸的是,但我认为你需要一些代码来完成这项工作 .

    我在转换过程中一直在做的只是添加一个调用旧事件处理程序的单击侦听器 .

    例如:

    final Button loginButton = (Button) view.findViewById(R.id.loginButton);
    loginButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(final View v) {
            onLoginClicked(v);
        }
    });
    
  • 1

    您的活动正在接收必须使用的回调:

    mViewPagerCloth.setOnClickListener((YourActivityName)getActivity());
    

    如果您希望片段接收回调,请执行以下操作:

    mViewPagerCloth.setOnClickListener(this);
    

    并在Fragment上实现 onClickListener 接口

  • 1

    您可能需要考虑将EventBus用于解耦事件 . 您可以非常轻松地监听事件 . 您还可以确保在ui线程上接收事件(而不是为每个事件订阅调用runOnUiThread ..)

    https://github.com/greenrobot/EventBus

    来自Github:

    Android优化事件总线,简化活动,碎片,线程,服务等之间的通信 . 减少代码,提高质量

  • 2

    你可以这样做:

    活动:

    Fragment someFragment;    
    
    //...onCreate etc instantiating your fragments
    
    public void myClickMethod(View v) {
        someFragment.myClickMethod(v);
    }
    

    分段:

    public void myClickMethod(View v) {
        switch(v.getId()) {
            // Just like you were doing
        }
    }
    

    回应@Ameen谁想要更少的耦合,所以片段是可重复使用的

    接口:

    public interface XmlClickable {
        void myClickMethod(View v);
    }
    

    活动:

    XmlClickable someFragment;    
    
    //...onCreate, etc. instantiating your fragments casting to your interface.
    
    public void myClickMethod(View v) {
        someFragment.myClickMethod(v);
    }
    

    分段:

    public class SomeFragment implements XmlClickable {
    
    //...onCreateView, etc.
    
    @Override
    public void myClickMethod(View v) {
        switch(v.getId()){
            // Just like you were doing
        }
    }
    
  • 0

    在我的用例中,我有50个奇怪的ImageViews,我需要挂钩到一个onClick方法 . 我的解决方案是遍历片段内的视图并在每个视图上设置相同的onclick侦听器:

    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);
            }
        }
    
  • 6

    加入Blundell的回答,
    如果你有更多的片段,有大量的onClicks:

    活动:

    Fragment someFragment1 = (Fragment)getFragmentManager().findFragmentByTag("someFragment1 "); 
    Fragment someFragment2 = (Fragment)getFragmentManager().findFragmentByTag("someFragment2 "); 
    Fragment someFragment3 = (Fragment)getFragmentManager().findFragmentByTag("someFragment3 "); 
    
    ...onCreate etc instantiating your fragments
    
    public void myClickMethod(View v){
      if (someFragment1.isVisible()) {
           someFragment1.myClickMethod(v);
      }else if(someFragment2.isVisible()){
           someFragment2.myClickMethod(v);
      }else if(someFragment3.isVisible()){
           someFragment3.myClickMethod(v); 
      }
    
    }
    

    在你的片段中:

    public void myClickMethod(View v){
         switch(v.getid()){
           // Just like you were doing
         }
      }
    
  • 1

    这一直对我有用:( Android工作室)

    @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
    
  • 161

    这是另一种方式:

    1.像这样创建一个BaseFragment:

    public abstract class BaseFragment extends Fragment implements OnClickListener
    

    2.使用

    public class FragmentA extends BaseFragment
    

    代替

    public class FragmentA extends Fragment
    

    3.在你的活动中:

    public class MainActivity extends ActionBarActivity implements OnClickListener
    

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

    希望能帮助到你 .

  • 19

    当我看到答案时,他们有点老了 . 最近谷歌推出了DataBinding,这更容易处理 onClick 或在你的xml中分配 .

    这是一个很好的例子,你可以看到如何处理这个:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="handlers" type="com.example.Handlers"/>
           <variable name="user" type="com.example.User"/>
       </data>
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"
               android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.lastName}"
               android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
       </LinearLayout>
    </layout>
    

    还有关于DataBinding的非常好的教程,你可以找到它Here .

  • 0

    我最近解决了这个问题,无需向上下文Activity添加方法或必须实现OnClickListener . 我不确定它是否是一个“有效”的解决方案,但它确实有效 .

    基于:https://developer.android.com/tools/data-binding/guide.html#binding_events

    它可以通过数据绑定来完成:只需将您的片段实例添加为变量,然后您可以使用onClick链接任何方法 .

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        tools:context="com.example.testapp.fragments.CustomFragment">
    
        <data>
            <variable name="fragment" type="com.example.testapp.fragments.CustomFragment"/>
        </data>
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_place_black_24dp"
                android:onClick="@{fragment.buttonClicked}"/>
        </LinearLayout>
    </layout>
    

    片段链接代码将是......

    public class CustomFragment extends Fragment {
    
        ...
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            View view = inflater.inflate(R.layout.fragment_person_profile, container, false);
            FragmentCustomBinding binding = DataBindingUtil.bind(view);
            binding.setFragment(this);
            return view;
        }
    
        ...
    
    }
    
  • 0

    在使用片段时,我宁愿在代码中使用单击处理而不是在XML中使用 onClick 属性 .

    将活动迁移到片段时,这变得更加容易 . 您可以直接从每个 case 块调用单击处理程序(以前在XML中设置为 android:onClick ) .

    findViewById(R.id.button_login).setOnClickListener(clickListener);
    ...
    
    OnClickListener clickListener = new OnClickListener() {
        @Override
        public void onClick(final View v) {
            switch(v.getId()) {
               case R.id.button_login:
                  // Which is supposed to be called automatically in your
                  // activity, which has now changed to a fragment.
                  onLoginClick(v);
                  break;
    
               case R.id.button_logout:
                  ...
            }
        }
    }
    

    在处理片段中的点击时,这对我来说比 android:onClick 更简单 .

  • 6

    我更喜欢使用以下解决方案来处理onClick事件 . 这适用于Activity和Fragments .

    public class StartFragment extends Fragment implements OnClickListener{
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
    
            View v = inflater.inflate(R.layout.fragment_start, container, false);
    
            Button b = (Button) v.findViewById(R.id.StartButton);
            b.setOnClickListener(this);
            return v;
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.StartButton:
    
                ...
    
                break;
            }
        }
    }
    
  • 2

    您可以将回调定义为XML布局的属性 . 文章Custom XML Attributes For Your Custom Android Widgets将向您展示如何为自定义小部件执行此操作 . 归功于Kevin Dion :)

    我正在调查是否可以向基础Fragment类添加可样式属性 .

    基本思想是在处理onClick回调时具有View实现的相同功能 .

  • 27

    ButterKnife可能是解决杂乱问题的最佳解决方案 . 它使用注释处理器生成所谓的"old method"样板代码 .

    但是仍然可以使用onClick方法,使用自定义充气器 .

    如何使用

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

    实施

    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);
                }
            }
        }
    }
    
  • 571

    我'd like to add to Adjorn Linkz' s answer .

    如果您需要多个处理程序,则可以使用lambda引用

    void onViewCreated(View view, Bundle savedInstanceState)
    {
        view.setOnClickListener(this::handler);
    }
    void handler(View v)
    {
        ...
    }
    

    这里的诀窍是 handler 方法的签名匹配 View.OnClickListener.onClick 签名 . 这样,您将不需要 View.OnClickListener 接口 .

    此外,您不需要任何switch语句 .

    遗憾的是,此方法仅限于需要单个方法或lambda的接口 .

相关问题