I'm using CollapsingToolbarLayout in my application in Android. My app's minimum requirement API is 9.
I need the collapsed toolbar to be expanded when the user clicks in the collapsed one, just like in latest Gmail Calendar's app. So I set an onClickListener and inside it I do the following:
public void onClick(View v) {
if(toolbarExpanded) {
mAppBar.setExpanded(false, true);
} else {
mAppBar.setExpanded(true, true);
}
toolbarExpanded = !toolbarExpanded;
}
Which is working quite well but my problem is that the animation that it's running is slow, meaning a bad user experience.
Is there anyway to change the duration or to define a custom animation for this?
Thank you in advance.
Note: this answer is based on android design library v25.0.0.
You can call the private method animateOffsetTo of the AppBarLayout.Behavior of your NestedScrollView with reflection. This method has a velocity parameter that has an impact on the animation duration.
private void expandAppBarLayoutWithVelocity(AppBarLayout.Behavior behavior, CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout, float velocity) {
try {
//With reflection, we can call the private method of Behavior that expands the AppBarLayout with specified velocity
Method animateOffsetTo = AppBarLayout.Behavior.getClass().getDeclaredMethod("animateOffsetTo", CoordinatorLayout.class, AppBarLayout.class, int.class, float.class);
animateOffsetTo.setAccessible(true);
animateOffsetTo.invoke(behavior, coordinatorLayout, appBarLayout, 0, velocity);
} catch (Exception e) {
e.printStackTrace();
//If the reflection fails, we fall back to the public method setExpanded that expands the AppBarLayout with a fixed velocity
Log.e(TAG, "Failed to get animateOffsetTo method from AppBarLayout.Behavior through reflection. Falling back to setExpanded.");
appBarLayout.setExpanded(true, true);
}
}
To get the Behavior, you need to fetch it from the LayoutParams of your AppBarLayout.
AppBarLayout appBarLayout = (AppBarLayout)findViewById(R.id.app_bar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = params.getBehavior();
To expand with animation use
AppBarLayout.setExpanded(true,true);
To Collapse with animation use
AppBarLayout.setExpanded(false,true);
Related
I currently have a bottom navigation bar inside a Coordinator layout, which I added a HideBottomViewOnScrollBehaviour to. Some screens require the navigation bar to be hidden, which I can achieve by calling the slideUp / slideDown methods from the behaviour object of BottomNavigationBar layout params.
The issue is, even if i'm hiding it programatically, you can reveal it by simply scrolling up again.
I didn't find any solutions, i was thinking there will be something like disabling the behaviour and enabling it on certain screens, but that's not a thing.
Any solutions?
Thanks!
Here are methods to disable/enable scrolling behaviour:
fun enableLayoutBehaviour() {
val params = navView?.layoutParams as CoordinatorLayout.LayoutParams
if (params.behavior == null) {
params.behavior = HideBottomViewOnScrollBehavior<View>()
}
navView?.let {
(params.behavior as HideBottomViewOnScrollBehavior).slideUp(it)
}
}
fun disableLayoutBehaviour() {
val params = navView?.layoutParams as CoordinatorLayout.LayoutParams
navView?.let {
(params.behavior as HideBottomViewOnScrollBehavior).slideDown(it)
}
params.behavior = null
}
You can also just replace NestedScrollView with regular ScrollView on dedicated tabs to disable bottom bar from scrolling.
I'm using Bottom Sheet from Android support library like this:
XML:
<LinearLayout
android:id="#+id/bottomSheetLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/fourth_white"
android:orientation="vertical"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior" />
I add child views to LinearLayout:
bottomSheet.addView(actionButtonView);
After I've finished adding child views, I initialize BottomSheetBehavior and expand it:
BottomSheetBehavior sheetBehavior = BottomSheetBehavior.from(bottomSheet);
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
This doesn't work. Nothing shows. Even if I preset the LinearLayout height inside XML, it's just all white.
If I add all the child views inside LinearLayout in XML, then everything works fine. It just doesn't work when I try to dynamically add views programatically.
Anyone had any similar issues?
Troubles with dynamic content on BottomSheetBehavior related to implementation of it's expanded size calculation. BottomSheetBehavior calculates expanded size in onLayoutChild method. But when you change content of sheet layout process launches asynchronous. Even if you call RequestLayout or something similar. So consequence of calls is like this:
BottomSheetBehavior have old expanded size (in your case I think it is zero)
You add content to BottomSheet. Expanded size is still old.
You call SetState to EXPANDED. BottomSheetBehavior still remember old expanded size and launches animation to that size. State changed to STATE_SETTLING!
onLayoutChild called and BottomSheetBehavior calculates new expanded size. But animation is already in progress and state is STATE_SETTLING so BottomSheetBehavior do not change its size
Animation finished. Size of BottomSheet is old. State changed to EXPANDED but BottomSheetBehavior "forgot" that expanded size was changed during animation.
It is surely the bug of BottomSheetBehaviour implementation.
In my project I found such workaround:
private void showPanel(final View panelContent) {
if (panelBehavior.getState()!=BottomSheetBehavior.STATE_EXPANDED) {
panelBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(final View bottomSheet, int newState) {
if (newState==BottomSheetBehavior.STATE_EXPANDED) {
panelBehavior.setBottomSheetCallback(null);
contentView.removeAllViews();
contentView.addView(panelContent);
panelView.setVisibility(View.VISIBLE);
}
}
#Override
public void onSlide(View bottomSheet, float slideOffset) {
}
});
panelBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
return;
}
contentView.removeAllViews();
contentView.addView(panelContent);
panelView.setVisibility(View.VISIBLE);
}
private void hidePanel() {
panelBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
panelView.setVisibility(View.GONE);
contentView.removeAllViews();
}
So when you need to show BottomSheet with new content call ShowPanel. When you need to completely hide BottomSheet call hidePanel (if you need to hide it in your project. If not you could remove setVisibility from methods).
The idea of workaround is to never change content of BottomSheet when BottomSheetBehavior is not in expanded state. If state is not expanded just change it to expanded, wait until animation finished and only then change content.
Try to post runnable to view's message queue:
bottomSheet.post(new Runnable() {
#Override
public void run() {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
});
Or with retrolambda:
bottomSheet.post(() -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED));
I am using Activity class with (usually) one fragment as a content. In the Activity I use CollapsingToolbarLayout as some kind of header for some information and everything works fine. But in some cases (when some fragments are attached) I don't want to show that info, I don't want CollapsingToolbarLayout to open on scroll.
What I want to achieve is to lock CollapsingToolbarLayout, prevent it from opening from the fragment. I am collapsing it programmatically with appBarLayout.setExpanded(false, true);
I came up with a different method as setting the nested scrolling flag only works when dragging the NestedScrollView. The appbar can still be expanded by swiping on the bar itself.
I set this up as a static function in "Utils" class. Obviously the flags you set upon unlocking will depend on which ones are relevant for your use case.
This function assumes you are are starting with an expanded toolbar
public static void LockToolbar(boolean locked, final AppBarLayout appbar, final CollapsingToolbarLayout toolbar) {
if (locked) {
// We want to lock so add the listener and collapse the toolbar
appbar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (toolbar.getHeight() + verticalOffset < 2 * ViewCompat.getMinimumHeight(toolbar)) {
// Now fully expanded again so remove the listener
appbar.removeOnOffsetChangedListener(this);
} else {
// Fully collapsed so set the flags to lock the toolbar
AppBarLayout.LayoutParams lp = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
lp.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED);
}
}
});
appbar.setExpanded(false, true);
} else {
// Unlock by restoring the flags and then expand
AppBarLayout.LayoutParams lp = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
lp.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
appbar.setExpanded(true, true);
}
}
Well, I managed to solve it myself. The trick is to disable nested scrolling behaviour with ViewCompat.setNestedScrollingEnabled(recyclerView, expanded);
As I am using one fragment in the activity as a content view and putting it on the backstack I simply check when backstack has changed and which fragment is visibile. Note that I NestedScrollView in every fragment to trigger collapsible toolbar. This is my code:
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
NestedScrollView nestedScrollView = (NestedScrollView)findViewById(R.id.nested_scroll_view);
int size = getSupportFragmentManager().getBackStackEntryCount();
if (size >= 1 && nestedScrollView != null) {
if (getSupportFragmentManager().getBackStackEntryAt(size - 1).getName().equals("SpotDetailsFragment")) {
Log.d(LOG_TAG, "Enabling collapsible toolbar.");
ViewCompat.setNestedScrollingEnabled(nestedScrollView, true);
} else {
Log.d(LOG_TAG, "Disabling collapsible toolbar.");
ViewCompat.setNestedScrollingEnabled(nestedScrollView, false);
}
}
}
});
This thread helped me a lot, where another possible solution is presented:
Need to disable expand on CollapsingToolbarLayout for certain fragments
I was playing with the new Android Design Library. The CollapsingToolbarLayout works perfectly. However, I am having trouble setting default state of toolbar as Collapsed.
I am trying to implement the solution shown here and here
I am calling following code in my onResume of Activity:
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
if(behavior!=null)
{
Log.d("DEBUG", "Behaviour is Not Null ");
int[] consumed = new int[2];
behavior.onNestedPreScroll(coordinator, appBarLayout, null, 0, 1000,consumed);
// behavior.onNestedFling(coordinator, appBarLayout, null, 0, 10000, true);
}
else
Log.d("DEBUG", "Behaviour is Null " );
However, the behaviour returned by params is null.
My xml is code same as here, except i am not using drawer and CordinatorLayout is my root Layout.
EDIT: I earlier tried switching AppBarLayout.Behavior to AppBarLayout.ScrollingViewBehavior and setting layout_behavior for AppBarLayout to #string/appbar_scrolling_view_behavior, but it resulted in weird layout.
Robin's answer works nicely. To Complement that, the behavior can also set in xml using following tag in AppBarLayout:
app:layout_behavior="android.support.design.widget.AppBarLayout$Behavior"
You have to set a layout behavior before.
Just use following code at your onCreate method:
((CoordinatorLayout.LayoutParams) YOUR_LAYOUT.getLayoutParams()).setBehavior(new AppBarLayout.Behavior() {});
In my activity I do:
setSupportActionBar(toolbar);
where toolbar is an instance of android.support.v7.widget.Toolbar
Is there any way after this to hide and show back Toolbar widget programmatically? I already tried
toolbar.setVisibility(View.INVISIBLE);
but this only makes it invisible and it still takes space, so that content of the activity starts after it, and I see white space instead in the header.
INVISIBLE only hides the view.
GONE however, will hide the view and prevent it from taking up any space.
toolbar.setVisibility(View.GONE);
If your toolbar is inside the AppBarLayout then you can use setExpanded method of AppBarLayout to expand and collapse the toolbar with or without animation.
setExpanded(boolean expanded, boolean animate)
this method is available from v23 of Support Library.
From the documentation for reference.
As with AppBarLayout scrolling, this method relies on this layout being a direct child of a CoordinatorLayout.
expanded : true if the layout should be fully expanded, false if it should be fully collapsed
animate : Whether to animate to the new state
AppBarLayout appBarLayout = (AppBarLayout)findViewById(R.id.appBar);
to expand the toolbar with animation.
appBarLayout.setExpanded(true, true);
to collapse the toolbar with animation.
appBarLayout.setExpanded(false, true);
Here is my code try this. It worked perfectly for me.
private static final float APPBAR_ELEVATION = 14f;
private void hideAppBar(final AppBarLayout appBar) {
appBar.animate().translationY(-appBar.getHeight()).setInterpolator(new LinearInterpolator()).setDuration(500);
}
public void showAppBar(final AppBarLayout appBar){
appBar.animate().translationY(0).setInterpolator(new LinearInterpolator()).setDuration(500).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
appBar.setElevation(APPBAR_ELEVATION);
}
});
}
Hope this might help you