My layout is absolutely identical with this tutorial. But in this tutorial we use app:layout_behavior="pl.michalz.hideonscrollexample.ScrollingFABBehavior"
in Fab XML, so SnackBar covers the Fab. Without this code Fab does not move followed by RecyclerView. How to show SnackBar correctly?
Snackbar.make(getActivity().findViewById(R.id.coordinatorLayout),
adapter.getNewsList().get(position).getTitle(), Snackbar.LENGTH_LONG).show();
The example you follow is pretty unlucky. The default behavior of FloatingActionButton within CoordinatorLayout is to move up when you display SnackBar. Since this code overrides the Behavior you lose this feature because the methods never call their super class implementations. Clearly the author have not thought about this. However, you can modify the ScrollingFABBehavior to extend the original Behavior and thus support SnackBar:
public class ScrollingFABBehavior extends FloatingActionButton.Behavior {
private int toolbarHeight;
public ScrollingFABBehavior(Context context, AttributeSet attrs) {
super();
this.toolbarHeight = Utils.getToolbarHeight(context);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
return super.layoutDependsOn(parent, fab, dependency) || (dependency instanceof AppBarLayout);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
boolean returnValue = super.onDependentViewChanged(parent, fab, dependency);
if (dependency instanceof AppBarLayout) {
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
int fabBottomMargin = lp.bottomMargin;
int distanceToScroll = fab.getHeight() + fabBottomMargin;
float ratio = (float)dependency.getY()/(float)toolbarHeight;
fab.setTranslationY(-distanceToScroll * ratio);
}
return returnValue;
}
}
This is actually the class from the example's github repository, I have found it just after I coded the same myself and wanted to test it. They only forgot to update the blog post :-/
Have you tried this?
...
View view = inflater.inlfate(R.layout.my_layout, parent, false);
...
Snackbar.make(view.findViewById(R.id.fab),
adapter.getNewsList().get(position).getTitle(), Snackbar.LENGTH_LONG).show();
...
return view;
I assume that you call above code in Fragment, so I added view variable to call the findViewById() method.
Related
I have a Snackbar which is as follows:
However, if the drop down of the AutoCompleteTextView is too long, the drop down will block the Snackbar.
As you can see in the above image, the Snackbar is actually showing. However, its visibility is blocked by the long drop down. You can see from the above image
I try to use the following Snackbar code. Adding bringToFront() doesn't help much.
private void showSnackbar(String message) {
Snackbar snackbar
= Snackbar.make(getActivity().findViewById(R.id.content), message, Snackbar.LENGTH_LONG);
snackbar.getView().bringToFront();
snackbar.show();
}
R.id.content is a CoordinatorLayout:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/content"
android:background="?attr/MyActivityBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?attr/headerShadow" />
Is there any good way, to avoid Snackbar from being covered by AutoCompleteTextView's drop down?
I might have a solution for that case. Of course, there are some assumptions but maybe the solution would suit you.
The key here is putting AutoCompleteTextView inside CoordinatorLayout and add custom CoordinatorLayout.Behavior to it.
Create appropriate Behavior for your class:
public class AutoCompleteTextViewBehaviour extends CoordinatorLayout.Behavior<AutoCompleteTextView> {
public AutoCompleteTextViewBehaviour(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, AutoCompleteTextView child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
}
Override a method layoutDependsOn:
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, AutoCompleteTextView child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
Get reference to AutoCompleteTextView popup view:
Unfortunately I haven't found a simple solution for that. However can be done via reflection.
#Nullable
private View getPopupList(AutoCompleteTextView child) {
try {
Field popupField;
Class clazz;
if (child instanceof AppCompatAutoCompleteTextView) {
clazz = child.getClass().getSuperclass();
} else {
clazz = child.getClass();
}
popupField = clazz.getDeclaredField("mPopup");
popupField.setAccessible(true);
ListPopupWindow popup = (ListPopupWindow) popupField.get(child);
Field popupListViewField = popup.getClass().getDeclaredField("mDropDownList");
popupListViewField.setAccessible(true);
return (View) popupListViewField.get(popup);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
Override onDependentViewChanged method:
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, final AutoCompleteTextView child, View dependency) {
if (popupList == null) {
popupList = getPopupList(child);
if (popupList == null) {
return super.onDependentViewChanged(parent, child, dependency);
}
}
int dropdownBottom = child.getBottom() + child.getDropDownVerticalOffset() + popupList.getHeight();
int snackBarTop = dependency.getTop();
int difference = dropdownBottom - snackBarTop;
if (difference > 0) {
child.setDropDownHeight(popupList.getHeight() - difference);
return true;
} else {
child.setDropDownHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
}
return super.onDependentViewChanged(parent, child, dependency);
}
Apply the behavior to AutocompleteTextView in .xml:
app:layout_behavior="com.example.package.AutoCompleteTextViewBehaviour"/>
Of course this is a very basic solution, that for example does not animate list height, but I think this is a good start. Here is the full gist.
You can calculate and adjust the height of the popup as an alternative.
In the following picture, I am setting the dropdown height as bellow:
textView.viewTreeObserver.addOnGlobalLayoutListener {
textView.dropDownHeight = snackbarView.top - textView.bottom
}
This calculation is for the case where the suggestion list's height is long enough. You might want to set that property to WRAP_CONTENT instead.
As far as I know, it is not possible to change the "z-order" unless you add the SnackBar directly to the WindowManager. AutoCompleteTextView internally is using ListPopupWindow to show the suggestions popup and ListPopupWindow has the window type of WindowManager.LayoutParams.TYPE_APPLICATION_PANEL = 1000 which is higher than the Activity's Window type which is of WindowManager.LayoutParams.TYPE_APPLICATION = 2.
In my case I could get it to work with setZ:
Snackbar snackbar = Snackbar.make(container, text, Snackbar.LENGTH_LONG);
snackbar.getView().setZ(200);
snackbar.show();
Why not just set android:dropDownHeight to a fixed dp value? You can even define it to be dynamically based on screen size by reference a dimension resource. It is one line of code, it solves your problem, easy to maintain and to understand (in six month you will ask yourself, what the whole Behavior stuff is used for).
I think you have to take many thinks in consideration:
there must be space for all. keyboard, snackbar and autocomplete Textview
you cannot change the keyboard height (by your app), so you have to change the height of the autocomplete TextView to be above the snackbar (which is above the keyboard)
I am recently using CustomBottomSheetBehavior to make an googlemaps like bottom sheet behavior and it works great. I have only one problem.please look at this image
If I use it in a scrolling activity the content of tool-bar covers my list box. so I ahve to add margin-top to my list view. It works but when I draw bottomsheet up toolbar goes up and behind it, there is an empty space. This is because I have added some margin top to make my list's top visible. Is there any way to connect list's margin top to the amount of moving bottom-sheet and when It moves up decrease margin value to and when it moves down increase it?or is there any better way ?
It seems I have to develope my own TopMarginBehavior for this job but I have no idea how to do it.
thanks
Create your own class related to the behavior you want (MarginTopBehavior)
Extends it from CoordinatorLayout.Behavior
Now you have to focus on 2 methods: layoutDependsOn and onDependentViewChanged. With the first one you are selecting the view that your MarginTopBehavior is following, in this case is a NestedScrollView. With the second one you are reacting (the magic!) when the scroll get moved.
At this point you get this:
public class MarginTopBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
private FrameLayout.LayoutParams mLayoutParams;
public MarginTopBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof NestedScrollView;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
}
}
The logic to be applied in onDependentViewChanged is just this:
* Define the cap (min/max margin value) and controls when the margin value has reached one of those cap.
* Update margin value while the values are between the caps. In this point you have to implement an algorithm about what you want (parallax, linear, etc). That is what I'm calling THE_MAGIC_ECC in the next code:
public class MarginTopBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
/**
* Params of the component you want to modify the margin
*/
private FrameLayout.LayoutParams mLayoutParams;
/**
* Used to access DIMENS in your project
*/
private Context mContext;
private int mMinYvalue;
private int mMaxYValue;
public MarginTopBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof NestedScrollView;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (mLayoutParams == null) {
mLayoutParams = (FrameLayout.LayoutParams) child.getLayoutParams();
}
if (dependency.getY() <= mMinYvalue) {
mLayoutParams.setMargins(0, 0, 0, 0);
child.setLayoutParams(mLayoutParams);
return true;
}
else if (dependency.getY() > mMinYvalue && dependency.getY() <= mMaxYValue) {
int THE_MAGIC_ECC = 1 + 2 + 3;
mLayoutParams.setMargins(0, 0, 0, THE_MAGIC_ECC );
child.setLayoutParams(mLayoutParams);
return true;
}
else {
mLayoutParams.setMargins(0, 0, 0, 100);
child.setLayoutParams(mLayoutParams);
return true;
}
}
}
In the GIF below you'll see I'm having trouble coordinating (heh) an AppBarLayout, containing a CollapsingToolbarLayout, and a Persistent BottomSheet in such a way that they play nicely together
Goal: Have the fragment contents, seen above in turquoise (#26999f), remain above, yet scroll behind, the BottomSheet, seen above in dark green (#12783e), while also respecting the AppBarLayout and its Behavior
Again, as you can see from the GIF I'm close; the fragment contents is using a custom layout_behavior, MyScrollingViewBehavior, which extends AppBarLayout.ScrollingViewBehavior
In the code snippet below you'll see MyScrollingViewBehavior#layoutDependsOn returns true if the dependency is an instance of either a RelativeLayout (The BottomSheet in this example) or whatever super (AppBarLayout.ScrollingViewBehavior#layoutDependsOn) is depentent on
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof RelativeLayout ||
super.layoutDependsOn(parent, child, dependency);
}
Within MyScrollingViewBehavior#onDependentViewChanged if the dependency is an instance of a RelativeLayout, meaning the dependency is the BottomSheet, the child, aka fragment contents, is moved up or down using offsetTopAndBottom to ensure it remains above the BottomSheet
#TargetApi(VERSION_CODES.LOLLIPOP)
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (dependency instanceof RelativeLayout) {
RelativeLayout bottomSheet = (RelativeLayout) parent.findViewById(R.id.bottom_sheet);
int bottomSheetHeight = (bottomSheet.getTop() - child.getBottom());
ViewCompat.offsetTopAndBottom(child, bottomSheetHeight);
}
super.onDependentViewChanged(parent, child, dependency);
return false;
}
The full code sample can be found on Github here. Additionally, this question is a followup and hopeful improvement to my original question here
I have an ImageView in the top of the layout and a RecyclerView below it; That's all I need in my custom Behavior step 1; But, it doesn't seem to work well.
Here is the code:
Behavior.java
public class Depency extends CoordinatorLayout.Behavior<RecyclerView> {
private String TAG = "tag";
public Depency() {
}
public Depency(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, RecyclerView child, View
dependency) {
int delta = (int) (dependency.getTranslationY() + dependency.getBottom());
delta -= child.getTop();
child.offsetTopAndBottom(delta);
Log.i(TAG,
"onDependentViewChanged: " + delta + "," + child.getTop() + dependency.getClass().getSimpleName());
return true;
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, RecyclerView child, View dependency) {
return dependency instanceof AppCompatImageView;
}
}
layout.xml
<android.support.design.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"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="com.example.kenchan.fullypjo.MainActivity"
tools:showIn="#layout/activity_main">
<android.support.v7.widget.AppCompatImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="150dp"
android:scaleType="centerCrop"
android:src="#drawable/ic_girl"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.example.kenchan.fullypjo.view.Depency"/>
</android.support.design.widget.CoordinatorLayout>
The preview in xml:
seems work fine.
But when I run the project on my cell phone , I get wrong:
Could somebody help me or advise me with my code ?
The problem has bothered me for a while;
I tried to read source code in android.support.design.widget.AppBarLayout$ScrollingViewBehavior
and I found the code logic just at different nothing:
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
offsetChildAsNeeded(parent, child, dependency);
return false;
}
private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
final CoordinatorLayout.Behavior behavior =
((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
if (behavior instanceof Behavior) {
// Offset the child, pinning it to the bottom the header-dependency, maintaining
// any vertical gap and overlap
final Behavior ablBehavior = (Behavior) behavior;
ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
+ ablBehavior.mOffsetDelta
+ getVerticalLayoutGap()
- getOverlapPixelsForOffset(dependency));
}
}
RecyclerView is at top z-index, that's why you are seeing it as overlayed over the ImageView. In the preview you see it with only see 9 items.
You need to change RecyclerView top when inside your Behavior's onLayoutChild() method, to the bottom of the ImageView. This method gives you a chance to lay out the child yourself, instead of allowing CoordinatorLayout to do it deliberately. If you return true, CoordinatorLayout will not attempt to layout that view.
LinearLayout bottomSheetViewgroup = (LinearLayout) findViewById(R.id.bottomSheet);
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetViewgroup);
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); //this line
I have this code within my activity's onCreate() method and I'm getting the below NPE exception when the last line is executed:
Caused by: java.lang.NullPointerException:
Attempt to invoke virtual method 'java.lang.Object java.lang.ref.WeakReference.get()' on a null object reference
at android.support.design.widget.BottomSheetBehavior.setState(BottomSheetBehavior.java:440)
While Sanf0rds answer is correct, it doesn't allow the ability to define the BottomSheet as expanded by default. The issue is caused by the WeakReference not being set until the last line of onLayoutChild.
The solution is to provide our own class which extends BottomSheetBehavior, but setting the state inside an overridden onLayoutChild. The code is provided below.
uk/ac/qub/quibe/misc/ExpandedBottomSheetBehavior.java
package uk.ac.qub.quibe.misc;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by mcp on 15/03/16.
*/
public class ExpandedBottomSheetBehavior<V extends View> extends android.support.design.widget.BottomSheetBehavior<V> {
public ExpandedBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onLayoutChild(final CoordinatorLayout parent, final V child, final int layoutDirection) {
SavedState dummySavedState = new SavedState(super.onSaveInstanceState(parent, child), STATE_EXPANDED);
super.onRestoreInstanceState(parent, child, dummySavedState);
return super.onLayoutChild(parent, child, layoutDirection);
/*
Unfortunately its not good enough to just call setState(STATE_EXPANDED); after super.onLayoutChild
The reason is that an animation plays after calling setState. This can cause some graphical issues with other layouts
Instead we need to use setInternalState, however this is a private method.
The trick is to utilise onRestoreInstance to call setInternalState immediately and indirectly
*/
}
}
In the layout file reference reference your new custom behavior.
Change
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
To
app:layout_behavior="uk.ac.qub.quibe.misc.ExpandedBottomSheetBehavior"
public class ExpandedBottomSheetBehavior<V extends View> extends
android.support.design.widget.BottomSheetBehavior<V> {
public ExpandedBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onLayoutChild(final CoordinatorLayout parent, final V child, final int layoutDirection) {
return super.onLayoutChild(parent, child, layoutDirection);
}
#Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
try {
return super.onInterceptTouchEvent(parent, child, event);
} catch (NullPointerException ignored) {
return false;
}
}
}
The issue with your code is you are trying to call the setState method directly inside onCreate. This is will throw a nullPointer because the WeakReference is not initialized yet. It will get initialized when the Coordinator layout is about to lay its child view.
onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)
Called when the parent CoordinatorLayout is about the lay out the
given child view.
So the best approach is set the peek height to 0 and show/hide inside the onItemClick listener.
I have answered this question here:
https://stackoverflow.com/a/36236743/1314796
I've found a solution but I still don't known why this happen. The solution is put this last line to user call directly after the activity is running. Ex: in a contextMenu callback or in any OnClickListener.
You can also consider listening to the global layout event, this way you'll be sure that the bottomsheet has been laid out when setting the collapsed state.
final View bottomSheet = findViewById(R.id.bottom_sheet);
bottomSheet.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
bottomSheet.getViewTreeObserver().removeOnGlobalLayoutListener(this);
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
bottomSheetBehavior.setPeekHeight(300);
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
});