Как обрабатывать нажатия кнопок, используя XML onClick внутри фрагментов - PullRequest
419 голосов
/ 23 мая 2011

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

Проблема в том, что, когда мои макеты завышены, все же хост-активность получает нажатия кнопок, а не отдельные фрагменты. Есть ли хороший подход к

  • Зарегистрировать фрагмент для получения нажатия кнопки?
  • Передать события клика из Действия в фрагмент, к которому они принадлежат?

Ответы [ 17 ]

583 голосов
/ 29 января 2013

Я предпочитаю использовать следующее решение для обработки событий onClick.Это работает и для Деятельности и Фрагментов.

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;
        }
    }
}
168 голосов
/ 08 июня 2011

Вы могли бы просто сделать это:

Активность:

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
    }
}    
28 голосов
/ 01 ноября 2011

Проблема, я думаю, заключается в том, что представление - это все еще деятельность, а не фрагмент. Фрагменты не имеют собственного независимого представления и привязаны к представлению родительских действий. Вот почему событие заканчивается в деятельности, а не фрагмент. Это прискорбно, но я думаю, что вам понадобится некоторый код для этой работы.

То, что я делал во время преобразований, это просто добавление прослушивателя кликов, который вызывает старый обработчик событий.

например:

final Button loginButton = (Button) view.findViewById(R.id.loginButton);
loginButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(final View v) {
        onLoginClicked(v);
    }
});
20 голосов
/ 11 сентября 2015

Недавно я решил эту проблему, не добавляя метод в контекстную операцию или не внедряя 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;
    }

    ...

}
6 голосов
/ 19 апреля 2014

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);
            }
        }
    }
}
6 голосов
/ 27 сентября 2012

Я бы предпочел использовать обработку кликов в коде, а не использовать атрибут 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.

5 голосов
/ 23 октября 2013

Это еще один способ:

1.Создайте базовый фрагмент следующим образом:

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

Надеюсь, это поможет.

3 голосов
/ 26 января 2016

В моем случае у меня есть 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);
        }
    }
2 голосов
/ 03 февраля 2016

Как я вижу ответы, они как-то старые.Недавно 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>

Существует также очень хороший учебник по привязке данных, вы можете найти его Здесь .

2 голосов
/ 26 ноября 2011

Вы можете определить обратный вызов как атрибут вашего макета XML.В статье Пользовательские атрибуты XML для пользовательских настраиваемых виджетов Android будет показано, как это сделать для настраиваемого виджета.Кредит идет Кевину Диону :)

Я исследую, могу ли я добавить настраиваемые атрибуты в базовый класс Fragment.

Основная идея заключается в том, чтобы иметь ту же функциональность, которую реализует View при работе собратный вызов onClick.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...