Several tries to ask this question in #android-dev (irc) and hours of searching, but I still don't have a solution to this problem.
I'm currently working on the search-function in my android music player. I'm using the amazing ActionBarSherlock to provide support for older android versions.
My Problem is the following:
When the user clicks the search menu/action button, the actionView of the clicked action should be expanded, and a new Fragment (the searchFragment) should be shown instead of the currently active one.
However when i'm attempting to do this, the actionView doesn't expand.
I've tried to expand the actionView, without adding the SearchFragment, and in that case the actionView DOES expand. However the combination seems impossible.
Here's my code:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item != null) {
if (item.getItemId() == R.id.collectionactivity_search_menu_button) {
item.expandActionView();
mTabsAdapter.replace(new SearchFragment(), false);
return true;
}
}
return false;
}
/**
* Replaces the view pager fragment at specified position.
*/
public void replace(int position, Fragment newFragment, boolean isBackAction) {
// Get currently active fragment.
ArrayList<Fragment> fragmentsStack = mFragments.get(position);
Fragment currentFragment = fragmentsStack.get(fragmentsStack.size() - 1);
if (currentFragment == null) {
return;
}
// Replace the fragment using a transaction.
this.startUpdate(mViewPager);
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.attach(newFragment).remove(currentFragment).commit();
if (isBackAction == true)
fragmentsStack.remove(currentFragment);
else
fragmentsStack.add(newFragment);
this.notifyDataSetChanged();
this.finishUpdate(mViewPager);
}
The mTabsAdapter.replace(...) method replaces the currently shown Fragment with the one in the first parameter. In Addition the fragment is being added to a custom backStack.
Replacing the Fragment before or after expanding the View didn't make any difference.
Hopefully somebody is able to help me :)
thanks in advance!
Have you tried setting your actionviews android:showAsAction to collapseActionView? that way you don't have to manage the expand/close action.
If that does not work you can handle it in another way,you set an expand listener and replace your fragment once your action view starts expanding
item.setOnActionExpandListener(new OnActionExpandListener() {
#Override
public boolean onMenuItemActionCollapse(MenuItem item) {
// Do something when collapsed
return true; // Return true to collapse action view
}
#Override
public boolean onMenuItemActionExpand(MenuItem item) {
mTabsAdapter.replace(new SearchFragment(), false);
return true; // Return true to expand action view
}
});
remember to return true to let the actionview expand
I found out what the problem was caused by.
My mTabsAdapter.replace(..) method was calling notifyDataSetChanged();. So everytime I replaced the fragment, onPrepareOptionsMenu was being called, resulting in the search action button being removed and added again, thus resulting in the actionView being collapsed.
The solution to this is to fix my onPrepareOptionsMenu, so the actionView will be expanded again, whenever onPrepareOptionsMenu is called and the actionView was expanded before.
Related
I have a bottom navigation view with 3 items which navigate to 3 different fragments (fragments are created only once and their instances are saved in mainactivity's onSavedInstanceState()) and on top of it a floating action button.
We want to change the icon drawable for the fab when each fragment is visited we tried both setImageResource() and .setImageDrawable() on the fab in a switch case when each bottom navigation icon is picked.
/**
* used to handle switching between fragments when a new navigation item is selected
*/
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.nav_tasks:
.........
loadFragment(tasksFragment);
mFab.setOnClickListener(mFabClickListenerTasks);
mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_add_task));
//2 tabs in 1 fragment
if (mTabLayout.getSelectedTabPosition() == 1)
mFab.hide();
else mFab.show();
break;
case R.id.nav_employees:
.......
loadFragment(employeesFragment);
mFab.setOnClickListener(mFabClickListenerEmployees);
mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_add_employee2));
mFab.show();
break;
case R.id.nav_departments:
.......
loadFragment(departmentsFragment);
mFab.setOnClickListener(mFabClickListenerDepartments);
mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_add_department));
mFab.show();
break;
}
item.setChecked(true);
return true;
}
void loadFragment(Fragment fragment) {
if (activeFragment == fragment)
return;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.hide(activeFragment).show(fragment);
activeFragment = fragment;
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if (activeFragment instanceof TasksFragment)
mFab.setImageResource(R.drawable.ic_add_task);
else if(activeFragment instanceof DepartmentsFragment)
mFab.setImageResource(R.drawable.ic_add_department);
else if(activeFragment instanceof EmployeesFragment)
mFab.setImageResource(R.drawable.ic_add_employee2);
transaction.commit();
}
The 3 fragments are mainly 3 recycler views, we also hide the fab when recyclerview scrolls.
The fab drawable will be set correctly when traversing the fragments from the bottom navigation , but in any fragment when we scroll it saves this state to return to it afterwards.
This removes the fab drawable when going to another fragment and leaves the fab empty with no icon drawable.
How can we solve this ?
I am having the same issue when the activity goes into onPause then onResume I was calling setImageResource on the FloatingActionButton. The FAB icon was disappearing. My solution was to call the following right after setImageResource
mFloatingActionButton.hide();
mFloatingActionButton.show();
It's a bug in the FloatingActionButton class: When calling show(), imageMatrixScale is set to 0. A call to setImageResource() then just displays blank. It works before calling show().
The bug has been introduced in the design lib 28.0.0, it was working on v27.1.1. Downgrade to 27.1.1
EDIT: Google Issuetracker
In my case downgrading design lib wasn't posible for many reasons. MrStahlfelge's answer helped me to find solution:
public class MyNewFab extends FloatingActionButton {
private Matrix imageMatrix;
...
#Override
protected void onFinishInflate() {
super.onFinishInflate();
imageMatrix = getImageMatrix();
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
setImageMatrix(imageMatrix);
}
}
This works for me. Hope it will help the others facing same problem.
The issue seems to be fixed in Material Components 1.1.0, but it is currently in alpha.
Tried with:
implementation com.google.android.material:material:1.1.0-alpha10
My problem is as follows:
I have one activity with many fragments. Main activity has "three dots" menu.
When I call a fragment, it has it's own menu (which is showing).
This fragment is a form with EditText fields, and checkboxes.
When a user changes something in those fields, and then press the "save" icon in the toolbar, the values get collected, and sent to the web service, and updated. BUT only the first time.
After going back (back arrow in toolbar), and returning to a fragment (new fragment gets created), after editing the fields, and pressing the "save" button, the form doesn't get saved/updated.
I debugged it, and found out that the "save" button which gets called for the second time, is actually the "save" button of the previous fragment.
The values in the editText fields gets collected inside a HashMap. And this HashMap gets updated and filled with new data inside new fragment. BUT when it gets to calling onOptionsItemSelected(), OLD DATA from the previous (or THE FIRST fragment) gets in the focus, and those data gets saved/updated.
So, my question is this: Is there a way to detach the menu items from the fragment, so when another (new) fragment is called, and gets created it attaches its own menu option to itself (not from the first fragment ever created)?
I tested it with System.identityHashCode(fragment), and when a user edit fields in "second" fragment, the hash code is OK, BUT when the "save" gets called hash code gets changed to the hash code of the first fragment.
This is the part of code from FormFragment:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.menu_main, menu);
int itemId = 0;
for (ActionList menuItem : allData.getActionList()) {
menu.add(1, itemId, itemId, menuItem.getPrompt());
itemId++;
}
inflater.inflate(R.menu.menu_activity, menu);
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_favourite) {
if (valuesArray != null) {
((CommInterface) getActivity()).getUpdateList(valuesArray, taskUrl, dataItemId);
return true;
} else {
Toast.makeText(getActivity(), "Not updated!", Toast.LENGTH_SHORT).show();
return true;
}
}
for (ActionList menuName : allData.getActionList()) {
if (item.getTitle().equals(menuName.getPrompt())) {
((CommInterface) getActivity()).formMenuItemSelected(menuName.getActionName(), position);
return true;
}
}
return super.onOptionsItemSelected(item);
}
And the part of code from MainActivity:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_choose_instance:
....
case android.R.id.home:
if (height > width) {
getSupportFragmentManager().popBackStack();
toolbarTitles.remove(toolbarTitlesCurrentNumber);
toolbarTitlesCurrentNumber--;
toolbar.setTitle(toolbarTitles.get(toolbarTitlesCurrentNumber));
if (toolbarTitlesCurrentNumber == 0) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
}
} else if (...) {
....
}
default:
return super.onOptionsItemSelected(item);
}
}
Create the menu items under fragments not under activity,
see the previous discussion
Android Options Menu in Fragment
The answer to this problem is: use the getChildFragmentManager() in TabbedPagerAdapter.
I was using the Activity's getSupportFragmentManager() which is wrong when you are using NESTED fragments like in TabbedFragment which has a ViewPager, and a ViewPager has (child) Fragments inside.
I am using SLiding menu at My App and according to menu item , I am changing always the fragment at activity which have binded already to menu. But There is performance issue. it is freezing when i am attaching the fragments. but after the wiew created. the performance is normal. I am replacing the fragments after call the toggle function at menu.
is there anyone to have any opinion about it ?
just call it as:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Pass the event to ActionBarDrawerToggle, if it returns
// true, then it has handled the app icon touch event
if (item.getItemId() == R.id.evnetCalender) {
if (!isToggle) {
item.setIcon(R.drawable.ic_view_list_white_24dp);
setFragment(caldroidCalendarFragment);
isToggle = true;
} else {
item.setIcon(R.drawable.ic_event_white_24dp);
setFragment(scheduleEventFragment);
isToggle = false;
}
}
and set your fragment when you toggle it
private void setFragment(Fragment fragment1) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.content, fragment1);
fragmentTransaction.commit();
}
this content identifier is your frame layout id in which your fragment take place
I have an activity which can contain several fragments. Each of the fragments can have their own menu entries in the ActionBar. This works fine so far and each item is clickable and performs the desired action.
My problem is the following. In the MainActivity I declared the following lines to intercept calls to the HomeIcon of the ActionBar:
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
clearBackStack();
setHomeFragment();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
I declared it in the Activity because I wanted that every Fragment should call this so that I don't have to catch the android.R.id.home case in each fragment.
In one Fragment I am using setDisplayHomeAsUpEnabled(true), so that I get the little arrow left of the ActionBar Icon. When the HomeIcon is clicked in this fragment I don't want to set the HomeFragment, I want to set the Fragment which was last displayed. So I have a onOptionsItemSelected - Method in the Fragment:
#Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case android.R.id.home:
setLastFragment();
return true;
...
However this does not work the way I wanted it to work. The Activity's onOptionsItemSelected is called first, catches the MenuItem and redirects to the HomeFragment. With the other MenuItems declared in other fragments i can check the see the same behaviour. Activity is called first, doesn't catch the MenuItem (default case) and then redirects to super.onOptionsItemSelected(item).
So it seems that this is the case how Android handles the Menu Clicks. First Activity, then Fragment. Is there a way to change this? I don't want to put the android.R.id.home-case in every fragment and handle it there. Is there a nicer way to do this?
I just encounter this problem, and I have made it work using following code.
In the activity's onOptionsItemSelectedfunction, add:
if (id == android.R.id.home){
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.container);
if(null != currentFragment && currentFragment.onOptionsItemSelected(item)){
return true;
}
}
And in the fragment's onOptionsItemSelected method, you handle the corresponding things.
In this way, if the fragment has any things to do for the menu item, it will do it and return true to stop any other process.
And if the fragment does not have anything to do with this item, it will return false or call super.onOptionsItemSelected method which may eventually return false to let others process it.
According to the developers reference,
"Return false to allow normal menu processing to proceed, true to consume it here."
So I would try returning 'false' by default in the Activity's implementation of onOptionsItemSelected(), this way the event will pass on to the Fragment's implementation if it is not caught.
Not sure if it's possible. In the official docs available here:
http://developer.android.com/guide/topics/ui/actionbar.html#ActionEvents
There is a note, that states the following:
[...] However, the activity gets a chance to handle the event first, so the system first calls onOptionsItemSelected() on the activity, before calling the same callback for the fragment.
You can do as #Surely written, it's great idea, but in that case you will call onOptionsItemSelected on fragment without knowing which fragment it is, and you should override onOptionsItemSelected method in all your fragments.
If you only want to call this method for particular fragments, you should find them by tag, which you used when adding them:
case android.R.id.home:
Fragment frag = getSupportFragmentManager()
.findFragmentByTag(FRAGMENT_TAG);
if(frag != null && frag.isVisible() && frag.onOptionsItemSelected(item)) {
return true;
Tag is specifying like this:
fragmentManager.beginTransaction()
.add(containerId, fragment, FRAGMENT_TAG)
I'm currently trying to adapt my application to use the "Compatibility Libraries for Android v4" to provide the benefits of the usage of fragments even to Android 1.6 users.
The implementation of a context menu seems to be tricky:
The main activity of the application
is extending the FragmentActivity
class.
The fragments are all based on one
class which extends the Fragment class.
The fragment class is calling
registerForContextMenu() in its onCreateView() method and overrides the methods
onCreateContextMenu() and onContextItemSelected().
For onCreateContextMenu() this works pretty well. The context menu is inflated from a resource file and slightly modified based on the selected item (which is based on a listView... even if the fragment is not an ListFragment).
The problem occurs when a context menu entry is selected.
onContextItemSelected() is called for all currently existing fragments starting with the first added one.
In my case the fragments are used to show the content of a folder structure. When the context menu of a subfolder fragment is opened and a menu item is selected, onContextItemSelected() is first called on the upper levels (depending on how many fragments are allowed/visible in this moment).
Right now, I use a workaround by a field on activity level which holds the tag of last fragment calling its onCreateContextMenu(). This way I can call "return super.onContextItemSelected(item)" in the begin of onContextItemSelected() when the stored tag is not the same as getTag().
But this approach looks a bit dirty to me.
Why is onContextItemSelected() called on all fragments? and not just one the one that was calling onCreateContextMenu()?
What is the most elegant way to handle this?
I'll post an answer even though you found a workaround because I just dealt with a similar issue. When you inflate the context menu for a specific fragment, assign each menu item a groupId that is unique to the fragment. Then test for the groupId in 'onContextItemSelected.' For Example:
public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo menuInfo) {
menu.add(UNIQUE_FRAGMENT_GROUP_ID, MENU_OPTION_1, 0, R.string.src1);
menu.add(UNIQUE_FRAGMENT_GROUP_ID, MENU_OPTION_2, 0, R.string.src2);
}
public boolean onContextItemSelected(MenuItem item) {
//only this fragment's context menus have group ID of -1
if (item.getGroupId() == UNIQUE_FRAGMENT_GROUP_ID) {
switch(item.getItemId()) {
case MENU_OPTION_1: doSomething(); break;
case MENU_OPTION_2: doSomethingElse(); break;
}
}
This way all of your fragments will still receive calls to 'onContextItemSelected,' but only the correct one will respond, thus avoiding the need to write activity-level code. I assume a modified version of this technique could work even though you aren't using 'menu.add(...)'
Another one solution:
#Override
public boolean onContextItemSelected(MenuItem item) {
if (getUserVisibleHint()) {
// context menu logic
return true;
}
return false;
}
Based upon this patch from Jake Wharton.
I liked the simple solution by Sergei G (based on Jake Wharton fix), but inverted because it is easier to add to several fragments:
public boolean onContextItemSelected(android.view.MenuItem item)
{
if( getUserVisibleHint() == false )
{
return false;
}
// The rest of your onConextItemSelect code
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
}
After that, the code same as it was before.
I found out a very easy solution. As onCreateContextMenu() is called every time the ContextMenu is created I set a boolean variable to true.
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.film_menu, menu);
bMenu=true;
}
The only other thing I have to do is ask for that variable OnContextItemSelected()
public boolean onContextItemSelected(MenuItem item) {
if (bMenu) {
bMenu=false;
if (item.getItemId() == R.id.filmProperties) {
///Your code
return true;
} else {
return super.onContextItemSelected(item);
}
} else {
return super.onContextItemSelected(item);
}
}
That's it.
I found an alternative. It does not change anything on my problem above, but it makes it pointless.
I have remove the context menu completely from my application. Instead I capture the longclick on a list item and change the visible buttons of the action bar in this moment.
From the user point of view this is much more tablet like as a context menu.
In backward compatible applications the actionbar does not exist. So I've decided to build my own (kind of toolbar on top) for the devices pre Honeycomb.
If you would like to stay with the context menu, I did not find a better solution as the workaround I've mentioned above.
In my first fragment, i have set all my menu id > 5000 so, as first line of code of onContextItemSelected of first fragment i have
if (item.getItemId() < 5000) return false;
and the second fragment will be invoked.
If you are using adapters with listviews in your fragment this might help.
public boolean onContextItemSelected(final MenuItem item) {
final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
//Check if the context menu call came from the list in this fragment (needed for support for multiple fragments in one screen)
if (info.targetView.getParent() != getView().findViewById(android.R.id.list))
return super.onContextItemSelected(item);
//Handle context menu item call
switch (item.getItemId()) {
...
}
}
Just change
#Override
public boolean onContextItemSelected(MenuItem item) {
return true;
}
to
#Override
public boolean onContextItemSelected(MenuItem item) {
return super.onContextItemSelected(item);
}
and will work great!!!
IMHO we may just check if target view is child of fragment listview. It is very simple and work for me well. I just added to all my fragments:if (getListView.getPositionForView(info.targetView) == -1)
return false when migrate from older API
This is example from one of my parent fragments. This is Scala, but I hope you got an idea.
#Loggable
override def onContextItemSelected(menuItem: MenuItem): Boolean = {
for {
filterBlock <- TabContent.filterBlock
optionBlock <- TabContent.optionBlock
environmentBlock <- TabContent.environmentBlock
componentBlock <- TabContent.componentBlock
} yield menuItem.getMenuInfo match {
case info: AdapterContextMenuInfo =>
if (getListView.getPositionForView(info.targetView) == -1)
return false
TabContent.adapter.getItem(info.position) match {
case item: FilterBlock.Item =>
filterBlock.onContextItemSelected(menuItem, item)
case item: OptionBlock.Item =>
optionBlock.onContextItemSelected(menuItem, item)
case item: EnvironmentBlock.Item =>
environmentBlock.onContextItemSelected(menuItem, item)
case item: ComponentBlock.Item =>
componentBlock.onContextItemSelected(menuItem, item)
case item =>
log.debug("skip unknown context menu item " + info.targetView)
false
}
case info =>
log.fatal("unsupported menu info " + info)
false
}
} getOrElse false
P.S. If you trace calls of onContextItemSelected(...) you may notify that super.onContextItemSelected(item) return always false. Valid onContextItemSelected invoked AFTER, not WITHIN. So super.onContextItemSelected(item) is useless and I replaced it with false.
I found an easier solution than the exposed:
public boolean onContextItemSelected(MenuItem item) {
ListView yourList = (ListView) (ListView) getView().findViewById(R.id.yourList);
if (!yourList.hasFocus())
return false;
switch(item.getItemId()) {
...
}
}
In the method changed return true; to return super.onContextItemSelected(item); in my onContextItemSelected() override and everything started working.