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
Related
I have a BottomSheetDialog class that shows when we click the button, I need to make it full screen not on half on the page.
public class BottomSheetDialogBuyPlan extends BottomSheetDialog {
public BottomSheetDialogBuyPlan(#NonNull Context context) {
super(context);
BottomSheetBehavior<FrameLayout> behavior = getBehavior();
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
View bottomSheet = getLayoutInflater().inflate(R.layout.layout, null);
setContentView(bottomSheet);
show();
}
#Override
public void setOnShowListener(#Nullable OnShowListener listener) {
super.setOnShowListener(listener);
}
}
this is how i call it in activity
BottomSheetDialogBuyPlan bottomSheetDialog = new
BottomSheetDialogBuyPlan(getContext());
How to make it full screen?
Your code shown here is so limited that I can't also present code that is directly applicable to your case. At least I would suggest there is BottomSheetBehavior#setState(BottomSheetBehavior.STATE_EXPANDED) method.
Here is a minimum sample code:
MainActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BottomSheetDialog dialog = new BottomSheetDialog(this);
BottomSheetBehavior<FrameLayout> behavior = dialog.getBehavior();
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
View bottomSheet = getLayoutInflater().inflate(R.layout.bottom_sheet, null);
dialog.setContentView(bottomSheet);
dialog.show();
}
}
layout/bottom_sheet:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="a\na\na\na\na\na\na\na\na\na\na\na\na\na\na\na\na\na\na\na\na"
android:textSize="30sp" />
</ScrollView>
EDIT:
You can manage to expand the BottomSheet's content layout beyond its content originally requiring though I doubt you need to use BottomSheet for such usage...
MyBottomSheetDialog:
public class MyBottomSheetDialog extends BottomSheetDialog {
public MyBottomSheetDialog(#NonNull Context context) {
super(context);
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) context).getWindowManager()
.getDefaultDisplay()
.getMetrics(displayMetrics);
BottomSheetBehavior<FrameLayout> behavior = getBehavior();
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
View bottomSheet = getLayoutInflater().inflate(R.layout.bottom_sheet, null);
bottomSheet.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, displayMetrics.heightPixels
));
setContentView(bottomSheet);
}
}
layout/bottom_sheet:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="a\na"
android:textSize="30sp" />
</FrameLayout>
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.
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>
I am having a problem with a scrollView inside a fragment. My tablet application contain two main fragments one for the menu on the left and a other one on the right which contain an editText object and some other stuff.
I am trying to scroll vertically the content of the right fragment when the soft keyboard is showing.
The content scroll in the right fragment is working but after scroll the fragment seem to be cropped on the top and bottom where the system bars appeared while the soft keyboard was showing.
Layout before and after scroll.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.wenchao.cardstack.CardStack
android:id="#+id/card_stack_comment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="20dp"
android:visibility="visible"
android:background="#android:color/transparent"
android:clipChildren="false"
android:clipToPadding="false">
</com.wenchao.cardstack.CardStack>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="vertical"
android:layout_alignParentBottom="true"
android:id="#+id/general_comment">
<EditText
android:id="#+id/editText_feedback"
android:layout_below="#+id/text_leave_feedback"
android:layout_margin="10dp"
android:hint="#string/editText_hint"
android:padding="10dp"
android:gravity="top"
android:background="#drawable/background_with_border"
android:layout_width="match_parent"
android:layout_height="200dp" />
</RelativeLayout>
</RelativeLayout>
</ScrollView>
This is how I detect if the keyboard is showing and move the content of the
ScrollView:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
// r will be populated with the coordinates of your view
// that area still visible.
rootView.getWindowVisibleDisplayFrame(r);
int heightDiff = rootView.getRootView().getHeight() - (r.bottom - r.top);
if (heightDiff > 300) { // if more than 100 pixels, its probably a keyboard...
Log.d(LOG_TAG, "keyboard shows up");
scrollToCurrentFocusedView();
}else{
Log.d(LOG_TAG, "keyboard vanishes");
//try to refresh the scrollView but not working
//scrollView.invalidate()
//scrollView.requestLayout()
}
}
});
return rootView;
}
protected void scrollToCurrentFocusedView(){
Log.d(LOG_TAG, "calling scrollToCurrentFocusedView");
View view = getActivity().getCurrentFocus();
if (view != null && scrollView != null) {
scrollView.smoothScrollTo(0, view.getBottom()-(view.getHeight()*3));
}
}
I tried to refresh the scrollview after the keyboard is hidden with invalidate() and requestLayout() without success.
Any help would be appreciated. Thanks in advance.
Fixed the problem by using a customScrollView instead. The important part is to overload the onApplyWindowInsets() function.
public class CustomScrollView extends ScrollView {
public CustomScrollView(Context pContext, AttributeSet pAttrs, int pDefStyle) {
super(pContext, pAttrs, pDefStyle);
}
public CustomScrollView(Context pContext, AttributeSet pAttrs) {
super(pContext, pAttrs);
}
public CustomScrollView(Context pContext) {
super(pContext);
}
#Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if(insets.getSystemWindowInsetBottom() < 100){
WindowInsets newInset = insets.replaceSystemWindowInsets(new Rect(0,0,0,0));
Log.d("TEST","Keyboard is down");
return super.onApplyWindowInsets(newInset);
} else{
Log.d("TEST","Keyboard is up");
return super.onApplyWindowInsets(insets);
}
}
}
I'm using android.app.AlertDialog that contains a ScrollView and inside (of course) some content.
Google shows in its material-guidelines a small grey line above the buttons when the content is larger than the visible space: http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior
My alert-dialog doesn't have this grey line. How do I create this line?
I already tried a background for the ScrollView like this:
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#color/dark_transparent"/>
</shape>
But this created a line on top AND bottom. And it also appears when the content is smaller than the visible space, which looks ugly.
I found a solution for the grey line! :)
I found the solution how to show the grey line at all here: How to make a static button under a ScrollView?
For the check if I want to show it, I found the solution here: How can you tell when a layout has been drawn?
This is how my code looks like now:
This is my_material_dialog.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView
android:id="#+id/myMaterialDialog_scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<LinearLayout
android:id="#+id/myMaterialDialog_textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingLeft="26dp"
android:paddingRight="26dp"
android:paddingTop="15dp">
<!-- dynamically added content goes here -->
</LinearLayout>
</ScrollView>
<View
android:id="#+id/myMaterialDialog_lineView"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_gravity="center_horizontal"
android:background="#15000000"
android:gravity="center_horizontal"
android:visibility="gone"/>
</LinearLayout>
And this is MyMaterialDialog.java:
public class MyMaterialDialog extends AlertDialog {
private Context context;
private ScrollView scrollView;
private LinearLayout textView;
private View lineView;
private boolean checkingLayout;
public MyMaterialDialog(final Context context) {
super(context);
this.context = context;
final View myMaterialDialog = getLayoutInflater().inflate(R.layout.my_material_dialog, null);
this.scrollView = (ScrollView) myMaterialDialog.findViewById(R.id.myMaterialDialog_scrollView);
this.textView = (LinearLayout) myMaterialDialog.findViewById(R.id.myMaterialDialog_textView);
this.lineView = myMaterialDialog.findViewById(R.id.myMaterialDialog_lineView);
final ViewTreeObserver vto = scrollView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (checkingLayout) {
// avoid infinite recursions
return;
}
checkingLayout = true;
if (scrollView.canScrollVertically(1)) {
lineView.setVisibility(View.VISIBLE);
} else {
lineView.setVisibility(View.GONE);
}
checkingLayout = false;
}
});
setTitle(R.string.myMaterialDialog_title);
setText();
setView(myMaterialDialog);
show();
}
/**
* do request to webserver for texts
*/
private final void setText() {
final GetDialogTextRequest request = new GetDialogTextRequest();
final GetDialogTextResultHandler resultHandler = new GetDialogTextResultHandler(context, textView);
request.submit(resultHandler);
}
}
private final class GetDialogTextResultHandler extends DefaultRequestResultHandler<List<MyTextObject>> {
private final Context context;
private final LinearLayout textView;
private GetDialogTextResultHandler(final Context context, final LinearLayout textView) {
super(context);
this.context = context;
this.textView = textView;
}
#Override
public void handleResult(final List<MyTextObject> texts) {
setText(texts); // ... sets the content, can vary in size
}
}
Add something like this below your ScrollView:
<View android:layout_width="fill_parent"
android:layout_height="2px"
android:background="#90909090"/>
It should give you a slim greyish horizontal bar.
If you're using API 23+ (Android 6.0) using the following in scroll view will add the top and bottom indicators.
android:scrollIndicators="top|bottom"
If targeting older API's I looked into Google's Alert Dialog controller source code, and am using the following code:
private static void setScrollIndicators(ViewGroup root, final NestedScrollView content,
final int indicators, final int mask) {
// use it like this:
// setScrollIndicators(contentPanel, content, indicators,
// ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM);
// Set up scroll indicators (if present).
View indicatorUp = root.findViewById(R.id.scrollIndicatorUp);
View indicatorDown = root.findViewById(R.id.scrollIndicatorDown);
if (Build.VERSION.SDK_INT >= 23) {
// We're on Marshmallow so can rely on the View APIsaa
ViewCompat.setScrollIndicators(content, indicators, mask);
// We can also remove the compat indicator views
if (indicatorUp != null) {
root.removeView(indicatorUp);
}
if (indicatorDown != null) {
root.removeView(indicatorDown);
}
} else {
// First, remove the indicator views if we're not set to use them
if (indicatorUp != null && (indicators & ViewCompat.SCROLL_INDICATOR_TOP) == 0) {
root.removeView(indicatorUp);
indicatorUp = null;
}
if (indicatorDown != null && (indicators & ViewCompat.SCROLL_INDICATOR_BOTTOM) == 0) {
root.removeView(indicatorDown);
indicatorDown = null;
}
if (indicatorUp != null || indicatorDown != null) {
final View top = indicatorUp;
final View bottom = indicatorDown;
if (content != null) {
// We're just showing the ScrollView, set up listener.
content.setOnScrollChangeListener(
new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX,
int scrollY,
int oldScrollX, int oldScrollY) {
manageScrollIndicators(v, top, bottom);
}
});
// Set up the indicators following layout.
content.post(new Runnable() {
#Override
public void run() {
manageScrollIndicators(content, top, bottom);
}
});
} else {
// We don't have any content to scroll, remove the indicators.
if (top != null) {
root.removeView(top);
}
if (bottom != null) {
root.removeView(bottom);
}
}
}
}
}
private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) {
if (upIndicator != null) {
upIndicator.setVisibility(
ViewCompat.canScrollVertically(v, -1) ? View.VISIBLE : View.INVISIBLE);
}
if (downIndicator != null) {
downIndicator.setVisibility(
ViewCompat.canScrollVertically(v, 1) ? View.VISIBLE : View.INVISIBLE);
}
}
And XML looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="#+id/scrollIndicatorUp"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/dim_white"
android:visibility="gone"
tools:visibility="visible" />
<android.support.v4.widget.NestedScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<... you content here>
</android.support.v4.widget.NestedScrollView>
<View
android:id="#+id/scrollIndicatorDown"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/dim_white"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>