Action Bar .selectTab() and .setSelectedNavigationItem() not working - android

I have an ActionBar with multiple tabs, each linked to a fragment. The problem I have is that when I use either bar.selectTab(Tab) or bar.setSelectedNavigationItem(int), it doesn't work. Specifically, this problem occurs when the tabs get reduced down to a Spinner in the ActionBar.

There is a known bug with the ActionBar, specifically with the methods mentioned above and specifically when the ActionBar's tabs are reduced to a Spinner.
Here's my workaround. It uses reflection to drill into the ActionBar if the tabs have been reduced to a Spinner. In your Activity class, create a method like so:
/**
* A documented and yet to be fixed bug exists in Android whereby
* if you attempt to set the selected tab of an action bar when the
* bar's tabs have been collapsed into a Spinner due to screen
* real-estate, the spinner item representing the tab may not get
* selected. This bug fix uses reflection to drill into the ActionBar
* and manually select the correct Spinner item
*/
private void select_tab(ActionBar b, int pos) {
try {
//do the normal tab selection in case all tabs are visible
b.setSelectedNavigationItem(pos);
//now use reflection to select the correct Spinner if
// the bar's tabs have been reduced to a Spinner
View action_bar_view = findViewById(getResources().getIdentifier("action_bar", "id", "android"));
Class<?> action_bar_class = action_bar_view.getClass();
Field tab_scroll_view_prop = action_bar_class.getDeclaredField("mTabScrollView");
tab_scroll_view_prop.setAccessible(true);
//get the value of mTabScrollView in our action bar
Object tab_scroll_view = tab_scroll_view_prop.get(action_bar_view);
if (tab_scroll_view == null) return;
Field spinner_prop = tab_scroll_view.getClass().getDeclaredField("mTabSpinner");
spinner_prop.setAccessible(true);
//get the value of mTabSpinner in our scroll view
Object tab_spinner = spinner_prop.get(tab_scroll_view);
if (tab_spinner == null) return;
Method set_selection_method = tab_spinner.getClass().getSuperclass().getDeclaredMethod("setSelection", Integer.TYPE, Boolean.TYPE);
set_selection_method.invoke(tab_spinner, pos, true);
} catch (Exception e) {
e.printStackTrace();
}
}
Example usage of this might be:
private void delete_fragment_and_tab(String fragment_tag) {
//remove the fragment
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(getFragmentManager().findFragmentByTag(fragment_tag));
//now remove the tab from the ActionBar
//and select the previous tab
ActionBar b = getActionBar();
Tab tab = b.getSelectedTab();
bar.removeTab(tab);
select_tab(bar, bar.getNavigationItemCount() -1);
}

Related

Android findViewById(android.R.id.home) return null with support library

I want to change ActionBar home button left padding. I've tried this solution. But when I try findViewById(android.R.id.home) I get null. In the same time android.R.id.home is not 0.
And this happens only if I use android-suppot-v7. If I don't use support library all goes good.
Maybe someone can help me?
Here is my simple code:
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
ImageView view = (ImageView)findViewById(android.R.id.home);
if (view !=null){
view.setPadding(10, 0, 0, 0);
}
}
}
Layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.timoshkois.actionbar.MainActivity">
</RelativeLayout>
Hmm you're not wrong, if you look at the source for the Activity you inherit from, they also use android.R.id.home
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
Like this
#Override
public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
if (super.onMenuItemSelected(featureId, item)) {
return true;
}
final ActionBar ab = getSupportActionBar();
if (item.getItemId() == android.R.id.home && ab != null &&
(ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
return onSupportNavigateUp();
}
return false;
}
Looking how the ActionBar is created it uses these classes:
https://github.com/android/platform_frameworks_support/blob/5476e7f4203acde2b2abbee4e9ffebeb94bcf040/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
https://github.com/android/platform_frameworks_base/blob/master/core/java/com/android/internal/app/WindowDecorActionBar.java
which leads to the ActionBar class, this has a possible clue about why it returns null
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/ActionBar.java#L106
/**
* Standard navigation mode. Consists of either a logo or icon
* and title text with an optional subtitle. Clicking any of these elements
* will dispatch onOptionsItemSelected to the host Activity with
* a MenuItem with item ID android.R.id.home.
*
* #deprecated Action bar navigation modes are deprecated and not supported by inline
* toolbar action bars. Consider using other
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
This deprecation means the new ToolBar won't use nav modes so maybe this also means Toolbar will not have this Android id (R.id.home) - which makes sense as the previous links show that app compat not uses a Toolbar under the hood, which legacy implementations will not be using.
As a test you could do what the comment says and override onOptionsItemSelected press the logo and query the view you are passed to find it's id getId()
Apparently, 'home' is the name of your layout file? [No, I see that's activity_main.] To access the relativelayout item, it needs an ID of its own, like android:id="#+id/home"

Android fragment -- OnDestroy being called when switching to a different tab followed by OnCreate when returning to the tab?

I have a tabbed Android app. When I switch away from a tab, I am seeing OnDestroy called for the corresponding fragment. When I come back to that tab, I am then seeing OnCreate called for THE SAME FRAGMENT OBJECT. It is definitely the same object, not another instance of the same class. If I switch away to another tab, OnDestroy is called again, followed by a third call to OnCreate if I return to that tab, and so on.
Android docs appear to state that this should not happen.
Does this indicate an architectural problem with my app? If it's relevant, I am using Mono, and I am setting RetainInstance to true in OnActivityCreated.
EDIT: code below is from the activity that wraps all the tabs.
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
MainContext mainContext = MainContext;
TabContext tabContext = mainContext.TabContext;
// Tab is a custom model object; not related to Android tabs.
List<Tab> tabs = tabContext.Tabs;
foreach (Tab tab in tabs) {
string displayString = tab.DisplayString;
string withUnderscores = displayString.Replace(' ', '_');
Type fragmentType = Type.GetType(Assembly.GetExecutingAssembly().GetName().Name + "." + withUnderscores + "Fragment");
this.AddTab(tab, (Fragment) Activator.CreateInstance(fragmentType));
}
ActionBar bar = this.ActionBar;
bar.NavigationMode = ActionBarNavigationMode.Tabs;
bar.SetDisplayShowTitleEnabled(false);
bar.SetDisplayShowHomeEnabled(false);
SetContentView(Resource.Layout.MainTabActivityLayout);
if (bundle != null) {
int index = bundle.GetInt("index");
ActionBar.SetSelectedNavigationItem(index);
}
}
private void AddTab(Tab tab, Fragment fragment) {
ActionBar bar = this.ActionBar;
Android.App.ActionBar.Tab droidTab = bar.NewTab();
droidTab.SetTag(tab.ToString());
// omitting code that sets icon and display text.
droidTab.TabSelected += (sender, e) => {
e.FragmentTransaction.Replace(Resource.Id.fragmentContainer, fragment, tab.ToString());
};
this.ActionBar.AddTab(droidTab);
}
It is calling the onDestroy because you are replacing the fragment each time you change the tab
e.FragmentTransaction.Replace(Resource.Id.fragmentContainer, fragment, tab.ToString());
As you could see that you are only using one layout(Resource.Id.fragmentContainer) for replacing the fragment therefore it is reusing it after you change tab and releasing the memory of the replaced fragment thus calling the onDestroy.

Actionbar Sherlock - tabs won't change in landscape when swiping

I have a screen which uses ViewPager + actionbar Sherlock tabs. I have a setOnPageChangeListener set on the pager and it does the following:
#Override
public void onPageSelected(final int position) {
actionBar.setSelectedNavigationItem(position);
}
This works just fine in portrait mode and even in landscape if I only have few tabs and all the tabs are displayed. However if I add few more tabs in landscape, these collapse into a single drop-down widget. When I page through the ViewPager the setSelectedNavigationItem method is executed but now it has no effect on the drop-down selection: it stays at the last selected value. Which is really bad since user is missing a visual clue: tab may say "One" but user is already on page #6.
Is there a way to programmatically change which tab to display based on the position?
P.S. I know why this happens:
Here's code from com.android.internal.app.ActionBarImpl:
public void setSelectedNavigationItem(int position) {
switch (mActionView.getNavigationMode()) {
case NAVIGATION_MODE_TABS:
selectTab(mTabs.get(position));
break;
case NAVIGATION_MODE_LIST:
mActionView.setDropdownSelectedPosition(position);
break;
default:
throw new IllegalStateException(
"setSelectedNavigationIndex not valid for current navigation mode");
}
}
And when I step through that I can see that the navigation mode is still NAVIGATION_MODE_TABS though tabs are displayed as list. Now - my knee jerk reaction is to put code into onConfigurationChanged to set navigation mode appropriately but shouldn't this happen automatically?
P.P.S. And there's Android bug filed for it already that contains the patch
Base on the Android bug I'm referencing in the question (see P.P.S.) here's workaround that indeed works. Just add it to your pager
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener()
{
#Override
public void onPageSelected(int position)
{
actionBar.getTabAt(position).select();
ViewParent root = findViewById(android.R.id.content).getParent();
findAndUpdateSpinner(root, position);
}
/**
* Searches the view hierarchy excluding the content view
* for a possible Spinner in the ActionBar.
*
* #param root The parent of the content view
* #param position The position that should be selected
* #return if the spinner was found and adjusted
*/
private boolean findAndUpdateSpinner(Object root, int position)
{
if (root instanceof android.widget.Spinner)
{
// Found the Spinner
Spinner spinner = (Spinner) root;
spinner.setSelection(position);
return true;
}
else if (root instanceof ViewGroup)
{
ViewGroup group = (ViewGroup) root;
if (group.getId() != android.R.id.content)
{
// Found a container that isn't the container holding our screen layout
for (int i = 0; i < group.getChildCount(); i++)
{
if (findAndUpdateSpinner(group.getChildAt(i), position))
{
// Found and done searching the View tree
return true;
}
}
}
}
// Nothing found
return false;
}
});
You need to add some code in the ActionBarSherlock library project in file ( ActionBarImpl.java ) see this link

How to show selected fragment in action bar tab

I am facing one issue regarding tab swipe. My project is built on Android 3.2. I am implementing tab swipe using support library 4.0 (android-support-v4.jar). Everything implemented is working fine but when I deploy my app to an ICS device, then in portrait mode I am getting a spinner in action bar for tab selection. In portrait mode, the tab selection is not changing when swipe is done although content is changing, and everything is working fine in landscape mode.
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayHomeAsUpEnabled(true);
// Set up the ViewPager with the sections adapter.
ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
});
I have tried putting breakpoint actionBar.setSelectedNavigationItem(position); on this line and even on portrait mode it's getting called but the selection is not changing.
Can anybody help with this?
EDITED:
Found a similar problem but don't see exactly how it is solved and how to integrate it in my code.
Problem:
Due to an insufficient real-state the platform uses collapsed navigation (i.e. Spinner). The system auto-determines NAVIGATION_MODE_TABS for landscape & NAVIGATION_MODE_LIST for portrait, changing the orientation from landscape to portrait updates the UI but for some reason does not update the navigation mode property to NAVIGATION_MODE_LIST and hence mActionView.setDropdownSelectedPosition(position) is not called. See the following code of ActionBarImpl : setSelectedNavigationItem
public void setSelectedNavigationItem(int position) {
switch (mActionView.getNavigationMode()) {
case NAVIGATION_MODE_TABS:
selectTab(mTabs.get(position));
break;
case NAVIGATION_MODE_LIST:
mActionView.setDropdownSelectedPosition(position);
break;
default:
throw new IllegalStateException(
"setSelectedNavigationIndex not valid for current navigation mode");
}
}
Workaround solution:
Through reflection we can get the tab spinner object and call setSelection method.
private Spinner getTabSpinner()
{
try
{
int id = getResources().getIdentifier("action_bar", "id", "android");
View actionBarView = findViewById(id);
Class<?> actionBarViewClass = actionBarView.getClass();
Field mTabScrollViewField = actionBarViewClass.getDeclaredField("mTabScrollView");
mTabScrollViewField.setAccessible(true);
Object mTabScrollView = mTabScrollViewField.get(actionBarView);
if (mTabScrollView == null) {
return null;
}
Field mTabSpinnerField = mTabScrollView.getClass().getDeclaredField("mTabSpinner");
mTabSpinnerField.setAccessible(true);
Object mTabSpinner = mTabSpinnerField.get(mTabScrollView);
if (mTabSpinner != null)
{
return (Spinner)mTabSpinner;
}
}
catch (Exception e) {
return null;
}
return null;
}
Then call the above method in onPageSelected event.
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
Spinner spinner = getTabSpinner();
if (spinner != null) {
spinner.setSelection(position);
}
}
Referred this post https://gist.github.com/2657485

Problems when switching ListView ChoiceMode on screen rotation

I am trying to build a split pane based on the example code provided at the end of the Android documentation about Fragments, basically a LinearLayout containing two fragments (all the details can be found in the above link):
TitlesFragment to display a list of titles
DetailsFragment to display the details of the currently selected title
My problem is that the following unexpected behavior happens:
I start the application in Landscape orientation: the titles list is displayed on the left and the details of the currently selected title (the first one at the beginning) are displayed on the right.
I select item X from the titles list: the title becomes highlighted since the ListView is set to CHOICE_MODE_SINGLE and its items implement the Checkable interface.
I switch to Portrait orientation (by rotating the device or emulator): as expected, only the titles list is shown and no item is selected in this configuration, because the ListView gets recreated and, by default, it is set to CHOICE_MODE_NONE.
I select item Y from the titles list: as expected, an activity showing only the DetailsFragment corresponding to the selection made is shown.
I switch back to Landscape orientation (here's where the unexpected behavior happens): the DetailsFragmenton the right correctly shows the details of item Y (which was selected in portrait orientation) but the TitlesFragment still shows item X as selected (i.e. the one which was selected BEFORE the first screen rotation).
Of course, I would like that the last selection made in Portrait orientation was correctly shown also within the TitlesFragment, otherwise I end up with an inconsistent highlighting which shows item X as selected while displaying the details of item Y.
I post the relevant code where the ListView mode is changed (within 'onActivityCreated') and the selected item is set (within the 'showDetails' method):
public static class TitlesFragment extends ListFragment {
boolean mDualPane;
int mCurCheckPosition = 0;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Populate list with our static array of titles.
setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
// Check to see if we have a frame in which to embed the details
// fragment directly in the containing UI.
View detailsFrame = getActivity().findViewById(R.id.details);
mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
if (mDualPane) {
// In dual-pane mode, the list view highlights the selected item.
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Make sure our UI is in the correct state.
showDetails(mCurCheckPosition);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
/**
* Helper function to show the details of a selected item, either by
* displaying a fragment in-place in the current UI, or starting a
* whole new activity in which it is displayed.
*/
void showDetails(int index) {
mCurCheckPosition = index;
if (mDualPane) {
// We can display everything in-place with fragments, so update
// the list to highlight the selected item and show the data.
getListView().setItemChecked(index, true);
// Check what fragment is currently shown, replace if needed.
DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {
// Make new fragment to show this selection.
details = DetailsFragment.newInstance(index);
// Execute a transaction, replacing any existing fragment
// with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.details, details);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), DetailsActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
}
Does anyone have an idea about how to solve such issue?
Thanks in advance.
Two years later, here I am working with the Fragment Layout sample from Android's v4 support library and I'm stuck with this exact same issue!
Here's the only fix I came up with that doesn't require using CHOICE_MODE_SINGLE all the time:
just add a call to getListView().setItemChecked(mCurCheckPosition, true); in the onResume() method of TitlesFragment. And... that's it!
Maybe you should set the value of outState BEFORE you call super.onSaveInstanceState?
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("curChoice", mCurCheckPosition);
super.onSaveInstanceState(outState);
}
I've encountered the same problem on that code and as workaround I call setItemChecked on the onStart method:
#Override
public void onStart() {
super.onStart();
if (mDualPane) {
getListView().setItemChecked(mCurCheckPosition, true);
}
}
Or as LearningNerd said do it in onResume()
If you place android:configChanges="orientation" in your activity tag in the manifest file, your app will ignore the rotations but still render the relevant layouts.

Categories

Resources