Drawer item animation when opened - android

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

Related

How to add animation in bottomsheet when changes its state

My app contain a bottomsheeet and i am using following methods to change its state
public void toggleBottomSheet() {
if (sheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
But in some devices it is not showing animation properly ( Bottom to up ), It opens directly like popup.
How to add animation so it looks it is coming from bottom.
Not sure about this point of "in some devices", can you please mention the android version and other specifications? My suggestion is to add a BottomSheetBehavior.BottomSheetCallback to the BottomSheetBehavior
and in its onSlide method put a log.d() to check if its really sliding. It will help to find out the cause. Also, make sure that you have added the right BootmSheetBehavior and you can try by setting the peeckHeight explicitly.
yourBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback()
{
#Override
public void onStateChanged(#NonNull View view, int newState)
{
if (newState == BottomSheetBehavior.STATE_EXPANDED)
{
}
}
#Override
public void onSlide(#NonNull View view, float v)
{
Log.d("tag", "sliding from bottom to top");
}
});

Do action only ONCE when NavigationDrawer is OPENING

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

fab.show() not animated first time after initializing new activity

I am using the floating action button (fab) component from com.android.support:design:23.1.0 Library to generate my app's fabs.
But the first time I load a new activity with fab.hide() and try to make the icon visible through fab.show() after a button was clicked, there is no animation for the fab. This happens only the first time after loading a new activity. When I try that multiple times to hide and show the button, it is animated properly.
What is the issue here? It would be a charm to get it animated also right after an activity is loaded.
Java in activity:
fabSend = (FloatingActionButton) findViewById(R.id.fabSend);
fabSend.hide();
CompoundButton.OnCheckedChangeListener changeChecker = new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked){
// FAB on
fabSend.show();
} else {
// FAB off
fabSend.hide();
}
}
};
Layout.xml
<android.support.design.widget.FloatingActionButton
android:id="#+id/fabSend"
app:borderWidth="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_alignParentBottom="true"
android:layout_marginRight="#dimen/fab_margin"
android:layout_marginBottom="54dp"
android:src="#drawable/ic_check_white_24dp" />
I've had the same problem. In my fab xml I had visibility="gone", than I tried to show fab from the code by fab.show() - and animation was not working the first time. I've changed xml to visibility="invisible" and problem was solved.
Solved this one finally. I designed a new class to handle the reveal animation with a delay. Grab it here, initialize it and you're good to go. I found a pretty similar animation to the standard fab.show() at 50ms delay on it.
public static void showFabWithAnimation(final FloatingActionButton fab, final int delay) {
fab.setVisibility(View.INVISIBLE);
fab.setScaleX(0.0F);
fab.setScaleY(0.0F);
fab.setAlpha(0.0F);
fab.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
fab.getViewTreeObserver().removeOnPreDrawListener(this);
fab.postDelayed(new Runnable() {
#Override
public void run() {
fab.show();
}
}, delay);
return true;
}
});
}
The best way to achieve that is by simply setting your Fab's scaleX and scaleY to zero in XML. It is the easiest method and it also leaves your application code clean.
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleX="0"
android:scaleY="0"
android:visibility="invisible"/>
According to docs:
android.support.design.widget.FloatingActionButton
public void show()
Shows the button. This method will animate the button show if the
view has already been laid out.
So, to make it animate the first time you can write you own animation to animate it when it's not currently laid out
/**
* Unlike {#link FloatingActionButton#show()} animates button even it not currently
* laid out
* #param fab fab to show
*/
#SuppressLint("NewApi")
public static void show(FloatingActionButton fab) {
if (ViewCompat.isLaidOut(fab) ||
Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
fab.show();
} else {
fab.animate().cancel();//cancel all animations
fab.setScaleX(0f);
fab.setScaleY(0f);
fab.setAlpha(0f);
fab.setVisibility(View.VISIBLE);
//values from support lib source code
fab.animate().setDuration(200).scaleX(1).scaleY(1).alpha(1)
.setInterpolator(new LinearOutSlowInInterpolator());
}
}

Hide navigation drawer when user presses back button

I've followed Google's official developer tutorials here to create a navigation drawer.
At the moment, everything works fine, except for when the user uses the native back button Android provides at the bottom of the screen (along with the home and recent app buttons). If the user navigates back using this native back button, the navigation drawer will still be open. If the user instead navigates back using the ActionBar, the navigation drawer will be closed like I want it to be.
My code is nearly identical to the official tutorials, except for how I handle the user selecting an item on the drawer:
mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView parent, View view, int position, long id)
{
switch(position)
{
case 0:
{
Intent intent = new Intent(MainActivity.this, NextActivity.class);
startActivity(intent);
}
}
}
});
How can I have the navigation drawer be closed when the user navigates back using the native back button? Any advice appreciated. Thanks!
You have to override onBackPressed(). From the docs :
Called when the activity has detected the user's press of the back
key. The default implementation simply finishes the current activity,
but you can override this to do whatever you want.
So you can have code like this :
#Override
public void onBackPressed() {
if (this.drawerLayout.isDrawerOpen(GravityCompat.START)) {
this.drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
If is open this method closes it, else falls back to the default behavior.
You need to override onBackPressed() in your activity and check for the condition where the navigation drawer is open. If it is open, then close it, else do a normal back pressed method. Here is some code mixed with some pseudocode to help you:
#Override
public void onBackPressed(){
if(drawer.isDrawerOpen()){ //replace this with actual function which returns if the drawer is open
drawer.close(); // replace this with actual function which closes drawer
}
else{
super.onBackPressed();
}
}
To replace the pseudocode look in the documentation for the drawer. I know both those methods exist.
Here is an alternative solution to your problem.
#Override
public void onBackPressed(){
if(drawerLayout.isDrawerOpen(navigationView)){
drawerLayout.closeDrawer(navigationView);
}else {
finish();
}
}
UPDATE:
As of support library 24.0.0 this is possible without any workarounds. Two new openDrawer and closeDrawer methods have been added to DrawerLayout that allow the drawer to be opened or closed with no animation.
You can now use openDrawer(drawerView, false) and closeDrawer(drawerView, false) to open and close the drawer with no delay.
If you call startActivity() without calling closeDrawer(), the drawer will be left open in that instance of the activity when you navigate back to it using the back button. Calling closeDrawer() when you call startActivity() has several issues, ranging from choppy animation to a long perceptual delay, depending on which workaround you use. So I agree the best approach is to just call startActivity() and then close the drawer upon return.
To make this work nicely, you need a way to close the drawer without a close animation when navigating back to the activity with the back button. (A relatively wasteful workaround would be to just force the activity to recreate() when navigating back, but it's possible to solve this without doing that.)
You also need to make sure you only close the drawer if you're returning after navigating, and not after an orientation change, but that's easy.
Details
(You can skip past this explanation if you just want to see the code.)
Although calling closeDrawer() from onCreate() will make the drawer start out closed without any animation, the same is not true from onResume(). Calling closeDrawer() from onResume() will close the drawer with an animation that is momentarily visible to the user. DrawerLayout doesn't provide any method to close the drawer without that animation, but it's possible to extend it in order to add one.
Closing the drawer actually just slides it off the screen, so you can effectively skip the animation by moving the drawer directly to its "closed" position. The translation direction will vary according to the gravity (whether it's a left or right drawer), and the exact position depends on the size of the drawer once it's laid out with all its children.
However, simply moving it isn't quite enough, as DrawerLayout keeps some internal state in extended LayoutParams that it uses to know whether the drawer is open. If you just move the drawer off screen, it won't know that it's closed, and that will cause other problems. (For example, the drawer will reappear on the next orientation change.)
Since you're compiling the support library into your app, you can create a class in the android.support.v4.widget package to gain access to its default (package-private) parts, or extend DrawerLayout without copying over any of the other classes it needs. This will also reduce the burden of updating your code with future changes to the support library. (It's always best to insulate your code from implementation details as much as possible.) You can use moveDrawerToOffset() to move the drawer, and set the LayoutParams so it will know that the drawer is closed.
Code
This is the code that'll skip the animation:
// move drawer directly to the closed position
moveDrawerToOffset(drawerView, 0.f);
// set internal state so DrawerLayout knows it's closed
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
invalidate();
Note: if you just call moveDrawerToOffset() without changing the LayoutParams, the drawer will move back to its open position on the next orientation change.
Option 1 (use existing DrawerLayout)
This approach adds a utility class to the support.v4 package to gain access to the package-private parts we need inside DrawerLayout.
Place this class into /src/android/support/v4/widget/:
package android.support.v4.widget;
import android.support.annotation.IntDef;
import android.support.v4.view.GravityCompat;
import android.view.Gravity;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class Support4Widget {
/** #hide */
#IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
#Retention(RetentionPolicy.SOURCE)
private #interface EdgeGravity {}
public static void setDrawerClosed(DrawerLayout drawerLayout, #EdgeGravity int gravity) {
final View drawerView = drawerLayout.findDrawerWithGravity(gravity);
if (drawerView == null) {
throw new IllegalArgumentException("No drawer view found with gravity " +
DrawerLayout.gravityToString(gravity));
}
// move drawer directly to the closed position
drawerLayout.moveDrawerToOffset(drawerView, 0.f);
// set internal state so DrawerLayout knows it's closed
final DrawerLayout.LayoutParams lp = (DrawerLayout.LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
drawerLayout.invalidate();
}
}
Set a boolean in your activity when you navigate away, indicating the drawer should be closed:
public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER";
private boolean mCloseNavDrawer;
#Override
public void onCreate(Bundle savedInstanceState) {
// ...
if (savedInstanceState != null) {
mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER);
}
}
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
// ...
startActivity(intent);
mCloseNavDrawer = true;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer);
super.onSaveInstanceState(savedInstanceState);
}
...and use the setDrawerClosed() method to shut the drawer in onResume() with no animation:
#Overrid6e
protected void onResume() {
super.onResume();
if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
Support4Widget.setDrawerClosed(mDrawerLayout, GravityCompat.START);
mCloseNavDrawer = false;
}
}
Option 2 (extend from DrawerLayout)
This approach extends DrawerLayout to add a setDrawerClosed() method.
Place this class into /src/android/support/v4/widget/:
package android.support.v4.widget;
import android.content.Context;
import android.support.annotation.IntDef;
import android.support.v4.view.GravityCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class CustomDrawerLayout extends DrawerLayout {
/** #hide */
#IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
#Retention(RetentionPolicy.SOURCE)
private #interface EdgeGravity {}
public CustomDrawerLayout(Context context) {
super(context);
}
public CustomDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setDrawerClosed(View drawerView) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
// move drawer directly to the closed position
moveDrawerToOffset(drawerView, 0.f);
// set internal state so DrawerLayout knows it's closed
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
invalidate();
}
public void setDrawerClosed(#EdgeGravity int gravity) {
final View drawerView = findDrawerWithGravity(gravity);
if (drawerView == null) {
throw new IllegalArgumentException("No drawer view found with gravity " +
gravityToString(gravity));
}
// move drawer directly to the closed position
moveDrawerToOffset(drawerView, 0.f);
// set internal state so DrawerLayout knows it's closed
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
invalidate();
}
}
Use CustomDrawerLayout instead of DrawerLayout in your activity layouts:
<android.support.v4.widget.CustomDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
...and set a boolean in your activity when you navigate away, indicating the drawer should be closed:
public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER";
private boolean mCloseNavDrawer;
#Override
public void onCreate(Bundle savedInstanceState) {
// ...
if (savedInstanceState != null) {
mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER);
}
}
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
// ...
startActivity(intent);
mCloseNavDrawer = true;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer);
super.onSaveInstanceState(savedInstanceState);
}
...and use the setDrawerClosed() method to shut the drawer in onResume() with no animation:
#Overrid6e
protected void onResume() {
super.onResume();
if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.setDrawerClosed(GravityCompat.START);
mCloseNavDrawer = false;
}
}
Using an implementation of the answer provided by #James Cross worked, but the animation to close the drawer was undesirable and unfixable without much hassle, example.
#Override
public void onResume()
{
super.onResume();
mDrawerLayout.closeDrawers();
}
A work-around is to restart the activity when the device back button is pressed. It does not seem ideal to me, but it works. Overriding onBackPressed(), as suggested by #mt0s and #Qazi Ahmed and passing an extra to determine the calling activity:
mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView parent, View view, int position, long id)
{
switch(position)
{
case 0:
{
Intent intent = new Intent(MainActivity.this, NextActivity.class);
//pass int extra to determine calling activity
intent.putExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY);
startActivity(intent);
}
}
}
});
In NextActivity.class, check for the calling activity:
#Override
public void onBackPressed()
{
int callingActivity = getIntent().getIntExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY);
switch(callingActivity)
{
case CallingActivityInterface.MAIN_ACTIVITY:
{
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}
...
}
}
This way the drawer is closed with no animation when I return to MainActivity regardless of whether I use the up button or the back button. There are probably better ways to do this. My app is relatively simple at the moment and this works, but I await a more effective method if anyone has one.
Why the hassle? Simply close the Drawer when clicking a drawer item. That's how it's done in the official Google Play app.
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
drawerLayout.closeDrawer(GravityCompat.START, false);
selectItem(position);
}
}
You will probably want to make sure the navigation draw is always closed when the activity is opened. Use this to do that:
#Override
public void onResume(){
mDrawerList.closeDrawer(Gravity.LEFT);
}
simple sample:
Drawer resultDrawer;
public void onBackPressed(){
if (this.resultDrawer.isDrawerOpen()) {
this.resultDrawer.closeDrawer();
} else {
super.onBackPressed();
}
}
With androidx.drawerlayout:drawerlayout:1.1.0 or higher, you can keep it simple using isOpen and close().
// YourActivity.kt
override fun onBackPressed() {
if (drawerLayout.isOpen) {
drawerLayout.close()
} else {
super.onBackPressed()
}
}
This how i did it:
#Override
public void onBackPressed() {
if(drawerLayout.isDrawerOpen(navigationView)){
drawerLayout.closeDrawer(Gravity.LEFT);
}else{
super.onBackPressed();
}
}
JETPACK COMPOSE
For someone that using jetpack compose.
use this code in your scaffold:
BackHandler(enabled = drawerState.isOpen) {
scope.launch { drawerState.close() }
}
complete version:
val scope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
Scaffold(
topBar = {},
bottomBar = {},
snackbarHost = {},
content = {
...
BackHandler(enabled = drawerState.isOpen) {
scope.launch { drawerState.close() }
}
},
...
)

Remove Item + Scroll to Next in Android ViewPager

I have an Activity containing a ViewPager that displays N fragments. Each fragment is showing the properties of an object from an ArrayList in my ViewPager's custom adapter (extends FragmentStatePagerAdapter).
The fragment has (among other things) a button that should remove the currently displayed fragment and scroll to the next one with setCurrentItem(position, true) so that if the user scrolls back, the previous item is gone. I do so by using something like this (simplified):
deleteButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
MyActivity parentActivity = (MyActivity)getActivity();
// First, scroll to next item (smoothly)
parentActivity.pager.setCurrentItem(parentActivity.pager.getCurrentItem()+1, true);
// Database stuff...
doSomeDBOperations();
// Method in Activity that removes the current object (I believe this method is working fine and yes, it calls notifyDataSetChanged())
parent.removeObject(currentObject);
}
});
This has the desired behavior as the object represented by the fragment whose delete button was pressed gets removed and the viewpager goes to the next page.
My problem is that the ViewPager doesn't scroll smoothly but rather "jumps instantly" to the next fragment. If I comment the removeObject() call, the smooth scroll works (but the item isn't removed). I believe it's has something to do with the removeObject() being called before the setCurrentItem() has finished the smooth scrolling animation?
Any ideas on how to fix this and achieve item removal + smooth scroll? If my assumption is correct, how can I make sure I get the smooth scroll to finish before removing the object?
EDIT 1:
My assumption seems correct. If I put the parent.removeObject(currentObject) inside
// ...inside the previously shown public void onClick(View v)...
confirm.postDelayed(new Runnable() {
#Override
public void run() {
// Method in Activity that removes the current object (I believe this method is working fine and yes, it calls notifyDataSetChanged())
parent.removeObject(currentObject);
}
}, 1000);
so that the removeObject() call waits for a second, it works as expected: scroll to the next item, remove the previous. But this is a very ugly workaround so I'd still like a better approach.
EDIT 2:
I figured out a possible solution (see below).
I ended up overriding the
public void onPageScrollStateChanged(int state)
method:
Whenever the user presses the delete button in the fragment, the listener sets a bool in the current item (flagging it for deletion) and scrolls to the next one.
When the onPageScrollStateChanged detects that the scroll state changed to ViewPager.SCROLL_STATE_IDLE (which happens when the smooth scroll ends) it checks if the previous item was marked for deletion and, if so, removes it from the ArrayList and calls notifyDataSetChanged().
By doing so, I've managed to get the ViewPager to smoothly scroll to the next position and delete the previous item when the "delete" button is pressed.
EDIT: Code snippet.
#Override
public void onPageScrollStateChanged(int state)
{
switch(state)
{
case ViewPager.SCROLL_STATE_DRAGGING:
break;
case ViewPager.SCROLL_STATE_IDLE:
int previousPosition = currentPosition - 1;
if(previousPosition < 0){
previousPosition = 0;
}
MyItem previousItem = itemList.get(previousPosition);
if(previousItem.isDeleted())
{
deleteItem(previousItem);
// deleteItem() Does some DB operations, then calls itemList.remove(position) and notifyDataSetChanged()
}
break;
case ViewPager.SCROLL_STATE_SETTLING:
break;
}
}
Have you tried ViewPager.OnPageChangeListener?
I would call removeObject(n) method in OnPageChangeListener.onPageSelected(n+1) method.
I did something different that works smoothly. The idea is to to remove the current item with animation (setting its alpha to 0), then translating horizontally the left or right item (with animation) to the now invisible item position.
After the animation is complete, I do the actual data removal and notfyDataSetChanged() call.
This remove() method I put inside a subclass of ViewPager
public void remove(int position, OnViewRemovedListener onViewRemovedListener) {
final int childCount = getChildCount();
if (childCount > 0) {
View toRemove = getChildAt(position);
int to = toRemove.getLeft();
final PagerAdapter adapter = getAdapter();
toRemove.animate()
.alpha(0)
.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime))
.setListener(new SimpleAnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
if (childCount == 1) {
if (onViewRemovedListener != null) onViewRemovedListener.onRemoved(position, -1);
if (adapter!= null) adapter.notifyDataSetChanged();
}
}
})
.start();
if (childCount > 1) {
int newPosition = position + 1 <= childCount - 1 ? position + 1 : position - 1;
View replacement = getChildAt(newPosition);
int from = replacement.getLeft();
replacement.animate()
.setInterpolator(new DecelerateInterpolator())
.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime))
.translationX(to - from)
.setListener(new SimpleAnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
if (onViewRemovedListener != null) onViewRemovedListener.onRemoved(position, newPosition);
if (adapter!= null) adapter.notifyDataSetChanged();
}
})
.start();
}
}
}
public interface OnViewRemovedListener {
void onRemoved(int position, int newPosition);
}

Categories

Resources