Pin a view to bottom of screen in BottomSheetDialog Fragment - android

I've used BottomSheetDialogFragment in my project and I've designed it as below:
Target: I'm going to stick the bottom menu of BottomSheetDialog to bottom of the screen, in either mode collapse and expand.
So in BottomSheetDialog layout, I used RelativeLayout for parent and "layout_alignParentBottom" for menu container, As below:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/bottomSheetContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
tools:context=".MyBottomSheetDialogFragment">
<RelativeLayout
android:id="#+id/topSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true">
....
</RelativeLayout>
<android.support.v4.widget.NestedScrollView
android:id="#+id/descriptionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/topSection">
....
</android.support.v4.widget.NestedScrollView>
<HorizontalScrollView
android:id="#+id/iconsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
....
</HorizontalScrollView>
</RelativeLayout>
But the dialogue is as follows:
As you can see, the bottom menu is not visible at first.
Can someone help me to solve this problem?

To solve this, several things came to my mind when I tried, but I did not succeed.
But this finally solved for me by this way:
For collapse mode, I set the bottomSheetBehavior's peekHeight to 1/3 of the screen (with the following code):
View bottomSheetContainer = dialog.findViewById(R.id.bottomSheetContainer);
View parent = (View) bottomSheetContainer.getParent();
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) parent.getLayoutParams();
BottomSheetBehavior bottomSheetBehavior = (BottomSheetBehavior) params.getBehavior();
View inflatedView = View.inflate(getContext(), R.layout.word_details_bottom_sheet, null);
inflatedView.measure(0, 0);
int screenHeight = getActivity().getResources().getDisplayMetrics().heightPixels;
if (bottomSheetBehavior != null) {
bottomSheetBehavior.setPeekHeight(screenHeight /3);
}
So I decided to do it:
1- for collapse mode: bottomSheet container's height = bottomSheetBehavior's peekHeight
2- for expand mode: bottomSheet container's height = full screen Height
So I wrote the following code (full code):
WordDetailsBottomSheet.java
public class WordDetailsBottomSheet extends BottomSheetDialogFragment {
public WordDetailsBottomSheet() { // Required empty public constructor }
#NotNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
BottomSheetDialog dialog = new BottomSheetDialog(getActivity(), 0);
dialog.setContentView(R.layout.word_details_bottom_sheet);
View bottomSheetContainer = dialog.findViewById(R.id.bottomSheetContainer);
View parent = (View) bottomSheetContainer.getParent();
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) parent.getLayoutParams();
BottomSheetBehavior bottomSheetBehavior = (BottomSheetBehavior) params.getBehavior();
View inflatedView = View.inflate(getContext(), R.layout.word_details_bottom_sheet, null);
inflatedView.measure(0, 0);
int screenHeight = getActivity().getResources().getDisplayMetrics().heightPixels;
int statusBarHeight = getStatusBarHeight();
if (bottomSheetBehavior != null) {
bottomSheetBehavior.setPeekHeight(screenHeight / BOTTOM_SHEET_PEEK_HEIGHT_PERCENT);
bottomSheetContainer.getLayoutParams().height = bottomSheetBehavior.getPeekHeight();
}
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View view, int newState) {
switch (newState) {
case BottomSheetBehavior.STATE_EXPANDED:
bottomSheetContainer.getLayoutParams().height = screenHeight-statusBarHeight;
break;
case BottomSheetBehavior.STATE_COLLAPSED:
bottomSheetContainer.getLayoutParams().height = bottomSheetBehavior.getPeekHeight();
break;
case BottomSheetBehavior.STATE_HIDDEN:
dismiss();
break;
default:
break;
}
}
#Override
public void onSlide(#NonNull View view, float slideOffset) {
}
});
return dialog;
}
public int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}
word_details_bottom_sheet.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/bottomSheetContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
tools:context=".MyBottomSheetDialogFragment">
<RelativeLayout
android:id="#+id/topSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true">
....
</RelativeLayout>
<android.support.v4.widget.NestedScrollView
android:id="#+id/descriptionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/topSection">
....
</android.support.v4.widget.NestedScrollView>
<HorizontalScrollView
android:id="#+id/iconsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
....
</HorizontalScrollView>
</RelativeLayout>
In the xml file, things that matter are:
1- parent id (android:id="#+id/bottomSheetContainer")
2- iconsContainer align (android:layout_alignParentBottom="true")

As you can see, the bottom menu is not visible at first.
Can someone help me to solve this problem?
I'm guessing that this behavior is working perfectly and fine because you set layout_height of NestedScrollView (Center content) to wrap_content which means, it will be wrapped by the content inside.
Meanwhile;
android:layout_alignParentBottom="true"
To HorizontalScrollView (below layout) means that it will be under the other layouts which it currently is!
So, if you are trying to see if it is working fine or not, set 100dp-50dp (or a specific size which you can see when BottomSheetDialog show up) instead of wrap_content to NestedScrollView then you probably would see that the below layout with the other layouts will be visible.
Anyways, everything's in this layout looks correct and fine. As well as pictures says the truth.

Related

How to scale the view to fit the content size in Android using animations

I have two views on parent Layout those are top and bottom screens and bottom screen having fixed height and its align to parent-bottom and top screen based on content it must be extend and collapse on parent Layout when i tapped on it and bottom screen automatically come down from top screen and i can able to scroll total content on parent Layout can some one help me please
activity_main.layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/bottom_view"
android:background="#3143ff" />
<View
android:id="#+id/bottom_view"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentBottom="true"
android:background="#color/colorPrimary" />
</RelativeLayout>
MainActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View view = findViewById(R.id.view);
final View contentView = findViewById(R.id.content_frame);
view.setOnClickListener(v -> {
final int screenHeight = contentView.getHeight();
ValueAnimator heightAnimator = ValueAnimator.ofInt(view.getHeight(), screenHeight);
heightAnimator.setDuration(1500);
heightAnimator.addUpdateListener(animation -> {
view.getLayoutParams().height = (int) animation.getAnimatedValue();
view.requestLayout();
});
heightAnimator.start();
});
}
}
See FoldingCell for Android for more help.
In your build.gradle()
dependencies{
implementation 'com.ramotion.foldingcell:folding-cell:1.2.2'
}
In you XML layout
<com.ramotion.foldingcell.FoldingCell
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/folding_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content">
​
<!--Layout One--> ​
<!--Layout Two-->
</com.ramotion.foldingcell.FoldingCell>
Almost done! Two steps remain! For correct animation, you need to set up two properties on the root element(s) of your Folding Cell: ​
android:clipChildren="false"
android:clipToPadding="false"
In your Java class
// get our folding cell
final FoldingCell fc = (FoldingCell) findViewById(R.id.folding_cell);
​
// attach click listener to folding cell
fc.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
fc.toggle(false);
}
});

Android - How to add parallax effect as bottom sheet covers another view

Using BottomSheetBehavior from the google design library, it looks like the default behavior is for the bottom sheet to "cover" other views in the same CoordinatorLayout as it expands. I can anchor something like a FAB (or other view with an appropriately defined CoordinatorLayout.Behavior) to the top of the sheet and have it slide up as the sheet expands, which is nice, but what I want is to have a view "collapse" as the bottom sheet expands, showing a parallax effect.
This effect in Google Maps is similar to what I'm looking for; it starts as a parallax effect, and then switches back to just having the bottom sheet "cover" the map once a certain scroll position is reached:
One thing I tried (though I suspected from the start it wouldn't work), was setting the upper view's height programmatically in the onSlide call of my BottomSheetBehavior.BottomSheetCallback. This was somewhat successful, but the movement wasn't nearly as smooth as in Google Maps.
If anyone has an idea how the effect is accomplished I would appreciate it a lot!
After a bit more experimenting/research I realized from this post
How to make custom CoordinatorLayout.Behavior with parallax scrolling effect for google MapView? that a big part of my problem was not understanding the parallax effect, which translates views rather than shrinking them. Once I realized that, it was trivial to create a custom behavior that would apply the parallax to my main view when the bottom sheet expanded:
public class CollapseBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V>{
public CollapseBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
if (isBottomSheet(dependency)) {
BottomSheetBehavior behavior = ((BottomSheetBehavior) ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior());
int peekHeight = behavior.getPeekHeight();
// The default peek height is -1, which
// gets resolved to a 16:9 ratio with the parent
final int actualPeek = peekHeight >= 0 ? peekHeight : (int) (((parent.getHeight() * 1.0) / (16.0)) * 9.0);
if (dependency.getTop() >= actualPeek) {
// Only perform translations when the
// view is between "hidden" and "collapsed" states
final int dy = dependency.getTop() - parent.getHeight();
ViewCompat.setTranslationY(child, dy/2);
return true;
}
}
return false;
}
private static boolean isBottomSheet(#NonNull View view) {
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
return ((CoordinatorLayout.LayoutParams) lp)
.getBehavior() instanceof BottomSheetBehavior;
}
return false;
}
}
Then in my layout XML, I set the app:layout_behavior of my main view to be com.mypackage.CollapseBehavior and the app:layout_anchor to be my bottom sheet view so that the onDependentViewChanged callback would trigger. This effect was much smoother than trying to resize the view. I suspect returning to my initial strategy of using a BottomSheetBehavior.BottomSheetCallback would also work similarly to this solution.
Edit: per request, the relevant XML is below. I add a MapFragment into #+id/map_container at runtime, though this should also work with anything you drop into that container like a static image. The LocationListFragment could likewise be replaced with any view or fragment, so long as it still has the BottomSheetBehavior
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_coordinator">
<FrameLayout
android:id="#+id/map_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:layout_anchor="#+id/list_container"
app:layout_behavior="com.mypackage.behavior.CollapseBehavior"/>
<fragment
android:name="com.mypackage.fragment.LocationListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/list_container"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"/>
</android.support.design.widget.CoordinatorLayout>
Patrick Grayson's post was very helpful. In my case though, I did need something that resized the map. I adopted the solution above to resize instead of translate. Perhaps others may be looking for a similar solution.
public class CollapseBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V> {
private int pixels = NO_RESIZE_BUFFER; // default value, in case getting a value from resources, bites the dust.
private static final int NO_RESIZE_BUFFER = 200; //The amount of dp to not have the bottom sheet ever push away.
public CollapseBehavior(Context context, AttributeSet attrs)
{
super(context, attrs);
pixels = (int)convertDpToPixel(NO_RESIZE_BUFFER,context);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
// child is the map
// dependency is the bottomSheet
if(isBottomSheet(dependency))
{
BottomSheetBehavior behavior = ((BottomSheetBehavior) ((CoordinatorLayout.LayoutParams)dependency.getLayoutParams()).getBehavior());
int peekHeight;
if (behavior != null) {
peekHeight = behavior.getPeekHeight();
}
else
return true;
if(peekHeight > 0) { // Dodge the case where the sheet is hidden.
if (dependency.getTop() >= peekHeight) { // Otherwise we'd completely overlap the map
if(dependency.getTop() >= pixels) { // On resize when we have more than our NO_RESIZE_BUFFER of dp left.
if(dependency.getTop() > 0) { // Don't want to map to be gone completely.
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
params.height = dependency.getTop();
child.setLayoutParams(params);
}
return true;
}
}
}
}
return false;
}
private static float convertDpToPixel(float dp, Context context)
{
float densityDpi = context.getResources().getDisplayMetrics().densityDpi;
return dp * (densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
private static boolean isBottomSheet(#NonNull View view)
{
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if(lp instanceof CoordinatorLayout.LayoutParams)
{
return ((CoordinatorLayout.LayoutParams) lp).getBehavior() instanceof BottomSheetBehavior;
}
return false;
}
}
And the layout...
<FrameLayout
android:id="#+id/flMap"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
android:layout_margin="0dp"
app:layout_anchor="#+id/persistentBottomSheet"
app:layout_behavior="com.yoursite.yourapp.CollapseBehavior">
<fragment
android:id="#+id/mapDirectionSummary"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.yoursite.yourapp.YourActivity" />
</FrameLayout>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/persistentBottomSheet"
app:behavior_peekHeight="#dimen/bottom_sheet_peek_height"
app:behavior_hideable="false"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
tools:context="com.yoursite.yourapp.YourActivity">
<!-- Whatever you want on the bottom sheet. -->
</android.support.constraint.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardElevation="8dp"
app:cardBackgroundColor="#324">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?attr/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="#style/Theme.AppCompat.Light">
<EditText
android:id="#+id/txtSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:ems="10"
android:inputType="text"
android:maxLines="1" />
</android.support.v7.widget.Toolbar>
</android.support.v7.widget.CardView>
</LinearLayout>

How to use BottomSheetDialog?

I want to try BottomSheetDialog introduced in Android Support Library 23.2 but it doesn't seem to work correctly. Here is what the doc says:
While BottomSheetBehavior captures the persistent bottom sheet case, this release also provides a BottomSheetDialog and
BottomSheetDialogFragment to fill the modal bottom sheets use case.
Simply replace AppCompatDialog or AppCompatDialogFragment with their
bottom sheet equivalents to have your dialog styled as a bottom
sheet."
So I changed my AppCompatDialog to BottomSheetDialog:
package my.package.ui.dialog;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.design.widget.BottomSheetDialog;
import my.package.R;
public class AccountActionsDialog extends BottomSheetDialog {
public AccountActionsDialog(Context context) {
super(context);
if (context instanceof Activity) {
setOwnerActivity((Activity) context);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutInflater().inflate(R.layout.dialog_account_actions, null));
}
}
Here is my layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ff0000"
android:padding="16dp"
android:text="Delete account"
android:textColor="#ffffff" />
</LinearLayout>
Then I use the following code in my Activity:
new AccountActionsDialog(this).show();
My screen becomes dimmed but the content of my dialog is not visible. Any thoughts on what might be missing? It works fine when I use AppCompatDialog instead.
Instead of having a separate class, you can simply create an instance for BottomSheetDialog in your Activity/Fragment like following and you can use it. It is very easier and simpler I think.
val dialog = BottomSheetDialog(this)
val bottomSheet = layoutInflater.inflate(R.layout.bottom_sheet, null)
bottomSheet.buttonSubmit.setOnClickListener { dialog.dismiss() }
dialog.setContentView(bottomSheet)
dialog.show()
This is the layout file of BottomSheetDialog.
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:soundEffectsEnabled="false">
<FrameLayout
android:id="#+id/design_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:layout_behavior="#string/bottom_sheet_behavior"
style="?attr/bottomSheetStyle"/>
</android.support.design.widget.CoordinatorLayout>
Your content view is inside the view design_bottom_sheet, it will be positioned center vertically by CoordinatorLayout, and BottomSheetBehavior will offset it.
mParentHeight = parent.getHeight();
mMinOffset = Math.max(0, mParentHeight - child.getHeight());
mMaxOffset = mParentHeight - mPeekHeight;
if (mState == STATE_EXPANDED) {
ViewCompat.offsetTopAndBottom(child, mMinOffset);
} else if (mHideable && mState == STATE_HIDDEN) {
ViewCompat.offsetTopAndBottom(child, mParentHeight);
} else if (mState == STATE_COLLAPSED) {
ViewCompat.offsetTopAndBottom(child, mMaxOffset);
}
It intented to positon design_bottom_sheet at mMaxOffset, but actually the initial getTop of the child view is not 0, but (mParentHeight - childHeight) / 2, so you view if offset more than the desired offset.
Find the view design_bottom_sheet and set its gravity to Gravity.TOP | Gravity.CENTER_HORIZONTAL will fix it. But, if the childHeight is less than mPeekHeight, there will be blank area below you content view.
However, if peekHeight > childHeight, the mMaxOffset will less than mMinOffset, which will cause weird behavior.
Maybe the code should be changed to
mMaxOffset = Math.max((mParentHeight - mPeekHeight), mMinOffset);
insted of
mMaxOffset = mParentHeight - child.getHeight();
Here's the issue on code.google.com https://code.google.com/p/android/issues/detail?id=201793
An issue some users are seeing boils down to the FrameLayout that wraps our content view being centered vertically. The BottomSheetBehavior only works if this view is top aligned. I haven't figured out what causes the FrameLayout to become centered vertically yet, but here's a possible workaround:
View contentView = ...
// You may have to measure your content view first.
dialog.setContentView(contentView);
// Change this to a percentage or a constant, whatever you want to do.
// The default is 1024 - any views smaller than this will be pulled off
// the bottom of the screen.
float peekHeight = contentView.getMeasuredHeight();
View parent = (View)contentView.getParent();
BottomSheetBehavior behavior = BottomSheetBehavior.from(parent);
behavior.setPeekHeight(peekHeight);
CoordinatorLayout.LayoutParams layoutParams =
(CoordinatorLayout.LayoutParams)parent.getLayoutParams();
layoutParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
I was expriencing the same issue, dimmed background and content not visible. Here is how I managed to workaround it by setting the content view in setupDialog() hidden method.
public class CustomBottomSheetDialogFragment extends BottomSheetDialogFragment {
private TextView mOffsetText;
private TextView mStateText;
private BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
setStateText(newState);
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss();
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
setOffsetText(slideOffset);
}
};
private LinearLayoutManager mLinearLayoutManager;
private ApplicationAdapter mAdapter;
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return super.onCreateDialog(savedInstanceState);
}
#Override
public void onViewCreated(View contentView, #Nullable Bundle savedInstanceState) {
super.onViewCreated(contentView, savedInstanceState);
}
#Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.bottom_sheet_dialog_content_view, null);
dialog.setContentView(contentView);
mBottomSheetBehavior = BottomSheetBehavior.from(((View) contentView.getParent()));
if (mBottomSheetBehavior != null) {
mBottomSheetBehavior.setBottomSheetCallback(mBottomSheetBehaviorCallback);
}
mOffsetText = (TextView) contentView.findViewById(R.id.offsetText);
mStateText = (TextView) contentView.findViewById(R.id.stateText);
}
}
And the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/offsetText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/black" />
<TextView
android:id="#+id/stateText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/black" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
It started to work when I set fixed height for my TextView (200dp), although for some height values it still behaves incorrectly. Obviously it's an issue of support lib. There are already few reports related to BottomSheetDialog in the bug tracker:
https://code.google.com/p/android/issues/detail?id=201793&sort=-opened&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened
https://code.google.com/p/android/issues/detail?id=201826

How to show Snackbar at top of the screen

As the Android documentation says
Snackbars provide lightweight feedback about an operation. They show a brief message at the bottom of the screen on mobile and lower left on larger devices.
Is there any alternative by which we can show snackbars at top of the screen instead of the bottom?
Right now I am doing something like this which shows a snackbar at the bottom of the screen.
Snackbar.make(findViewById(android.R.id.content), "Hello this is a snackbar!!!",
Snackbar.LENGTH_LONG).setAction("Undo", mOnClickListener)
.setActionTextColor(Color.RED)
.show();
It is possible to make the snackbar appear on top of the screen using this:
Snackbar snack = Snackbar.make(parentLayout, str, Snackbar.LENGTH_LONG);
View view = snack.getView();
FrameLayout.LayoutParams params =(FrameLayout.LayoutParams)view.getLayoutParams();
params.gravity = Gravity.TOP;
view.setLayoutParams(params);
snack.show();
From the OP:
I had to change the first line:
Snackbar snack = Snackbar.make(findViewById(android.R.id.content), "Had a snack at Snackbar", Snackbar.LENGTH_LONG);
CoordinatorLayout coordinatorLayout=(CoordinatorLayout)findViewById(R.id.coordinatorLayout);
Snackbar snackbar = Snackbar.make(coordinatorLayout, "Text", Snackbar.LENGTH_LONG);
View view = snackbar.getView();
CoordinatorLayout.LayoutParams params=(CoordinatorLayout.LayoutParams)view.getLayoutParams();
params.gravity = Gravity.TOP;
view.setLayoutParams(params);
snackbar.show();
Kotlin-
val snackBarView = Snackbar.make(view, "SnackBar Message" , Snackbar.LENGTH_LONG)
val view = snackBarView.view
val params = view.layoutParams as FrameLayout.LayoutParams
params.gravity = Gravity.TOP
view.layoutParams = params
view.background = ContextCompat.getDrawable(context,R.drawable.custom_drawable) // for custom background
snackBarView.animationMode = BaseTransientBottomBar.ANIMATION_MODE_FADE
snackBarView.show()
below line will resolve the animation issue.
snackBarView.animationMode = BaseTransientBottomBar.ANIMATION_MODE_FADE
Alternate solution-
snackBarView.anchorView = mention viewId above whom you want to show SnackBar
You can do the following to position a SnackBar anywhere inside a layout (This method has No Animation Issues)
1) According to:
https://developer.android.com/reference/android/support/design/widget/Snackbar.html#make(android.view.View, java.lang.CharSequence, int)
Snackbar make (View view,
CharSequence text,
int duration)
Make a Snackbar to display a message Snackbar will try and find a parent view to hold Snackbar's view from
the value given to view. Snackbar will walk up the view tree trying to
find a suitable parent, which is defined as a CoordinatorLayout or the
window decor's content view, whichever comes first.
So, one can position a snackBar anywhere inside a layout simply by adding a Coordinator Layout in the desired location and using that Coordinator Layout as the view argument inside Snackbar.make method above.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity"
android:orientation="vertical"
android:id="#+id/rl"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Coordinator Layout used to position the SnackBar -->
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cl"
android:layout_alignParentTop="true"
android:background="#android:color/transparent">
</android.support.design.widget.CoordinatorLayout>
<!-- add your layout here -->
</RelativeLayout>
2) The Coordinator Layout used to display the SnackBar should be on top of all other views (highest elevation). In order to do that, one might either call bringToFront() on the coordinator layout or elevate the coordinator layout (add
android:elevation="10dp" for example)
3) At this point the snackBar will show up in the desired location, but the snackBar is displayed with a bottom to top animation (default behavior). In order to achieve a top to bottom animation, you can do the following:
Rotate Coordinator layout used inside Snackbar.make method by 180 degrees
4) After step 3, the snackBar will be displayed with a top to bottom animation, but the message and action text are rotated and the gravity is reversed, so as a final step, I did the following:
Got SnackBar View, found the LinearLayout holding the TextView responsible about displaying the message and the TextView responsible about the action, and rotated the parent LinearLayout by 180 degress
5) Example:
public class MainActivity extends AppCompatActivity {
private final String TAG = MainActivity.class.getSimpleName();
private RelativeLayout rl;
private CoordinatorLayout cl;
private CoordinatorLayout cl1;
private CoordinatorLayout cl2;
private CoordinatorLayout cl3;
private CoordinatorLayout cl4;
private Snackbar snackbar_updated;
private Snackbar snackbar_updated1;
private Snackbar snackbar_updated2;
private Snackbar snackbar_updated3;
private Snackbar snackbar_updated4;
private Snackbar snackbar_ordinary;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rl = (RelativeLayout) findViewById(R.id.rl);
cl = (CoordinatorLayout) findViewById(R.id.cl);
cl1 = (CoordinatorLayout) findViewById(R.id.cl1);
cl2 = (CoordinatorLayout) findViewById(R.id.cl2);
cl3 = (CoordinatorLayout) findViewById(R.id.cl3);
cl4 = (CoordinatorLayout) findViewById(R.id.cl4);
cl.bringToFront();
cl1.bringToFront();
cl2.bringToFront();
cl3.bringToFront();
cl4.bringToFront();
snackbar_updated = Snackbar.make(cl, "Message", Snackbar.LENGTH_INDEFINITE);
snackbar_updated.setAction("Ok", new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
/** Snackbar message and action TextViews are placed inside a LinearLayout
*/
final Snackbar.SnackbarLayout snackBarLayout = (Snackbar.SnackbarLayout) snackbar_updated.getView();
for (int i = 0; i < snackBarLayout.getChildCount(); i++) {
View parent = snackBarLayout.getChildAt(i);
if (parent instanceof LinearLayout) {
((LinearLayout) parent).setRotation(180);
break;
}
}
snackbar_updated1 = Snackbar.make(cl1, "Message", Snackbar.LENGTH_INDEFINITE);
snackbar_updated1.setAction("Ok", new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
/** Snackbar message and action TextViews are placed inside a LinearLayout
*/
final Snackbar.SnackbarLayout snackBarLayout1 = (Snackbar.SnackbarLayout) snackbar_updated1.getView();
for (int i = 0; i < snackBarLayout1.getChildCount(); i++) {
View parent = snackBarLayout1.getChildAt(i);
if (parent instanceof LinearLayout) {
((LinearLayout) parent).setRotation(180);
break;
}
}
snackbar_updated2 = Snackbar.make(cl2, "Message", Snackbar.LENGTH_INDEFINITE);
snackbar_updated2.setAction("Ok", new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
snackbar_updated3 = Snackbar.make(cl3, "Message", Snackbar.LENGTH_INDEFINITE);
snackbar_updated3.setAction("Ok", new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
/** Snackbar message and action TextViews are placed inside a LinearLayout
*/
Snackbar.SnackbarLayout snackBarLayout3 = (Snackbar.SnackbarLayout) snackbar_updated3.getView();
for (int i = 0; i < snackBarLayout3.getChildCount(); i++) {
View parent = snackBarLayout3.getChildAt(i);
if (parent instanceof LinearLayout) {
((LinearLayout) parent).setRotation(180);
break;
}
}
snackbar_updated4 = Snackbar.make(cl4, "Message", Snackbar.LENGTH_INDEFINITE);
snackbar_updated4.setAction("Ok", new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
snackbar_ordinary = Snackbar.make(rl, "Message", Snackbar.LENGTH_INDEFINITE);
snackbar_ordinary.setAction("Ok", new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
rl.post(new Runnable() {
#Override
public void run() {
snackbar_updated.show();
rl.postDelayed(new Runnable() {
#Override
public void run() {
snackbar_updated1.show();
rl.postDelayed(new Runnable() {
#Override
public void run() {
snackbar_updated2.show();
rl.postDelayed(new Runnable() {
#Override
public void run() {
snackbar_updated3.show();
rl.postDelayed(new Runnable() {
#Override
public void run() {
snackbar_updated4.show();
rl.postDelayed(new Runnable() {
#Override
public void run() {
snackbar_ordinary.show();
}
}, 2000);
}
}, 2000);
}
}, 2000);
}
}, 2000);
}
}, 2000);
}
});
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity"
android:orientation="vertical"
android:id="#+id/rl"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Coordinator Layout used to position the SnackBar -->
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cl"
android:rotation="180"
android:layout_alignParentTop="true"
android:background="#android:color/transparent">
</android.support.design.widget.CoordinatorLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:itemIconTint="#333"
app:itemTextColor="#333"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/ThemeOverlay.AppCompat.Dark"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
</android.support.design.widget.AppBarLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_below="#id/appbar"
android:layout_gravity="bottom">
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:id="#+id/tv_top"
android:text="Layout Top"
android:gravity="center"
android:textSize="15sp"
android:textColor="#android:color/white"
android:layout_alignParentTop="true"
android:background="#color/colorAccent">
</TextView>
<!-- Coordinator Layout used to position the SnackBar -->
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cl1"
android:rotation="180"
android:layout_below="#id/tv_top"
android:background="#android:color/transparent">
</android.support.design.widget.CoordinatorLayout>
<!-- Coordinator Layout used to position the SnackBar -->
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cl2"
android:paddingBottom="75dp"
android:layout_centerInParent="true"
android:background="#android:color/transparent">
</android.support.design.widget.CoordinatorLayout>
<!-- Coordinator Layout used to position the SnackBar -->
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:id="#+id/tv_center"
android:text="Center"
android:gravity="center"
android:textSize="15sp"
android:layout_centerInParent="true"
android:textColor="#android:color/white"
android:background="#color/colorAccent">
</TextView>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cl3"
android:rotation="180"
android:paddingBottom="75dp"
android:layout_centerInParent="true"
android:background="#android:color/transparent">
</android.support.design.widget.CoordinatorLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:id="#+id/tv_bottom"
android:text="Layout Bottom"
android:gravity="center"
android:textSize="15sp"
android:textColor="#android:color/white"
android:layout_alignParentBottom="true"
android:background="#color/colorAccent">
</TextView>
<!-- Coordinator Layout used to position the SnackBar -->
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cl4"
android:layout_above="#id/tv_bottom"
android:background="#android:color/transparent">
</android.support.design.widget.CoordinatorLayout>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
6) Result:
Combined solution from the above ones:
final ViewGroup.LayoutParams params = snackbar.getView().getLayoutParams();
if (params instanceof CoordinatorLayout.LayoutParams) {
((CoordinatorLayout.LayoutParams) params).gravity = Gravity.TOP;
} else {
((FrameLayout.LayoutParams) params).gravity = Gravity.TOP;
}
snackbar.getView().setLayoutParams(params);
Still suffers from the improper animation.
This makes the Snackbar appears on Top without the strange slide in transition.
Best simple solution in Kotlin:
val snackbar = Snackbar.make(view, string, Snackbar.LENGTH_LONG)
val layoutParams = LayoutParams(snackbar.view.layoutParams)
layoutParams.gravity = Gravity.TOP
snackbar.view.setPadding(0, 10, 0, 0)
snackbar.view.layoutParams = layoutParams
snackbar.animationMode = BaseTransientBottomBar.ANIMATION_MODE_FADE
snackbar.show()
Idiomatic Kotlin version
snackbar.view.layoutParams = (snackbar.view.layoutParams as FrameLayout.LayoutParams).apply {
gravity = Gravity.TOP
}
Kotlin extension
/** Kotlin extension that adds this snackbar at the top of the screen. */
fun Snackbar.gravityTop() {
this.view.layoutParams = (this.view.layoutParams as FrameLayout.LayoutParams).apply {
gravity = Gravity.TOP
}
}
Then just call:
snackbar.gravityTop()
Before displaying the Snackbar, please add the following code
View snackbarView = snackbar.getView();
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) snackbarView.getLayoutParams();
params.gravity = Gravity.TOP;
snackbarView.setLayoutParams(params);
snackbarView.startAnimation(AnimationUtils.loadAnimation(host, R.anim.slide_in_snack_bar));
And when dismiss()
View snackbarView = snackbar.getView();
snackbarView.startAnimation(AnimationUtils.loadAnimation(_snackbar.getContext(), R.anim.slide_out_snack_bar));
snackbar.dismiss()
slide_in_snack_bar.xml
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="#android:integer/config_longAnimTime"
android:fromYDelta="-100%p"
android:toYDelta="0%p" />
slide_out_snack_bar.xml
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="#android:integer/config_longAnimTime"
android:fromYDelta="0%p"
android:toYDelta="-100%p" />
if you are using Constraintlayoutas as the root parent, with the help of Guidelines and CoordinatorLayout you can try this:
changing the value of: layout_constraintGuide_end="Your bottom margin value" inside Guideline as below example.
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/coordinator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/guideline3" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_end="150dp" />
in the activty you can pass CoordinatorLayout as the view for the snackbar
mSnackBar = Snackbar.make(
coordinator,
getString(R.string.no_internet_connection),
Snackbar.LENGTH_INDEFINITE
)
mSnackBar.show()
why not anchor it to some other view say you have other views in layout just anchor the snackbar to some view so that it displays there for example:
<LinearLayout>
......<Button>
<LinearLayout>
<TextView>
</LinearLayout>
</LinearLayout>
in code:
Snackbar snackbar = Snackbar.make(view,message, snackbar.LENGTH_SHORT).setAnchorView(R.id.reg_button);
R.id.reg_button is the button in the layout it can be anywhere snackbar will show up right there
No need to use any frame etc app will function as expected will not crash
2 years later, here's my solution..
Setting the Top and Bottom Margin changes the effect result,... I have tried to add as much customization options as i could, Overriding the animation is another option not written here.
Thanks to everyone's answers on several questions...
{
// usage for setSnackBar
int view = R.id.toolbar;
String snackMessage = "";
boolean useAction = false;
Runnable ifUseActionRunnable = null;
String actionText = "";
int leftMargin = 0;
int topMargin = 0;
int rightMargin = 0;
int bottomMargin = 0;
int backgroundColour = Color.BLACK;
int textColor = Color.WHITE;
int duration = Snackbar.LENGTH_LONG;
setSnackBar(view, snackMessage, useAction, ifUseActionRunnable, actionText, leftMargin, topMargin, rightMargin, bottomMargin, backgroundColour, textColor, duration);
}
Snackbar snb;
public void setSnackBar(int targetView, String snackMessage, boolean useAction, final Runnable ifUseActionRunnable, String actionText , int leftMargin, int topMargin, int rightMargin, int bottomMargin, int backgroundColour, int textColor, int duration)
{
snb = Snackbar.make(findViewById(targetView), snackMessage, duration);
View view = snb.getView();
view.setBackgroundColor(backgroundColour);
snb.setActionTextColor(textColor);
FrameLayout.LayoutParams params =(FrameLayout.LayoutParams)view.getLayoutParams();
params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
params.setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
view.setLayoutParams(params);
if (useAction)
{
snb.setAction(actionText, new View.OnClickListener(){
#Override
public void onClick(View p1)
{
ifUseActionRunnable.run();
}
});
}
if (snb.isShown())
{
snb.dismiss();
snb.show();
}
else
{
snb.show();
}
}
to show Snackbar at top of the screen (kotlin)
// Custom snackbar : banner model
val customSnackBar = Snackbar.make(snackbar_id, "", Snackbar.LENGTH_INDEFINITE)
val view = customSnackBar.view
val params = view.layoutParams as CoordinatorLayout.LayoutParams
params.gravity = Gravity.TOP
view.layoutParams = params
val layout = customSnackBar.view as Snackbar.SnackbarLayout
val customSnackView = layoutInflater.inflate(R.layout.snackbar_banner, null)
val actionButton1 = customSnackView.findViewById(R.id.action_button_1) as MaterialButton
actionButton1.setOnClickListener {
customSnackBar.dismiss()
}
val actionButton = customSnackView.findViewById(R.id.action_button_2) as MaterialButton
actionButton.setOnClickListener {
customSnackBar.dismiss()
}
// We can also customize the above controls
layout.setPadding(0, 0, 0, 0)
layout.addView(customSnackView, 0)
customSnackBar.show()
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#android:color/white"
>
<TextView
android:padding="16dp"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/snackbar_banner_message"
android:textColor="#android:color/black"
android:textSize="16sp" />
<LinearLayout
android:gravity="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="#+id/action_button_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="#android:color/black"
android:text="Close"
style="#style/Widget.MaterialComponents.Button.TextButton"
/>
<com.google.android.material.button.MaterialButton
android:id="#+id/action_button_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#android:color/black"
android:text="Fix it"
android:textStyle="bold"
style="#style/Widget.MaterialComponents.Button.TextButton"
/>
</LinearLayout>
</LinearLayout>

Hiding AppBarLayout and giving its space to the remaining view

I have a pretty standard layout using the new design libraries:
<AppBarLayout>
<CollapsingToolbarLayout>
<ImageView/>
<Toolbar/>
</CollapsingToolbarLayout>
</AppBarLayout>
<android.support.v4.widget.NestedScrollView/> <!-- content here -->
What I'm trying to do is to completely hide the whole AppBarLayout programmatically, to temporarily get rid of the Toolbar and its collapsing feature.
So I'm calling this:
private void disableCollapsing() {
AppBarLayout.LayoutParams p = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
p.setScrollFlags(0);
collapsingToolbarLayout.setLayoutParams(p);
}
to disable the collapsing behavior (works well), and finally this:
#Override
public void hide() {
final AppBarLayout layout = (AppBarLayout) findViewById(R.id.appbar);
layout.animate().translationY(-layout.getHeight())
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
layout.setVisibility(View.GONE);
}
}).start();
}
I make the AppBarLayout translate to the top (works smoothly), and at the end of the animation set is visibility to View.GONE.
Issue
At the end of the animation, no matter I also set the visibility to GONE, I can't get the space that was previously occupied by the AppBarLayout. My NestedScrollView remains confined in the lower half of the screen, as if the AppBarLayout was still there (which is not). How can I fix it?
Before hiding:
After hiding (AppBar translated to the top):
As you can see, the top space is empty and unreachable. The scroll view scrolls inside the margins it had before, as if the visibility change was not measured by the CoordinatorLayout.
I have tried calling coordinator.requestLayout(), with no success.
I also tried setting the AppBarLayout as an app:anchor for my NestedScrollView, but that screws things up - scroll view ends up taking the whole screen even before hiding.
I was thinking of a custom Behavior to be set on the scroll view when entering this hidden-AppBar mode, but I can't get started on that.
Yes this looks like a bug, I solved this issue for my application setting the appbar height to 0:
android.support.design.widget.AppBarLayout appbar = (android.support.design.widget.AppBarLayout) findViewById(R.id.appbar);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)appbar.getLayoutParams();
lp.height = 0;
appbar.setLayoutParams(lp);
As mentioned above, setting the Coordinator.LayoutParams#height fixes the issue.
However, I wanted to express how/when this occurs (not necessarily why):
The CollaspingToolbarLayout will exhibit this behavior only when its app:layout_scrollFlags property is set to exitUntilCollapsed and its nested ToolBar also has defines app:layout_collapseMode="pin". With this combination of flags, the Toolbar will pin itself to the top of the screen, and this is intentional and sometimes desirable.
(snipped for brevity)
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<!-- some other component here, i.e ImageView -->
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:gravity="top"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<!-- some scrolling view/layout -->
</androidx.coordinatorlayout.widget.CoordinatorLayout>
In the Fragment/Activity after the view is created, in Kotlin + ViewBinding:
binding.appbar.updateLayoutParams<CoordinatorLayout.LayoutParams> {
height = 0
}
For me, I had to capture the height of the AppBarLayout before hiding it to restore it to its original height when I wanted to show it.
private var appbarLayoutHeight = 0
private fun hideAppBar() {
appbarLayoutHeight = binding.appbar.measuredHeight
binding.appbar.updateLayoutParams<CoordinatorLayout.LayoutParams> {
height = 0
}
}
private fun showAppBar() {
binding.appbar.updateLayoutParams<CoordinatorLayout.LayoutParams> {
height = appbarLayoutHeight
}
}
Disclaimer: ViewBinding is not necessary to achieve this, nor is using Kotlin, and it's just what I use to acquire the AppBarLayoout and make this as terse/sugary as possible.
This works for me. Just toggles appbar on/off.
private boolean hide = true;
public void toggleAppBar() {
// Calculate ActionBar height
TypedValue tv = new TypedValue();
int actionBarHeight = 0;
if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)appBarLayout.getLayoutParams();
lp.height = hide ? 0 : actionBarHeight;
appBarLayout.setLayoutParams(lp);
appBarLayout.setExpanded(!hide, true);
hide = !hide;
appbar_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="#layout/content_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Thanks #Caleb Kleveter that is my code for Kotlin
val appBarLayout = activity?.findViewById<AppBarLayout>(R.id.app_bar_layout)
val lp = appBarLayout?.layoutParams
lp?.height = 0;
appBarLayout?.layoutParams = lp
The following works as well
appBarLayout.setExpanded(false, false);
appBarLayout.setVisibility(View.GONE);

Categories

Resources