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
}
});
Проблема заключается в том, что когда мой макет раздувается, он все еще выполняет Хостинг, который получает нажатие кнопки, а не отдельные фрагменты. Есть ли хороший подход к
Вы могли бы просто сделать это:
Активность:
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
}
}
Я предпочитаю использовать следующее решение для обработки событий 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;
}
}
}
android:onClick
для кнопок, размещенных в строках списка, а затем просто делаю getPositionForView(v)
чтобы получить позицию. Я не знаю, как бы я использовал это, чтобы достичь этого.
Проблема, я думаю, в том, что представление по-прежнему является активностью, а не фрагментом. Фрагменты не имеют самостоятельного представления и привязаны к представлению родительских действий. Вот почему событие завершается в Activity, а не в фрагменте. Несчастливо, но я думаю, вам понадобится код для выполнения этой работы.
То, что я делал во время конверсий, просто добавляет прослушиватель кликов, который вызывает старый обработчик событий.
например:
final Button loginButton = (Button) view.findViewById(R.id.loginButton);
loginButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
onLoginClicked(v);
}
});
Недавно я решил эту проблему, не добавляя метод в контекстное действие или не используя 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;
}
...
}
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);
}
}
}
}
Это другой способ:
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);
}
Надеюсь, что это поможет.
Я бы предпочел использовать обработку кода в коде, чем использовать атрибут 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
.
В моем случае использования у меня есть 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);
}
}
Вы можете определить обратный вызов как атрибут вашего XML-макета. В статье Пользовательские атрибуты XML для пользовательских виджетах Android будет показано, как это сделать для пользовательского виджета. Кредит идет к Кевину Диону:)
Я изучаю, могу ли я добавлять атрибуты стиля к базовому классу фрагментов.
Основная идея состоит в том, чтобы иметь ту же функциональность, что View реализует при работе с обратным вызовом onClick.
Я хочу добавить к Adjorn Linkz ответ.
Если вам нужны несколько обработчиков, вы можете просто использовать ссылки лямбда
void onViewCreated(View view, Bundle savedInstanceState)
{
view.setOnClickListener(this::handler);
}
void handler(View v)
{
...
}
Фокус в том, что подпись метода handler
соответствует подписи View.OnClickListener.onClick
. Таким образом вам не понадобится интерфейс View.OnClickListener
.
Кроме того, вам не нужны никакие инструкции switch.
К сожалению, этот метод ограничивается только интерфейсами, для которых требуется один метод, или лямбда.
Как я вижу ответы, они как-то стары. Недавно 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, который вы можете найти здесь.
Возможно, вы захотите использовать EventBus для развязанных событий. Вы можете слушать события очень легко. Вы также можете убедиться, что событие получено в потоке ui (вместо вызова runOnUiThread.. для себя для каждой подписки на события)
https://github.com/greenrobot/EventBus
из Github:
Оптимизированная автобусная шина Android, которая упрощает обмен данными между Деятельность, фрагменты, потоки, услуги и т.д. Меньше кода, лучше качество
Если вы зарегистрировались в xml с помощью android: Onclick = "", обратный вызов будет передан уважаемому действию, в контексте которого принадлежит ваш фрагмент (getActivity()). Если такой метод не найден в Activity, тогда система выдаст исключение.
Добавление к ответу Бланделла,
Если у вас больше фрагментов, с большим количеством 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
}
}
Ваша активность получает обратный вызов, который должен был использоваться:
mViewPagerCloth.setOnClickListener((YourActivityName)getActivity());
Если вы хотите, чтобы ваш фрагмент получал обратный вызов, сделайте следующее:
mViewPagerCloth.setOnClickListener(this);
и реализовать интерфейс onClickListener
на фрагменте
Лучшее решение 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);
Это работает для меня: (студия 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
setOnClickListener
иfindViewById
для каждой кнопки, поэтомуonClick
был добавлен, чтобы сделать вещи проще.