Как обрабатывать нажатия кнопок с помощью XML onClick внутри фрагментов
Pre-Honeycomb (Android 3), каждая активность была зарегистрирована для обработки щелчков кнопок с помощью тега onClick
в XML-макете:
android:onClick="myClickMethod"
В рамках этого метода вы можете использовать view.getId()
и оператор switch для логики кнопок.
С введением Honeycomb я разбиваю эти действия на фрагменты, которые можно повторно использовать во многих разных видах деятельности. Большая часть поведения кнопок зависит от активности, и я хотел бы, чтобы код находился внутри файла фрагментов без использования старого (до 1.6) метода регистрации OnClickListener
для каждой кнопки.
final Button button = (Button) findViewById(R.id.button_id);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
}
});
Проблема заключается в том, что когда мой макет раздувается, он все еще выполняет Хостинг, который получает нажатие кнопки, а не отдельные фрагменты. Есть ли хороший подход к
- Зарегистрировать фрагмент для получения щелчков кнопки?
- Передайте события кликов из Activity в том фрагменте, к которому они принадлежат?
Ответы
Ответ 1
Вы могли бы просто сделать это:
Активность:
Fragment someFragment;
//...onCreate etc instantiating your fragments
public void myClickMethod(View v) {
someFragment.myClickMethod(v);
}
Fragment:
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);
}
Fragment:
public class SomeFragment implements XmlClickable {
//...onCreateView, etc.
@Override
public void myClickMethod(View v) {
switch(v.getId()){
// Just like you were doing
}
}
Ответ 2
Я предпочитаю использовать следующее решение для обработки событий 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;
}
}
}
Ответ 3
Проблема, я думаю, в том, что представление по-прежнему является активностью, а не фрагментом. Фрагменты не имеют самостоятельного представления и привязаны к представлению родительских действий. Вот почему событие завершается в Activity, а не в фрагменте. Несчастливо, но я думаю, вам понадобится код для выполнения этой работы.
То, что я делал во время конверсий, просто добавляет прослушиватель кликов, который вызывает старый обработчик событий.
например:
final Button loginButton = (Button) view.findViewById(R.id.loginButton);
loginButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
onLoginClicked(v);
}
});
Ответ 4
Недавно я решил эту проблему, не добавляя метод в контекстную активность или не реализуя 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 android:name="fragment" android: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;
}
...
}
Ответ 5
Я бы предпочел использовать обработку кода в коде, чем использовать атрибут onClick
в XML при работе с фрагментами.
Это становится еще проще при переносе ваших действий на фрагменты. Вы можете просто вызвать обработчик кликов (ранее установленный в android:onClick
в XML) непосредственно из каждого блока case
.
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
ButterKnife, вероятно, является лучшим решением проблемы помех. Он использует обработчики аннотаций для генерации шаблона так называемого "старого метода".
Но метод 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);
}
}
}
}
Ответ 7
Это другой способ:
1. Создайте базовый фрагмент следующим образом:
public abstract class BaseFragment extends Fragment implements OnClickListener
2.Use
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);
}
Надеюсь, что это поможет.
Ответ 8
В моем случае использования у меня есть 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);
}
}
Ответ 9
Вы можете определить обратный вызов как атрибут вашего макета XML. В статье " Пользовательские атрибуты XML для пользовательских настраиваемых виджетов Android" будет показано, как это сделать для настраиваемого виджета. Кредит идет к Кевину Диону :)
Я изучаю, могу ли я добавить настраиваемые атрибуты в базовый класс Fragment.
Основная идея заключается в том, чтобы иметь ту же функциональность, которую реализует View при работе с обратным вызовом onClick.
Ответ 10
Как я вижу ответы, они как-то стары. Недавно 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, который вы можете найти здесь.
Ответ 11
Добавление к ответу Бланделла,
Если у вас больше фрагментов, с большим количеством 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
}
}
Ответ 12
Если вы зарегистрировались в xml с помощью android: Onclick = "", обратный вызов будет передан уважаемому действию, в контексте которого принадлежит ваш фрагмент (getActivity()). Если такой метод не найден в Activity, тогда система выдаст исключение.
Ответ 13
Возможно, вы захотите использовать EventBus для развязанных событий.
Вы можете слушать события очень легко. Вы также можете убедиться, что событие получено в потоке ui (вместо вызова runOnUiThread.. для себя для каждой подписки на события)
https://github.com/greenrobot/EventBus
из Github:
Оптимизированная автобусная шина Android, которая упрощает обмен данными между Деятельность, фрагменты, потоки, услуги и т.д. Меньше кода, лучше качество
Ответ 14
Я хочу добавить к Adjorn Linkz ответ.
Если вам нужны несколько обработчиков, вы можете просто использовать ссылки лямбда
void onViewCreated(View view, Bundle savedInstanceState)
{
view.setOnClickListener(this::handler);
}
void handler(View v)
{
...
}
Фокус в том, что подпись метода handler
соответствует подписи View.OnClickListener.onClick
. Таким образом вам не понадобится интерфейс View.OnClickListener
.
Кроме того, вам не нужны никакие инструкции switch.
К сожалению, этот метод ограничивается только интерфейсами, для которых требуется один метод, или лямбда.
Ответ 15
Это работает для меня: (студия 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
Ответ 16
Лучшее решение 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);
Ответ 17
Ваша активность получает обратный вызов, который должен был использоваться:
mViewPagerCloth.setOnClickListener((YourActivityName)getActivity());
Если вы хотите, чтобы ваш фрагмент получал обратный вызов, сделайте следующее:
mViewPagerCloth.setOnClickListener(this);
и реализовать интерфейс onClickListener
на фрагменте