Fragment activity catch onKeyDown and use in fragment
Asked Answered
C

6

35

I have Fragment activity with pager:

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

I catch onKeyDown event :

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

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

The Question is: How to use event in all fragments i have instantiated in this activity . Thanks

Christachristabel answered 31/8, 2012 at 7:41 Comment(0)
F
42

What you can do is to define a custom method in your fragment class(s). For example:

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

and call this method whenever a key-down event is raised in your Activity class. For example:

@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);
}
Frisch answered 31/8, 2012 at 8:17 Comment(5)
@JonWillis Feel free to provide a better solutionEverick
Actually you can use interface, LocalBroadcast or just use EventBus and send KeyEvent using it to any who wants it.Boutique
what does fragments mean, and where you declared it?Castleman
@JonWillis, Why shouldn't they by tight couled? Any activity can work only with those fragments that it is designed to. And only parent activity knows which fragments are shown and should receive the event Moreover, I'd move this into BaseFragment and BaseActivity to be default behavior for the whole project. Also should make myOnKeyDown the same signature especially return boolean for if event was handled or notCordle
@JonWillis, Activity also should know if event was handled or not. EventBus and instruments alike are good when you can fire and forget and don't care who will get the event, here it's not the caseCordle
M
11

If someone is interessed how to do it with Boradcast:

In your fragment in 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 - code to put in activity

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

your broadcast sender:

private void  sendVolumeBroadcast(){
    Intent intent = new Intent("activity-says-hi");
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
Manna answered 25/2, 2016 at 14:43 Comment(1)
I like this solution. Less coupling than the one marked correct. Thanks!Earmark
A
9

As mentioned by others, the accepted answer results in tight coupling between the activity and its fragments.

I would suggest using some sort of event-based implementation instead. This is much more reusable and results in a better software architecture. In previous projects I have used one of the following solutions (Kotlin):

Broadcasts

Using Android´s LocalBroadcastManager: Documentation

Create a BroadcastReceiver:

class SomeBroadcastReceiver : BroadcastReceiver() {

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

}

In your activity:

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

}

Then, in any of the fragments (or services, etc..):

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

Using EventBus

Create an event class:

data class Event(val keyCode: Int)

In your activity:

class SomeActivity : AppCompatActivity() {

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

}

Then, in your fragment:

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

}
Arela answered 6/12, 2018 at 9:44 Comment(1)
Broadcasts must die! haha. It was nice from you to include both broadcasts and eventbus in the anser by the way.Socialism
R
7

Following @hsu.tw answer to avoid tight coupling I found this gist.

Avoiding tight coupling comes with a price: you need a focusable view (luckily it was my case since I already had a view in foreground that listens to other touch events, so I just added the View.OnKeyListener to it).

The steps needed to attach a View.OnKeyListener to a view in a Fragment independently of the Activity are (check the gist):

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

I implemented this in the onViewCreated callback of my Fragment

Regional answered 5/8, 2019 at 15:24 Comment(1)
onViewCreated? It seems more sense.Cleric
P
2

I've subclassed Activity and Fragment classes to perform KeyEvents passing. For me, it looks clearer than sending local broadcasts. But this solution can be not so flexible. Choose the preferred way by yourself.

Here is the activity:

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

And the fragment:

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

Gist: https://gist.github.com/0neel/7d1ed5d26f2148b4168b6616337159ed

Poul answered 4/5, 2017 at 9:22 Comment(0)
C
1

I have the same problem in developing Android TV app.

And I solve this problem like this:

In onCreateView method, I call "requestFocus" by some View. (I mark it as ViewA.) Then I set KeyEventListener to ViewA.

In your case, you should do it (set-KeyEventListener) in Adapter and PagerChangeListener.

Cleric answered 14/6, 2019 at 8:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.