I have a BottomNavigationView in my android activity, which consists of 4 menuItem. When I tap on the downloads menu I check if there are any downloaded contents available, if available I will allow the navigation to happen and if there is no downloaded content I will show a Toast stating the same and I want the previous tab to remain selected. In iOS I can use the delegate method shouldSelectViewController to determine whether navigation can be allowed or not.
The method signature is specified below:
- (BOOL)tabBarController:(UITabBarController *)tabBarController
shouldSelectViewController:(UIViewController *)viewController;
I tried reselecting the previously selected tab, as a result, the previous item is retained but the selected item color is still assigned to the downloads tab.
private void BottomNavigationItemSelected(object obj, BottomNavigationView.NavigationItemSelectedEventArgs args)
{
Android.Support.V4.App.Fragment fragment = null;
Android.Support.V4.App.Fragment currentFragment = SupportFragmentManager.FindFragmentById(Resource.Id.content_frame);
string title = "";
if (args.Item.ItemId == Resource.Id.menu_explore)
{
_selectedToolbarId = args.Item.ItemId;
title = Resources.GetString(Resource.String.shelf_title);
fragment = _exploreFragment;
_fragmentTag = "Home";
}
else
{
title = args.Item.TitleFormatted.ToString();
}
if (args.Item.ItemId == Resource.Id.menu_dashboard)
{
//COULD BE MADE CONFIGURABLE
//fragment = _dashboardFragment;
_selectedToolbarId = args.Item.ItemId;
fragment = _redesignDashboard;
_fragmentTag = "Dashboard";
}
else if (args.Item.ItemId == Resource.Id.menu_more)
{
_selectedToolbarId = args.Item.ItemId;
fragment = _moreFragment;
_fragmentTag = "More";
}
else if (args.Item.ItemId == Resource.Id.menu_report)
{
_selectedToolbarId = args.Item.ItemId;
fragment = _reportFragment;
_fragmentTag = "Report";
}
else if (args.Item.ItemId == Resource.Id.menu_downloads)
{
List<Product> _downloadProducts = DBService.GetDB().GetDownloadedProducts();
if (_downloadProducts == null || _downloadProducts.Count == 0)
{
_bottomNavigationView.SelectedItemId = _selectedToolbarId;
Toast.MakeText(this, "No downloaded products", ToastLength.Short).Show();
args.Item.SetChecked(false);
}
else
{
_downloadGalleryFragment = new DownloadGalleryFragment(_downloadProducts);
fragment = _downloadGalleryFragment;
_fragmentTag = "Downloads";
}
}
if (fragment != null)
{
_toolbarTitle.Text = title;
ToggleTitle(true);
SupportFragmentManager.BeginTransaction().SetCustomAnimations(Resource.Animation.fab_slide_in_from_right, Resource.Animation.fab_slide_out_to_left).Replace(Resource.Id.content_frame, fragment, _fragmentTag).Commit();
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/menu_explore"
android:enabled="true"
android:title="#string/explore"
android:icon="#drawable/explore_icon"
app:showAsAction="always" />
<item
android:id="#+id/menu_dashboard"
android:enabled="true"
android:title="#string/dashboard"
android:icon="#drawable/Dashboard_new_icon"
app:showAsAction="always" />
<item
android:id="#+id/menu_report"
android:enabled="true"
android:title="#string/reports"
android:icon="#drawable/dashboard_icon"
app:showAsAction="always" />
<item
android:id="#+id/menu_downloads"
android:enabled="true"
android:title="#string/menu_downloads"
android:icon="#drawable/download_icon"
app:showAsAction="always" />
<item
android:id="#+id/menu_more"
android:enabled="true"
android:title="#string/more_bottombar"
android:icon="#drawable/more_icon"
app:showAsAction="always" />
</menu>
There is no such delegate that works as shouldSelectViewController but what you can do is get the menu items of the bottom nav bar and disables these menu items:
Something like this:
var listMenuItems = new List<IMenuItem>();
for (int i = 0; i < bottomNav.Menu.Size(); i++)
{
listMenuItems.Add(bottomNav.Menu.GetItem(i));
}
Once you have them here you can manipulate them as you like, to enable or disable an item just use the SetEnabled method that takes a boolean as a parameter.
Seems like this is an issue with Android or the Bottom Navigation View. When I executed the reselection of the previous fragment after a small delay of 50milliseconds its working fine. ie the reselected fragment or the previous fragments icon gets highlighted as required.
if (args.Item.ItemId == Resource.Id.menu_downloads)
{
List<Product> _downloadProducts = DBService.GetDB().GetDownloadedProducts();
if (_downloadProducts == null || _downloadProducts.Count == 0)
{
_readProgressTimerTask = new Timer
{
Enabled = true,
Interval = 50,
AutoReset = false
};
_readProgressTimerTask.Elapsed += OnProgressCheckTimeElapsed;
Toast.MakeText(this, this.Resources.GetString(Resource.String.no_downloads), ToastLength.Short).Show();
}
else
{
_downloadGalleryFragment = new DownloadGalleryFragment(_downloadProducts);
fragment = _downloadGalleryFragment;
_fragmentTag = "Downloads";
}
}
private void OnProgressCheckTimeElapsed(System.Object source, ElapsedEventArgs args)
{
this.RunOnUiThread(() =>
{
_bottomNavigationView.SelectedItemId = _selectedToolbarId;
});
}
I recommend that you first read the guideline available here to implement bottom navigation for android.
That what you want doesn't make much sense, an empty screen should be shown saying something like "There is not content available" or the message you like.
In this link BottomNavigationView you can find all public methods and listeners you can set to bottom navigation. As for your case, you might be interested in adding these two.
setOnNavigationItemReselectedListener()
setOnNavigationItemSelectedListener()
Best regards, Pedro.
Related
Currently, this is how I implement Chrome custom tabs
String url = "http://www.google.com/";
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(WelcomeFragment.this.getActivity(), Uri.parse(url));
I was wondering, is it possible to have Chrome custom tabs as View object?
The reason I'm asking so that that, previously, I have a fragment, which is having ViewAnimator object. ViewAnimator will in-turn animate between 2 WebViews.
One WevView is displaying mobile version of the web page. Another WebView is displaying desktop version of the web page.
Here's the code which used to alternate between the 2 WebViews
public void updateWebView() {
int index = getCurrentWebViewHolderIndex();
final WebViewHolder webViewHolder = webViewHolders[index];
if (webViewHolder == null) {
return;
}
final WebView webView = webViewHolder.webView;
boolean loadUrl = false;
boolean reload = false;
synchronized (monitor) {
if (false == webViewHolder.loadUrl) {
webViewHolder.loadUrl = true;
loadUrl = true;
} else if (webViewHolder.error) {
webViewHolder.error = false;
reload = true;
}
}
if (loadUrl) {
webView.loadUrl(getUrl(index));
} else if (reload) {
webView.reload();
}
final WebViewFragmentActivity activity = (WebViewFragmentActivity)WebViewFragment.this.getActivity();
if (activity != null) {
final int progress = webViewHolder.progress;
if (progress >= 100) {
activity.setProgressBarVisibilityEx(false);
} else {
activity.setProgressBarVisibilityEx(true);
activity.setProgressEx(progress);
}
}
if (index == 0) {
// Slide from left.
Animation slideInLeftFast = AnimationUtils.loadAnimation(this.getActivity(), R.anim.slide_in_left_fast);
Animation slideOutRightSlow = AnimationUtils.loadAnimation(this.getActivity(), R.anim.slide_out_right_slow);
this.webViewViewAnimator.setInAnimation(slideInLeftFast);
this.webViewViewAnimator.setOutAnimation(slideOutRightSlow);
} else {
// Slide from right.
Animation slideInRightFast = AnimationUtils.loadAnimation(this.getActivity(), R.anim.slide_in_right_fast);
Animation slideOutLeftSlow = AnimationUtils.loadAnimation(this.getActivity(), R.anim.slide_out_left_slow);
this.webViewViewAnimator.setInAnimation(slideInRightFast);
this.webViewViewAnimator.setOutAnimation(slideOutLeftSlow);
}
if (webViewViewAnimator.getChildCount() >= 2) {
webViewViewAnimator.removeViewAt(0);
}
webViewViewAnimator.addView(webView);
webViewViewAnimator.setDisplayedChild(webViewViewAnimator.getChildCount() - 1);
}
Here's the XML code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/web_view_linear_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal" >
<ViewAnimator
android:id="#+id/web_view_view_animator"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
</LinearLayout>
I was told that Chrome Custom Tabs is having a much better performance than WebView.
However, I don't find a way, to let single fragment holding 2 different Chrome Custom Tabs. As, they are Intent, not View.
But, is there any way, to have Chrome custom tabs as View object?
Chrome Custom Tabs, will launch its own UI on a full screen. You cannot have it like a View in XML or Java/Kotlin.
As per the docs here - https://developer.chrome.com/docs/android/custom-tabs/overview/, it lets you customize 3 things in the UI only -
Toolbar color
Enter and exit animations
Add custom actions to the browser toolbar, overflow menu and bottom toolbar
What I am trying to do is showing a PopupWindow pointing to the overflow icon (the three dots) on the Toolbar. So I need to get a reference to the View object with the id of the icon. But what is the id?
The PopupWindow is used to tell the users that there are new entries added to the overflow menu. And suggest users to check it out.
You should create the button id
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="overflowActionButton"/>
</resources>
then create the button style
<style name="Widget.ActionButton.Overflow" parent="Widget.AppCompat.ActionButton.Overflow">
<item name="android:id">#id/overflowActionButton</item>
</style>
and add this style in the theme
<style name="Theme.App" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="actionOverflowButtonStyle">#style/Widget.ActionButton.Overflow</item>
</style>
finally you should find the button view by id
activity.findViewById(R.id.overflowActionButton)
and do what you want
The overflow menu item doesn't have a resource id. I found the overflow view by traversing the toolbar. The debugger showed an id of -1 and the Hierarchy Viewer showed no resource-id.
Here is how I found the overflow view without a resource id:
/**
* Get the OverflowMenuButton.
*
* #param activity
* the Activity
* #return the OverflowMenuButton or {#code null} if it doesn't exist.
*/
public static ImageView getOverflowMenuButton(Activity activity) {
return findOverflowMenuButton(activity, findActionBar(activity));
}
static ImageView findOverflowMenuButton(Activity activity, ViewGroup viewGroup) {
if (viewGroup == null) {
return null;
}
ImageView overflow = null;
for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {
View v = viewGroup.getChildAt(i);
if (v instanceof ImageView && (v.getClass().getSimpleName().equals("OverflowMenuButton") ||
v instanceof ActionMenuView.ActionMenuChildView)) {
overflow = (ImageView) v;
} else if (v instanceof ViewGroup) {
overflow = findOverflowMenuButton(activity, (ViewGroup) v);
}
if (overflow != null) {
break;
}
}
return overflow;
}
static ViewGroup findActionBar(Activity activity) {
try {
int id = activity.getResources().getIdentifier("action_bar", "id", "android");
ViewGroup actionBar = null;
if (id != 0) {
actionBar = (ViewGroup) activity.findViewById(id);
}
if (actionBar == null) {
return findToolbar((ViewGroup) activity.findViewById(android.R.id.content).getRootView());
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
static ViewGroup findToolbar(ViewGroup viewGroup) {
ViewGroup toolbar = null;
for (int i = 0, len = viewGroup.getChildCount(); i < len; i++) {
View view = viewGroup.getChildAt(i);
if (view.getClass() == android.support.v7.widget.Toolbar.class ||
view.getClass().getName().equals("android.widget.Toolbar")) {
toolbar = (ViewGroup) view;
} else if (view instanceof ViewGroup) {
toolbar = findToolbar((ViewGroup) view);
}
if (toolbar != null) {
break;
}
}
return toolbar;
}
Calling getOverflowMenuButton(activity) will return null in onCreate because the overflow menu isn't laid out yet. To get the overflow menu in onCreate I did the following:
findViewById(android.R.id.content).post(new Runnable() {
#Override public void run() {
ImageView overflow = getOverflowMenuButton(MainActivity.this);
}
});
I found a library called TapTarget and a function TapTarget.forToolbarOverflow(). It presents a solution: https://github.com/KeepSafe/TapTargetView/blob/master/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTarget.java#L96
The way how it finds the overflow view is not neat but should be stable.
you want to create custom DropDown menu? consider this "native" way
or use android:showAsAction="never" in your menu.xml. doc of showAsAction attribute HERE. when one of MenuItems have set never value then you will get overflow three-dot icon automatically and these MenuItems will be hidding there
also you may try to use Hierarchy Viewer to investigate this id if really needed
Instead of using expensive and complicated layout traversal to find the overflow menu, I have achieved showing the PopupWindow under the overflow menu by using the Toolbar view as anchor and setting gravity to Gravity.END:
/**
* Sets the anchor view and shows the popup. In case of narrow display the menu items may be hidden in an overflow
* menu, in that case anchorView may be null and the popup will be anchored to the end of the toolbar.
*/
public void show(#Nullable View anchorView, #NonNull View toolbarView) {
if (anchorView == null) {
setDropDownGravity(Gravity.END);
setAnchorView(toolbarView);
} else {
setAnchorView(anchorView);
}
show();
}
As I liked the design from BottomNavigationView I decided to implement a new Menu for my App with it, instead of just using simple buttons.
I took this post as a guideline.
According to BottomNavigationView's documentation, its purpose is to
provide quick navigation between top-level views of an app. It is
primarily designed for use on mobile.
In my case, I just want each MenuItem to launch an activity, but by default there is always one MenuItem selected:
I tried to set the color to white with:
app:itemIconTint="#color/white"
app:itemTextColor="#color/white"
Still, visibly selected MenuItem is different from others (Title size bigger), which is still bothering me:
I came with the idea to place a hidden MenuItem to select like:
<item
android:id="#+id/uncheckedItem"
android:title="" />
and make its view GONE:
bottomNavigationView.getMenu().findItem(R.id.uncheckedItem).setChecked(true);
bottomNavigationView.findViewById(R.id.uncheckedItem).setVisibility(View.GONE);
This makes all MenuItems unchecked, but by default BottomNavigationView is hidding Titles, as it has more than 3 MenuItems to display, even if the fourth MenuItem is settle to GONE:
So my question remains, is there away/hack to unselect all MenuItems and keep its titles being displayed?
mNavigationBottom.getMenu().setGroupCheckable(0, false, true);
To unselect all items I have create this extension:
fun BottomNavigationView.uncheckAllItems() {
menu.setGroupCheckable(0, true, false)
for (i in 0 until menu.size()) {
menu.getItem(i).isChecked = false
}
menu.setGroupCheckable(0, true, true)
}
menu.setGroupCheckable(0, true, false) make it possible.
The third parameter made the menu not exclusive and then within the loop you change the checked status.
To finish set the menu to exclusive again.
Here the doc
Thanks for your idea. I have implement it in my lib.
I have a better way do it by reflect. So it won't show space.
If you have interest. Click here : https://github.com/ittianyu/BottomNavigationViewEx
private void initBottomViewAndLoadFragments(final BottomNavigationViewEx bnve) {
bnve.enableAnimation(false);
bnve.enableShiftingMode(false);
bnve.enableItemShiftingMode(false);
// use the unchecked color for first item
bnve.setIconTintList(0, getResources().getColorStateList(R.color.bnv_unchecked_black));
bnve.setTextTintList(0, getResources().getColorStateList(R.color.bnv_unchecked_black));
bnve.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
private boolean firstClick = true;
private int lastItemId = -1;
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
// restore the color when click
if (firstClick) {
firstClick = false;
bnve.setIconTintList(0, getResources().getColorStateList(R.color.selector_bnv));
bnve.setTextTintList(0, getResources().getColorStateList(R.color.selector_bnv));
}
if (firstClick || lastItemId == -1 || lastItemId != item.getItemId()) {
lastItemId = item.getItemId();
} else {
return false;
}
// do stuff
return fillContent(item.getItemId());
}
});
}
-- res/color/selector_bnv.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/bnv_checked_white" android:state_checked="true" />
<item android:color="#color/bnv_unchecked_black" />
</selector>
-- res/values/colors.xml
<color name="bnv_checked_white">#android:color/white</color>
<color name="bnv_unchecked_black">#android:color/black</color>
Your solution seems change the space between items
There is my solution :
"Just set color of clicked same as color of un-clicked."
for example:
private void changeMenuItemCheckedStateColor(BottomNavigationView bottomNavigationView, String checkedColorHex, String uncheckedColorHex) {
int checkedColor = Color.parseColor(checkedColorHex);
int uncheckedColor = Color.parseColor(uncheckedColorHex);
int[][] states = new int[][] {
new int[] {-android.R.attr.state_checked}, // unchecked
new int[] {android.R.attr.state_checked}, // checked
};
int[] colors = new int[] {
uncheckedColor,
checkedColor
};
ColorStateList colorStateList = new ColorStateList(states, colors);
bottomNavigationView.setItemTextColor(colorStateList);
bottomNavigationView.setItemIconTintList(colorStateList);
}
if you want to un-check all items, you can
changeMenuItemCheckedStateColor(mBottomNavigationView, "#999999", "#999999");
if you want to restore the color setting, you can
changeMenuItemCheckedStateColor(mBottomNavigationView, "FF743A", "999999");
I found my own solution merging my progress with this post.
Steps:
Update proguard-rules.pro and sync build
Create Helper to disable BottomNavigationView Shift Mode
Create an Item to hide on Menu.xml
Inflate BottomNavigationView
Set Item to be hidden as Checked GONE
Use Helper to disable Shifting Mode
Output:
Working code:
proguard-rules.pro:
-keepclassmembers class android.support.design.internal.BottomNavigationMenuView {
boolean mShiftingMode;
}
BottomNavigationShiftHelper.java:
public class BottomNavigationShiftHelper {
private final static String TAG = "DEBUG_BOTTOM_NAV_UTIL";
public static void disableShiftMode(BottomNavigationView view) {
BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
try {
Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
shiftingMode.setAccessible(true);
shiftingMode.setBoolean(menuView, false);
shiftingMode.setAccessible(false);
for (int i = 0; i < menuView.getChildCount(); i++) {
BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
item.setShiftingMode(false);
// set once again checked value, so view will be updated
item.setChecked(item.getItemData().isChecked());
}
} catch (NoSuchFieldException e) {
Log.d(TAG, "Unable to get shift mode field");
} catch (IllegalAccessException e) {
Log.d(TAG, "Unable to change value of shift mode");
}
}
}
Activity Sample.java:
private void loadNavigationBar() {
BottomNavigationView bottomNavigationView = (BottomNavigationView)
findViewById(R.id.navigation_bar);
bottomNavigationView.getMenu().findItem(R.id.uncheckedItem).setChecked(true);
bottomNavigationView.findViewById(R.id.uncheckedItem).setVisibility(View.GONE);
BottomNavigationViewUtils.disableShiftMode(bottomNavigationView);
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.newList:
//Do The Math
break;
case R.id.loadList:
//Do The Math
break;
case R.id.settings:
//Do The Math
break;
}
return false;
}
});
}
BottomNavigationMenu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/newList"
android:enabled="true"
android:icon="#drawable/new_list"
android:title="#string/common.button.list.new"
app:showAsAction="withText" />
<item
android:id="#+id/loadList"
android:enabled="true"
android:icon="#drawable/load"
android:title="#string/common.button.list.load"
app:showAsAction="withText" />
<item
android:id="#+id/settings"
android:enabled="true"
android:icon="#drawable/settings"
android:title="#string/common.label.settings"
app:showAsAction="withText" />
<item
android:id="#+id/uncheckedItem"
android:title="" />
</menu>
BottomNavigationComponent (inside Activity.xml):
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:itemIconTint="#color/white"
app:itemTextColor="#color/white"
android:background="#drawable/BottomNavigationMenu.xml"
app:menu="#menu/supercart_bottom_navigation" />
Try this, it worked for me
<item
android:id="#+id/uncheckedItem"
android:visible="false"
android:title="" />
and set
bottomNavigationView.getMenu().findItem(R.id.uncheckedItem).setChecked(true);
you get all menu item view as unselected; since the selection is given for uncheckedItem which is invisible
Hope it helped you.
There is an exception in accepted answer which we set maximum item we cant implement that code.So I got a code that is more simple than accepted code and it's also working with maximum item.
I referred from here Custom TextSize of BottomNavigationView support android
In your dimen.xml you can put:
<dimen name="design_bottom_navigation_text_size" tools:override="true">10sp</dimen>
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">10sp</dimen>
Doing this you are overriding the default value of dimen that the internal classes of BottomNavigationView use. So be carreful.
Set this code in your onCreate method where the bottom navigation view initialized
mNavigationBottom.getMenu().setGroupCheckable(0, false, true);
and Last set your bottom navigation bar like this :
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
android:fitsSystemWindows="true"
app:labelVisibilityMode="labeled"
app:layout_anchorGravity="fill"
app:menu="#menu/bottom_nav_menu" />
In this set your app:labelVisibilityMode to labeled
This is the same as the accepted answer except I have changed two lines of code marked below. When looping through the BottomNavigationItemViews, I set checked to false always and I also set checkable to false. This prevents the Menu Items from changing size. You still need this proguard rule:
-keepclassmembers class android.support.design.internal.BottomNavigationMenuView {
boolean mShiftingMode;
}
Updated code:
static void removeShiftMode(BottomNavigationView view)
{
BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
try
{
Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
shiftingMode.setAccessible(true);
shiftingMode.setBoolean(menuView, false);
shiftingMode.setAccessible(false);
for (int i = 0; i < menuView.getChildCount(); i++)
{
BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
item.setShiftingMode(false);
item.setChecked(false); // <--- This line changed
item.setCheckable(false); // <-- This line was added
}
}
catch (NoSuchFieldException e)
{
Log.e("ERROR NO SUCH FIELD", "Unable to get shift mode field");
}
catch (IllegalAccessException e)
{
Log.e("ERROR ILLEGAL ALG", "Unable to change value of shift mode");
}
}
The best answer mNavigationBottom.getMenu().setGroupCheckable(0, false, true) did not work for me. It still showed the first item as being selected.
What did work for me is to have an invisible item and to select that, exactly like you did in the question.
Add app:labelVisibilityMode="labeled" to your BottomNavigationView so that all items remain in view with their titles.
if(isDarkMode(context)) {
mBotNavView.setItemIconTintList(ColorStateList.valueOf(Color.parseColor("#FFFFFF")));
} else {
mBotNavView.setItemIconTintList(ColorStateList.valueOf(Color.parseColor("#000000")));
}
public boolean isDarkMode(Context context) {
int nightModeFlags = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return darkMode = (nightModeFlags == Configuration.UI_MODE_NIGHT_YES);
}
I'm trying to understand the total flow of Android applications, and I'm running into what I consider to be a strange situation (note: Im VERY new to android programming).
I made a test application with just a multiline edit text field. I wrote 1234 on the field. Without anything else, with no other changes to the default Eclipse ADT made application and without overriding anything specifically in the backend, I changed orientation. 1234 remained. I then hit the home button, then opened it from recent apps. 1234 remained.
My understanding of the app lifecycle was that the application was stopped and started when the home button is pressed, and the application is destroyed and created when the application's orientation is changed. If this is correct, is there some form of automatic state keeping that takes place? I was under the assumption that I had to pull, from the state bundle, individual variables and restore them myself. Is that not correct?
Any explanation of this that a more seasoned Android dev could give would be great. I've been trying to find the appropriate answer, but to no avail.
MainActivity.java
package com.example.teststate;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
Activity_Main.xml
<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"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.example.teststate.MainActivity" >
<EditText
android:id="#+id/editText1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginTop="30dp"
android:ems="10"
android:inputType="textMultiLine" >
<requestFocus />
</EditText>
</RelativeLayout>
Snippet from Manifest.xml
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Thank you!
The reason this happens is because the view explicitly saves and restores its state on orientation changes, by overriding View#onSaveInstanceState() and View#onRestoreInstanceStance(Parcelable). Here's the of the implementation in TextView (super class of EditText)
#Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
// Save state if we are forced to
boolean save = mFreezesText;
int start = 0;
int end = 0;
if (mText != null) {
start = getSelectionStart();
end = getSelectionEnd();
if (start >= 0 || end >= 0) {
// Or save state if there is a selection
save = true;
}
}
if (save) {
SavedState ss = new SavedState(superState);
// XXX Should also save the current scroll position!
ss.selStart = start;
ss.selEnd = end;
if (mText instanceof Spanned) {
Spannable sp = new SpannableStringBuilder(mText);
if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
}
ss.text = sp;
} else {
ss.text = mText.toString();
}
if (isFocused() && start >= 0 && end >= 0) {
ss.frozenWithFocus = true;
}
ss.error = getError();
return ss;
}
return superState;
}
[...]
#Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
// XXX restore buffer type too, as well as lots of other stuff
if (ss.text != null) {
setText(ss.text);
}
if (ss.selStart >= 0 && ss.selEnd >= 0) {
if (mText instanceof Spannable) {
int len = mText.length();
if (ss.selStart > len || ss.selEnd > len) {
String restored = "";
if (ss.text != null) {
restored = "(restored) ";
}
Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
"/" + ss.selEnd + " out of range for " + restored +
"text " + mText);
} else {
Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
if (ss.frozenWithFocus) {
createEditorIfNeeded();
mEditor.mFrozenWithFocus = true;
}
}
}
}
if (ss.error != null) {
final CharSequence error = ss.error;
// Display the error later, after the first layout pass
post(new Runnable() {
public void run() {
setError(error);
}
});
}
}
As you can see, it saves the state in a Parcelable object, which is then passed along to a view with the same android:id in the new instance, and onRestoreInstanceState() gets called on the new view. If you create a custom view, which doesn't just consists of other views, you might want to override these methods.
I am not able to give you a code level explanation. But it is one of those things android promised to do. You can check out this Recreating activity
Note: In order for the Android system to restore the state of the views in your activity, each view must have a unique ID, supplied by the android:id attribute.
Is there a way to find the position of an icon in the action bar?
I have used the code below:
final View actionBarView = getActivity().getWindow().getDecorView().findViewById(getResources().getIdentifier("action_bar_container", "id", "android"));
if (actionBarView != null) {
final View buttonInActionBar = actionBarView.findViewById(R.id.menu_item);
if (buttonInActionBar != null) {
If, I set action_bar_menu_layout like this:
<item
android:id="#+id/menu_item"
MyApp:actionProviderClass="my_provider"
MyApp:showAsAction="always|withText|collapseActionView"
android:orderInCategory="0"
android:title="#string/item_name"/>
so by using the collapseActionView flag, everything works. But without that flag the view is not found. It looks like only in that case the menu is build by using that id as the view id.
Is there a way to do it?
This is the way I did it.
I used:
mItemView = MenuItemCompat.getActionView(menu.findItem(R.id.menu_item));
then I registered a GlobalLayoutListener and in the listener:
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
mItemView.getLocationInWindow(mMyItemLocation);
if (mItemView == null || mMyItemLocation[0] == 0 || mItemView.getRight() == 0 || mItemView.getWidth() == 0) {
return;
}
mLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}