Фрагментная активность перехватывает ключ onKeyDown и используется во фрагменте

У меня есть активность фрагмента с пейджером:

List<Fragment> fragments = new Vector<Fragment>();
    fragments.add(Fragment.instantiate(this, PastEventListFragment.class.getName(),bundle));
    fragments.add(Fragment.instantiate(this, EventListFragment.class.getName(),bundle));

    this.mPagerAdapter  = new EventPagerAdapter(super.getSupportFragmentManager(), fragments);
    //
    ViewPager pager = (ViewPager)super.findViewById(R.id.viewpager1);

    pager.setAdapter(this.mPagerAdapter);
    pager.setCurrentItem(1);

Я ловлю событие onKeyDown:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_MENU) {

    }
    return super.onKeyDown(keyCode, event);
}

Вопрос: как использовать событие во всех фрагментах, которые я создал в этом действии. Спасибо


person FlorinD    schedule 31.08.2012    source источник


Ответы (6)


Что вы можете сделать, так это определить собственный метод в ваших классах фрагментов. Например:

public void myOnKeyDown(int key_code){
   //do whatever you want here
}

и вызывайте этот метод всякий раз, когда в вашем классе Activity возникает событие нажатия клавиши. Например:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_MENU) {
        ((PastEventListFragment)fragments.get(0)).myOnKeyDown(keyCode);
        ((EventListFragment)fragments.get(1)).myOnKeyDown(keyCode);

        //and so on...
    }
    return super.onKeyDown(keyCode, event);
}
person waqaslam    schedule 31.08.2012
comment
@JonWillis Не стесняйтесь предлагать лучшее решение - person Oleksiy; 25.11.2014
comment
На самом деле вы можете использовать интерфейс, LocalBroadcast или просто использовать EventBus и отправлять KeyEvent с его помощью любому, кто этого хочет. - person Stan; 18.12.2015
comment
что означает fragments и где вы это объявили? - person Choletski; 23.12.2016
comment
@JonWillis, почему бы им не сцепиться? Любая активность может работать только с теми фрагментами, на которые она рассчитана. И только родительская активность знает, какие фрагменты отображаются и должны получать событие. Более того, я бы переместил это в BaseFragment и BaseActivity, чтобы поведение по умолчанию для всего проекта. Также следует сделать myOnKeyDown ту же самую подпись, особенно возвращать логическое значение, если событие было обработано или нет - person Luten; 17.04.2019
comment
@JonWillis, Activity также должна знать, было ли обработано событие или нет. EventBus и инструменты хороши, когда можно выстрелить и забыть и не важно, кто получит событие, здесь это не так. - person Luten; 17.04.2019

Если кому-то интересно, как это сделать с помощью Boradcast:

В вашем фрагменте в onViewCreated

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);


// Register to receive messages.
// We are registering an observer (mMessageReceiver) to receive Intents
// with actions named "custom-event-name".
 LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
 new IntentFilter("activity-says-hi"));

...}

 // Our handler for received Intents. This will be called whenever an Intent
 // with an action named "custom-event-name" is broadcasted.
 private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
 @Override
 public void onReceive(Context context, Intent intent) {
 // Get extra data included in the Intent

 doSomethingCauseVolumeKeyPressed();

 }
};

your keyevent - код для активации

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    int action = event.getAction();
    int keyCode = event.getKeyCode();
    switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_UP:
            if (action == KeyEvent.ACTION_DOWN) {
                sendBroadcast();
            }
            return true;
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            if (action == KeyEvent.ACTION_DOWN) {
                sendBroadcast();
            }
            return true;
        default:
            return super.dispatchKeyEvent(event);
    }
}

ваш широковещательный отправитель:

private void  sendVolumeBroadcast(){
    Intent intent = new Intent("activity-says-hi");
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
person PKAP    schedule 25.02.2016
comment
Мне нравится это решение. Меньшая связь, чем та, которая отмечена как правильная. Спасибо! - person Jvalant Dave; 03.02.2017

Как упоминалось другими, принятый ответ приводит к тесной связи между действием и его фрагментами.

Вместо этого я бы предложил использовать какую-то реализацию на основе событий. Это гораздо более удобно для повторного использования и приводит к лучшей архитектуре программного обеспечения. В предыдущих проектах я использовал одно из следующих решений (Kotlin):

Трансляции

Использование Android LocalBroadcastManager: документация

Создайте BroadcastReceiver:

class SomeBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        val keyCode = intent?.getIntExtra("KEY_CODE", 0)
        // Do something with the event
    }

}

В вашей деятельности:

class SomeActivity : AppCompatActivity() {

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        val intent = Intent("SOME_TAG").apply { putExtra("KEY_CODE", keyCode) }
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
        return super.onKeyDown(keyCode, event)
    }

}

Затем в любом из фрагментов (или сервисов и т.д.):

class SomeFragment : Fragment() {

    val receiver = SomeBroadcastReceiver()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        val filter = IntentFilter().apply { addAction("SOME_TAG") }
        LocalBroadcastManager.getInstance(context!!).registerReceiver(receiver, filter)

        return super.onCreateView(inflater, container, savedInstanceState)
    }

}

EventBus

Использование EventBus

Создайте класс события:

data class Event(val keyCode: Int)

В вашей деятельности:

class SomeActivity : AppCompatActivity() {

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        EventBus.getDefault().post(Event(keyCode))
        return super.onKeyDown(keyCode, event)
    }

}

Затем в вашем фрагменте:

class SomeFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        // Register for events
        EventBus.getDefault().register(this)

        return super.onCreateView(inflater, container, savedInstanceState)
    }

    @Subscribe
    public fun onKeyEvent(event : Event) {
        // Called by eventBus when an event occurs
    }

    override fun onDestroyView() {
        super.onDestroyView()
        EventBus.getDefault().unregister(this)
    }

}
person kjellhaaland    schedule 06.12.2018
comment
Трансляции должны умереть! хаха. Кстати, с твоей стороны было приятно включить в ответ и трансляции, и шину событий. - person Machado; 19.02.2020

После ответа @hsu.tw, чтобы избежать тесной связи, я нашел этот суть.

Избегание жесткой связи имеет свою цену: вам нужно фокусируемое представление (к счастью, это был мой случай, поскольку у меня уже было представление на переднем плане, которое слушает другие события касания, поэтому я просто добавил к нему View.OnKeyListener).

Шаги, необходимые для присоединения View.OnKeyListener к представлению во фрагменте независимо от действия (проверьте суть):

 view.setFocusableInTouchMode(true);
 view.requestFocus();
 view.setOnKeyListener(pressKeyListener);

Я реализовал это в обратном вызове onViewCreated моего фрагмента.

person dnhyde    schedule 05.08.2019
comment
onViewCreated? Вроде больше смысла. - person hsu.tw; 08.07.2020

Я создал подклассы классов Activity и Fragment для передачи KeyEvents. Для меня это выглядит понятнее, чем отправка локальных трансляций. Но это решение может быть не таким гибким. Выберите предпочтительный способ самостоятельно.

Вот активность:

public abstract class KeyEventPassingActivity extends Activity {

    public interface KeyEventListener extends View.OnKeyListener {
        boolean isVisible();
        View getView();
    }

    private final List<KeyEventListener> keyEventHandlerList = new ArrayList<>();

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        for (KeyEventListener handler : keyEventHandlerList) {
            if (handleKeyEvent(handler, event)) {
                return true;
            }
        }
        return super.dispatchKeyEvent(event);
    }

    void addKeyEventHandler(@NonNull KeyEventListener handler) {
        keyEventHandlerList.add(handler);
    }

    void removeKeyEventHandler(@NonNull KeyEventListener handler) {
        keyEventHandlerList.remove(handler);
    }

    /**
     * @return <tt>true</tt> if the event was handled, <tt>false</tt> otherwise
     */
    private boolean handleKeyEvent(@Nullable KeyEventListener listener, KeyEvent event) {
        return listener != null
                && listener.isVisible()
                && listener.onKey(listener.getView(), event.getKeyCode(), event);
    }
}

И фрагмент:

public abstract class KeyEventHandlingFragment extends Fragment
        implements KeyEventPassingActivity.KeyEventListener {

    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (activity instanceof KeyEventPassingActivity) {
            ((KeyEventPassingActivity) activity).addKeyEventHandler(this);
        }
    }

    @Override
    public void onDetach() {
        Activity activity = getActivity();
        if (activity instanceof KeyEventPassingActivity) {
            ((KeyEventPassingActivity) activity).removeKeyEventHandler(this);
        }
        super.onDetach();
    }
}

Суть: https://gist.github.com/0neel/7d1ed5d26f2148b4168b6616337159ed

person 0neel    schedule 04.05.2017

У меня такая же проблема при разработке приложения для Android TV.

И я решаю эту проблему так:

В методе onCreateView я вызываю «requestFocus» некоторым представлением. (Я отмечаю его как ViewA.) Затем я устанавливаю KeyEventListener в ViewA.

В вашем случае вы должны сделать это (set-KeyEventListener) в адаптере и PagerChangeListener.

person hsu.tw    schedule 14.06.2019