I've been looking a solution for this quite some time. So this is what I'm trying to achieve. I have a NavigationDrawer implemented with the following menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="#+id/nav_dashboard"
android:icon="#drawable/ic_dashboard"
android:title="Dashboard" />
<item
android:id="#+id/nav_logbook"
android:icon="#drawable/ic_logbook"
android:title="Logbook" />
<item
android:id="#+id/nav_context"
android:icon="#drawable/ic_context"
android:title="Context" />
</group>
</menu>
When there's new context to be added, I want to show this to the user in the navigation drawer by adding the amount of new updates between brackets. Like this for example: Context (2) when there are two new updates.
I want to check this every time when the Drawer opens, but preferably only once, because there's a pretty heavy SQL query behind it. I know I can use the onDrawerOpened() and onDrawerClosed(), but I don't like this solution because of the fact the number will be updated suddenly only when the drawer is completely open. So it will stay one number, until it's completely open, by which it then will change. This just looks silly. So that's why I would want to change it once when the navigation drawer starts opening. I found no decent states to check this with the listener, since there is only STATE_IDLE, STATE_DRAGGING or STATE_SETTLING and nothing specifing for when it's opening and closing, just dragging and settling.
Additionally I also would like to keep the title of the fragment Context to stay "Context" and not also change to "Context (2)". Which happens in my current code:
mToggle = new ActionBarDrawerToggle(
this,
mDrawer,
mToolbar,
R.string.drawer_open,
R.string.drawer_close
){
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
if (slideOffset != 0) {
Log.i(TAG, "onDrawerSlide() in update amount");
int amount = db.getAmountNoContext();
String sourcestring = "Context <b>(" + amount + ")</b>";
mNavigationView.getMenu().getItem(2).setTitle(Html.fromHtml(sourcestring));
}
}
};
This is working for changing the menu title in the Navigation drawer, but it also gets called many times in a row, because the offset keeps changing. And this is because of the SQL query not ideal. I also tried to set it to a specific float value like 0.01, but it's not getting triggered. Also I would like to remind that I would like to change the menu title back when the navigation drawer is closed, so the fragment title in the ActionBar just says "Context". I've done my research, but it seems like I just have a really specific problem? Maybe one of you guys have an idea. Thanks in advance.
I "solved" my problem for now by simply using a boolean to keep track if the SQL query is already exectued. If so the boolean is set to true and the condition will not be entered again until the drawer is completely closed by using the onDrawerClosed() function. I'll do it like this until maybe someone else comes up with a better solution. My solution looks now like this:
mToggle = new ActionBarDrawerToggle(
this,
mDrawer,
mToolbar,
R.string.drawer_open,
R.string.drawer_close
){
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
if (slideOffset != 0 && !contextUpdated) {
int amount = db.getAmountNoContext();
String sourcestring = "Context (" + amount + ")";
NavigationView.getMenu().getItem(2).setTitle(Html.fromHtml(sourcestring));
contextUpdated = true;
}
}
#Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
contextUpdated = false;
}
};
For changing the title of the action bar back to the regular title without brackets. I just manually set the title in the onCreateView() of the fragment by doing this:
MainActivity:
public void setActionBarTitle(String title) {
getSupportActionBar().setTitle(title);
}
Fragment:
((MainActivity) getActivity()).setActionBarTitle("Context");
Related
i've a problem in my Android app.
I have a fragment that is generated as a list of editable textview. Every textview has a setOnFocusChangeListener that calls an API on server.
FocusChangedListener works correctly on last textview, and after that my textview remain focused as you can see in the figure below.
Problem
Now i have to change menu (fragment) by clicking on the right menu button.
When button is clicked and the new menu is about to be loaded, my textview loses focus and calls the API, but i don't want my app do that. If I click menu button is because i've to change it and i expect that my old fragment disappear or basically don't execute the FocusChangedListener.
Any ideas?
I made a little trick to solve the problem.
I just added this code inside OnCreate of my DrawerMenu and then i set a Global Variable "drawerOpened" to check every time if my Menu is open or not.
// Initialize the action bar drawer toggle instance
val drawerToggle:ActionBarDrawerToggle = object : ActionBarDrawerToggle(
this,
drawer_layout,
toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
){
override fun onDrawerClosed(view:View){
super.onDrawerClosed(view)
GlobalVar.drawerOpened = false
}
override fun onDrawerOpened(drawerView: View){
super.onDrawerOpened(drawerView)
GlobalVar.drawerOpened = true
currentFocus?.clearFocus() //clear focus - any view having focus
}
}
Then in my HomeFragment i did this:
// if GlobalVar.drawerOpened is false then setOnFocus
if (field.validatefield || field.nextpage != 0)
{
textView.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus && !GlobalVar.drawerOpened)
{
if ((field.mandatory && !textView.text.toString().isNullOrEmpty()) || (!field.mandatory)) {
if (field.validatefield) {
validateField(field.fieldcode, textView.text.toString(), field.nextpage)
} else {
_goToNextPageNo = field.nextpage
goToNextPageIfExist()
}
}
}
}
}
This is not a complete solution, but it works fine.
I assume that after a click, the application switches to touch mode and your EditText loses focus.
It happens at the system level.
A focus can have only one view at a time.
https://android-developers.googleblog.com/2008/12/touch-mode.html
I've created a drawer using a DrawerLayout which contains a RecyclerView with the items. I've also attached a layoutAnimation to the RecyclerView to have the items come in from the side when opening the drawer. This works peachy the first time but when opening the drawer the second time everything is already in place. I would like the layoutAnimation to run every time the drawer is opened.
What I've tried so far is to have a custom ActionBarDrawerToggle (I need that one anyway), and add the following:
#Override
public void onDrawerOpened(final View drawerView) {
super.onDrawerOpened(drawerView);
final RecyclerView recyclerView =
(RecyclerView) drawerView.findViewById(R.id.drawer_content);
if (recyclerView != null) {
recyclerView.startLayoutAnimation();
}
}
It works sort of, because it re-runs the animation, however all the items are there when opening the drawer, then they disappear and then the animations starts.
Anyone have a solution how to "reset" the drawer item views every time the drawer is closed?
Not sure these are needed but I'll include them anyway
<--! layout_animation.xml -->
<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="#anim/slide_from_right"
android:delay="15%"
android:animationOrder="normal"
/>
<--! slide_from_right.xml -->
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%p"
android:interpolator="#android:anim/decelerate_interpolator"
android:toXDelta="0"
/>
I found the solution after some more testing, perhaps not the prettiest solution but it works. By hiding the content when the drawer is closed and then making it visible again just before starting the animation solves the issue I was having:
private boolean mFirstDrawerOpen = true;
private boolean mAnimationScheduled;
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
// The framework handles the first animation
if (mIsFirstDrawerOpen) {
mIsFirstDrawerOpen = false;
return;
}
final RecyclerView recyclerView =
(RecyclerView) drawerView.findViewById(R.id.drawer_content);
if (mAnimationScheduled && recyclerView != null) {
recyclerView.setVisibility(View.VISIBLE);
recyclerView.startLayoutAnimation();
mAnimationScheduled = false;
} else if (slideOffset == 0f) {
// Handles the case when the drawer is not completly opened and then closed,
// which does not trigger onDrawerClosed()
mAnimationScheduled = true;
}
}
#Override
public void onDrawerOpened(final View drawerView) {
super.onDrawerOpened(drawerView);
mAnimationScheduled = false;
}
#Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
mAnimationScheduled = true;
final RecyclerView recyclerView =
(RecyclerView) drawerView.findViewById(R.id.drawer_content);
if (recyclerView != null) {
recyclerView.setVisibility(View.INVISIBLE);
}
}
Update:
The previous answer did not handle the case where the drawer is dragged halfway open and then closed, since onDrawerClosed is not called if the drawer haven't been fully opened. To solve that, I moved most of the code from onDrawerOpen to onDrawerSlide() and modify it a bit.
I was having the same problem as #patrick-iv and was wondering how other people solved it. I came up with adding the below code to the onDrawerStateChanged listener.
boolean drawerOpen = drawer.isDrawerOpen(GravityCompat.START);
_drawerTopMenu.setVisibility(drawerOpen ? View.VISIBLE : View.INVISIBLE);
if (newState == DrawerLayout.STATE_SETTLING && !drawerOpen)
_drawerTopMenu.startLayoutAnimation();
This is a short question:
I'm trying to force the action bar (used by a Toolbar) to use LTR alignment. I've succeeded making the layout itself use LTR, but not the "up" button (as I've done here, before Toolbar was introduced) .
It seems this view doesn't have an ID, and I think using getChildAt() is too risky.
Can anyone help?
The answer
Here's one way I've found to solve this, based on this answer .
I made it so that it is guarranteed to find only the "up" button, and whatever it does, it will revert back to the previous state it was before.
Here's the code:
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
public boolean onCreateOptionsMenu(final Menu menu)
{
// <= do the normal stuff of action bar menu preparetions
if(VERSION.SDK_INT>=VERSION_CODES.JELLY_BEAN_MR1&&getResources().getConfiguration().getLayoutDirection()==View.LAYOUT_DIRECTION_RTL)
{
final ArrayList<View> outViews=new ArrayList<>();
final CharSequence previousDesc=_toolbar.getNavigationContentDescription();
for(int id=0;;++id)
{
final String uniqueContentDescription=Integer.toString(id);
_toolbar.findViewsWithText(outViews,uniqueContentDescription,View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
if(!outViews.isEmpty())
continue;
_toolbar.setNavigationContentDescription(uniqueContentDescription);
_toolbar.findViewsWithText(outViews,uniqueContentDescription,View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
if (outViews.isEmpty())
if (BuildConfig.DEBUG)
throw new RuntimeException(
"You should call this function only when the toolbar already has views");
else
break;
outViews.get(0).setRotation(180f);
break;
}
_toolbar.setNavigationContentDescription(previousDesc);
}
//
return super.onCreateOptionsMenu(menu);
}
It seems this view doesn't have an ID
You're right, the navigation view is created programmatically and never sets an id. But you can still find it by using View.findViewsWithText.
View.findViewsWithText comes with two flags:
View.FIND_VIEWS_WITH_TEXT
View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
The navigation view's default content description is "Navigate up" or the resource id is abc_action_bar_up_description for AppCompat and action_bar_up_description for the framework's, but you can easily apply your own using Toolbar.setNavigationContentDescription.
Here's an example implementation:
final Toolbar toolbar = ...;
toolbar.setNavigationContentDescription("up");
setActionBar(toolbar);
final ArrayList<View> outViews = Lists.newArrayList();
toolbar.findViewsWithText(outViews, "up", View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
outViews.get(0).setRotation(180f);
Results
Has anyone been able to pull this off. UiScrollable, swipeLeft and swipeRight do not appear to have any affect on it. I am using a Nexus 5 emulator with the latest api. Has anyone been able to pull it off?
TL;DR: Use the content descriptions set in the ActionBarDrawerToggle constructor
When you set up your Navigation Drawer, set an ActionBarDrawerToggle with content descriptions for opening and closing.
// Open drawer content description for accessibility
private static final String CONTENT_DESCRIPTION_OPEN_DRAWER = "Open drawer";
// Close drawer content description for accessibility
private static final String CONTENT_DESCRIPTION_CLOSE_DRAWER = "Close drawer";
private void setUpNavigationDrawer() {
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, CONTENT_DESCRIPTION_OPEN_DRAWER, CONTENT_DESCRIPTION_CLOSE_DRAWER);
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
In your UiAutomatorTestCase, to open/close the drawer, find the UI object by the open/close content description defined in step 1 and perform a click on it.
// Open drawer content description for accessibility
private static final String CONTENT_DESCRIPTION_OPEN_DRAWER = "Open drawer";
// Close drawer content description for accessibility
private static final String CONTENT_DESCRIPTION_CLOSE_DRAWER = "Close drawer";
private void openNavDrawer() throws UiObjectNotFoundException {
findViewByContentDescription(CONTENT_DESCRIPTION_OPEN_DRAWER).click();
}
private void closeNavDrawer() throws UiObjectNotFoundException {
findViewByContentDescription(CONTENT_DESCRIPTION_CLOSE_DRAWER).click();
}
private UiObject findViewByContentDescription(String description) {
return new UiObject(new UiSelector().description(description));
}
Warning: If you use the Material design approach for the navigation drawer (where the drawer opens on top of the Topbar and behind the status bar), the "hamburger" icon will be drawn behind the drawer, making the closeDrawer() method not to work. As a workaround, you can just select the section that is open in the drawer menu; this closes the drawer and shows the same section that was there before opening it.
You could try this:
UiObject view = new UiObject(new UiSelector()."use what you want to identify the view");
Rect bounds = view.getBounds(); // Returns the bounds of the view as (left, top, right, bottom)
int center_x = bounds.centerX();
int center_y = bounds.centerY();
// Now you have the center of the view. You can just slide by using drag()
getUiDevice().drag(center_x, center_y, center_x + "amount of pixels", center_y)
// Where amount of pixels can be a variable set to a fraction of the screen width
I used this idea on a drawer that slides from left to right on a file browser app.
I did not use the exact same code as above because I'm using a python wrapper for uiautomator. (https://github.com/xiaocong/uiautomator)
I have built an Android app using the AppCompat navigation drawers (one on the left and one on the right). My questions is whether and if so, how, one would go about placing one of the "up" buttons on the title bar, so each drawer has its own button (one on each side of the title bar). I have the following code:
new DrawerToggle(this, getLeftDrawerLayout(), R.drawable.imgur_drawer_list_button_shape_left, R.string.open_drawer, R.string.close_drawer, getLeftDrawerListView(), LeftDrawerDrawerArrayAdapter);
new DrawerToggle(this, getRightDrawerLayout(), R.drawable.imgur_drawer_list_button_shape_right, R.string.open_drawer, R.string.close_drawer, getRightDrawerListView(), RightDrawerDrawerArrayAdapter);
where DrawerToggle extends android.support.v4.app.ActionBarDrawerToggle and R.drawable.imgur_drawer_list_button_shape_left as well as R.drawable.imgur_drawer_list_button_shape_right are in the drawable resource directory as required.
Also, the following code is handled in the activity onCreate method:
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
Unfortunately this only produces an application with solely the left-hand drawer "up" button icon, which reacts (i.e. slides in) to both, the left and right drawers...
Can anyone please help and provide a way to have the buttons on either sides of the title bar, each corresponding to its respective drawer?
Thank you sincerely! :)
Piotr.
Having toggles at both sides is never stated in the design guidelines. There is a hacky way to do this, it is real mess, but give it a try.
This includes overriding the functionality of the overflow menu item, which means you will never have it working as it supposed to.
Add an menu item that has showAsAction="never" to make sure you have always a overflow menu item.
Style it as your heart desires
Get View reference of the overflow button as following:
public static View getOverflowButton(Activity a){
View homeButton = a.findViewById(android.R.id.home);
ViewParent parentOfHome = homeButton.getParent().getParent(); //ActionBarView is parent of home ImageView, see layout file in sources
try{
parentOfHome = parentOfHome.getParent();//get to ActionBarView;
Class absActionBarView = parentOfHome.getClass().getSuperclass(); //ActionBarView -> AbsActionBarView class
Field menuPresenter = absActionBarView.getDeclaredField("mActionMenuPresenter"); // ActionMenuPresenter is the object that calls openOverflowMenu() closeOverflowMenu()
menuPresenter.setAccessible(true); // and contains the overflow button view.
Object menuPresenterView = menuPresenter.get(parentOfHome);
Field overflowField = menuPresenterView.getClass().getDeclaredField("mOverflowButton");
overflowField.setAccessible(true);
View overFlowButtonView = (View) overflowField.get(menuPresenterView);
return overFlowButtonView;
}
catch(NoSuchFieldException e){
Log.e("getOverflowButton()", e.getMessage());
} catch (IllegalArgumentException e) {
Log.e("getOverflowButton()", e.getMessage());
} catch (IllegalAccessException e) {
Log.e("getOverflowButton()", e.getMessage());
}
return null;
}
Add OnClickListener to the view reference (anonymous declaration or field). Trivial.
Toggle the drawer in the onClick()
Use onDrawerSlide(View drawerView, float slideOffset) to animate the drawable as it is animated in the ActionBarDrawerToggle. Basically, it is off-screened-drawable by half and it is moved back and forth by some factor made up from the slideOffset.