如何使用片段中的XML onClick处理button点击

Pre-Honeycomb(Android 3)中,每个Activity都被注册为通过Layout的XML中的onClick标签处理button点击:

 android:onClick="myClickMethod" 

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

随着蜂窝的引入,我把这些活动分解成碎片,可以在许多不同的活动中重复使用。 大多数button的行为是独立于Activity,我希望代码驻留在Fragments文件中, 而不必使用为每个button注册OnClickListener的旧(前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 } }); 

问题是,当我的布局充气,它仍然是托pipe活动,接受button点击,而不是个别碎片。 有没有一个好方法吗?

  • 注册片段以接收button点击?
  • 将活动中的点击事件传递给它们所属的片段?

你可以这样做:

活动:

 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谁想要更less的耦合,所以片段是可重用的

接口:

 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 } } 

我更喜欢使用以下解决scheme来处理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; } } } 

我认为这个问题是观点仍然是活动,而不是片段。 这些片段没有任何独立的视图,并且附属于父活动视图。 这就是为什么事件结束在活动,而不是片段。 它不幸的,但我认为你将需要一些代码来使这项工作。

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

例如:

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

我最近解决了这个问题,而不必添加一个方法的上下文活动或必须实现OnClickListener。 我不确定它是否是“有效”的解决scheme,但它的工作原理。

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

可以使用数据绑定完成:只需将您的片段实例添加为variables,然后您可以将任何方法与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; } ... } 

黄油刀可能是杂波问题的最佳解决scheme。 它使用注释处理器来生成所谓的“旧方法”样板代码。

但是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); } } } } 

这是另一种方式:

1.创build一个这样的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); } 

希望它有帮助。

我宁愿去处理代码中的点击处理,而不是在处理片段时使用XML中的onClick属性。

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

 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更简单。

您可以将callback定义为XML布局的属性。 文章定制您的自定义小部件的XML属性将告诉您如何为自定义小部件。 信贷去凯文·迪翁:)

我正在调查是否可以将样式属性添加到基本片段类。

基本思想是在处理onClickcallback时,View具有相同的function。

在我的使用案例中,我有50多个ImageViews需要钩入一个onClick方法。 我的解决scheme是循环浏览片段内的视图,并设置相同的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); } } 

除了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 } } 

如果您使用android:Onclick =“”在xml中注册,callback将被赋予您的片段所属的上下文(getActivity())所属的受尊重的Activity。 如果在Activity中找不到这样的方法,那么系统会抛出一个exception。

您可能要考虑使用EventBus解耦事件。您可以非常容易地收听事件。 你也可以确保在ui线程上接收事件(而不是为每个事件订阅调用runOnUiThread ..)

https://github.com/greenrobot/EventBus

来自Github:

Android优化的事件总线,简化活动,碎片,线程,服务等之间的通信。代码less,质量好

正如我看到的答案,他们有点老。 最近Google引入了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的非常好的教程,你可以在这里find它。

我想补充Adjorn Linkz的答案 。

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

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

这里的技巧是handler方法的签名与View.OnClickListener.onClick签名相匹配。 这样,您将不需要View.OnClickListener接口。

另外,你不需要任何switch语句。

可悲的是,这种方法仅限于需要单一方法或lambda的接口。

这一直在为我工作:( 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 

最好的解决scheme

片段:

 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); 

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

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

如果你想要你的片段接收callback,那么这样做:

 mViewPagerCloth.setOnClickListener(this); 

并在Fragment上实现onClickListener接口