Given I'm using a layout like this:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/main_content"
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:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="#dimen/flexible_space_image_height"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:statusBarScrim="#android:color/transparent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/mainView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
<android.support.design.widget.FloatingActionButton
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginBottom="20dp"
app:fabSize="normal"
app:layout_anchor="#id/appbar"
app:layout_anchorGravity="bottom|center_horizontal"
/>
</android.support.design.widget.CoordinatorLayout>
Which is pretty much the standard Cheesesquare sample - except the FloatingActionButton, which I would like to move up by about 20dp.
However, this will not work. No matter if I use margin, padding etc - the button will always be centered at the edge of the anchor, like this:
How can I move the FAB up by 20dp as intended?
I suggest an elegant solution for you:
<android.support.design.widget.FloatingActionButton
...
android:translationY="-20dp"
...
/>
Try putting it in a linear layout that have padding:
<LinearLayout
width=".."
height=".."
paddingBottom="20dp"
app:layout_anchor="#id/appbar"
app:layout_anchorGravity="bottom|center_horizontal">
<android.support.design.widget.FloatingActionButton
android:layout_width="70dp"
android:layout_height="70dp"
app:fabSize="normal" />
</LinearLayout>
As there might be bugs in the design-support-lib concerning the CoordinatorLayout & margins, I wrote a FrameLayout that implements/copies the same "Behavior" like the FAB and allows to set a padding to simulate the effect:
Be sure to put it in the android.support.design.widget package as it needs to access some package-scoped classes.
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.widget.FrameLayout;
import com.company.android.R;
import java.util.List;
#CoordinatorLayout.DefaultBehavior(FrameLayoutWithBehavior.Behavior.class)
public class FrameLayoutWithBehavior extends FrameLayout {
public FrameLayoutWithBehavior(final Context context) {
super(context);
}
public FrameLayoutWithBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public FrameLayoutWithBehavior(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public FrameLayoutWithBehavior(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public static class Behavior extends android.support.design.widget.CoordinatorLayout.Behavior<FrameLayoutWithBehavior> {
private static final boolean SNACKBAR_BEHAVIOR_ENABLED;
private Rect mTmpRect;
private boolean mIsAnimatingOut;
private float mTranslationY;
public Behavior() {
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FrameLayoutWithBehavior child, View dependency) {
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FrameLayoutWithBehavior child, View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
this.updateFabTranslationForSnackbar(parent, child, dependency);
} else if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
if (this.mTmpRect == null) {
this.mTmpRect = new Rect();
}
Rect rect = this.mTmpRect;
ViewGroupUtils.getDescendantRect(parent, dependency, rect);
if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
if (!this.mIsAnimatingOut && child.getVisibility() == VISIBLE) {
this.animateOut(child);
}
} else if (child.getVisibility() != VISIBLE) {
this.animateIn(child);
}
}
return false;
}
private void updateFabTranslationForSnackbar(CoordinatorLayout parent, FrameLayoutWithBehavior fab, View snackbar) {
float translationY = this.getFabTranslationYForSnackbar(parent, fab);
if (translationY != this.mTranslationY) {
ViewCompat.animate(fab)
.cancel();
if (Math.abs(translationY - this.mTranslationY) == (float) snackbar.getHeight()) {
ViewCompat.animate(fab)
.translationY(translationY)
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
.setListener((ViewPropertyAnimatorListener) null);
} else {
ViewCompat.setTranslationY(fab, translationY);
}
this.mTranslationY = translationY;
}
}
private float getFabTranslationYForSnackbar(CoordinatorLayout parent, FrameLayoutWithBehavior fab) {
float minOffset = 0.0F;
List dependencies = parent.getDependencies(fab);
int i = 0;
for (int z = dependencies.size(); i < z; ++i) {
View view = (View) dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - (float) view.getHeight());
}
}
return minOffset;
}
private void animateIn(FrameLayoutWithBehavior button) {
button.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ViewCompat.animate(button)
.scaleX(1.0F)
.scaleY(1.0F)
.alpha(1.0F)
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
.withLayer()
.setListener((ViewPropertyAnimatorListener) null)
.start();
} else {
Animation anim = android.view.animation.AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in);
anim.setDuration(200L);
anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
button.startAnimation(anim);
}
}
private void animateOut(final FrameLayoutWithBehavior button) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ViewCompat.animate(button)
.scaleX(0.0F)
.scaleY(0.0F)
.alpha(0.0F)
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
.withLayer()
.setListener(new ViewPropertyAnimatorListener() {
public void onAnimationStart(View view) {
Behavior.this.mIsAnimatingOut = true;
}
public void onAnimationCancel(View view) {
Behavior.this.mIsAnimatingOut = false;
}
public void onAnimationEnd(View view) {
Behavior.this.mIsAnimatingOut = false;
view.setVisibility(View.GONE);
}
})
.start();
} else {
Animation anim = android.view.animation.AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out);
anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
anim.setDuration(200L);
anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() {
public void onAnimationStart(Animation animation) {
Behavior.this.mIsAnimatingOut = true;
}
public void onAnimationEnd(Animation animation) {
Behavior.this.mIsAnimatingOut = false;
button.setVisibility(View.GONE);
}
});
button.startAnimation(anim);
}
}
static {
SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}
}
}
Easy workaround is to anchor a random layout to where FAB was anchored, give it specific margin, and then anchor FAB to random layout, like this
<LinearLayout
android:orientation="horizontal"
android:id="#+id/fab_layout"
android:layout_width="5dp"
android:layout_height="5dp"
android:layout_marginRight="80dp"
android:layout_marginEnd="80dp"
app:layout_anchor="#id/collapsing_toolbar"
app:layout_anchorGravity="bottom|end"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/fab_margin"
android:src="#android:drawable/ic_dialog_map"
app:layout_anchor="#id/fab_layout"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
/>
To anchor the FloatingActionButton below the AppBar like this:
Extend the FloatingActionButton and override offsetTopAndBottom:
public class OffsetFloatingActionButton extends FloatingActionButton
{
public OffsetFloatingActionButton(Context context)
{
this(context, null);
}
public OffsetFloatingActionButton(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public OffsetFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
super.onLayout(changed, left, top, right, bottom);
ViewCompat.offsetTopAndBottom(this, 0);
}
#Override
public void offsetTopAndBottom(int offset)
{
super.offsetTopAndBottom((int) (offset + (getHeight() * 0.5f)));
}
}
I was able to get around this issue by using both layout_anchor layout_anchorGravity along with some padding.. The anchor attribute allows you to have a View position itself relative to another view. However, it doesn't exactly work in the same way that RelativeLayout does. More on that to follow.
layout_anchor specifies which View your desired View should be positioned (i.e., this View should be placed relative to the View specified by the layout_anchor attribute). Then, layout_anchorGravity specifies which side of the relative View the current View will be positioned, using the typical Gravity values (top, bottom, center_horizontal, etc.).
The issue with using just these two attributes alone is that the center of the View with the anchors will be placed relative to the other View. For example, if you specify a FloatingActionButton to be anchored to the bottom of a TextView, what really ends up happening is that the the center of the FAB is placed along the bottom edge of the TextView.
To get around this issue, I applied some padding to the FAB, enough such that the top edge of the FAB was touching the bottom edge of the TextView:
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_anchor="#id/your_buttons_id_here"
android:layout_anchorGravity="bottom"
android:paddingTop=16dp" />
You might have to increase the padding to get the desired effect. Hope that helps!
I used app:useCompatPadding="true" with FAB and everything worked.
Related
So I have following basic code which makes sure that my button goes up when a snackbar appears:
public class MoveUpwardBehavior extends CoordinatorLayout.Behavior<View> {
private static final boolean SNACKBAR_BEHAVIOR_ENABLED;
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
static {
SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
}
}
My customlinearlayout:
public class CustomLinearLayout extends LinearLayout implements CoordinatorLayout.AttachedBehavior {
MoveUpwardBehavior mb = new MoveUpwardBehavior();
public CustomLinearLayout(Context context) {
super(context);
}
public CustomLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#NonNull
#Override
public CoordinatorLayout.Behavior getBehavior() {
return mb;
}
}
And last but not least my layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:layout_behavior=".pkgActivity.MoveUpwardBehaviour"
tools:context=".pkgTestforend.DriverListFragment">
<ListView
android:id="#+id/listAllDrivers"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
<com.example.dochjavatestimplementation.pkgTestforend.CustomLinearLayout
android:layout_width="match_parent"
android:id="#+id/cusLL"
android:layout_height="match_parent"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/btnOpenDriverAddFragment2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/baseline_person_add_24"
/>
</RelativeLayout>
</com.example.dochjavatestimplementation.pkgTestforend.CustomLinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
This works perfectly fine, however the issue is that button remains up, when the snackbar gets dissmissed manually:
In order to solve the issue I tried the following:
Snackbar kd = Snackbar.make(customLinearLayout, "Text to display", Snackbar.LENGTH_LONG)
.addCallback(new Snackbar.Callback() {
#Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_SWIPE) {
btnOpenDriverAddFragment2.setTranslationY(90); //lower button down manually!
}
}
#Override
public void onShown(Snackbar snackbar) {
}
});
kd.show();
However, this doesnt work very well, as the snackbar seems still be visible/button gets covered by the dissmissed snackbar?
Why is it so, that the snackbar remains basicallyvisible but dissmissed?
Changing btnOpenDriverAddFragment2.setTranslationY(90); with customLinearLayout.setTranslationY(0); will solve your issue.
Since not only the button gets moved up but also the custom linear layout which is also the parent of the button u just need to reset the y position of the parent.
U just change the y value of the button, but u forgot about the linearlayout.
I have following CoordinatorLayout behavior :
public class FooterBarBehavior extends CoordinatorLayout.Behavior<FooterBarLayout> {
//Required to instantiate as a default behavior
public FooterBarBehavior() {
}
//Required to attach behavior via XML
public FooterBarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
//This is called to determine which views this behavior depends on
#Override
public boolean layoutDependsOn(CoordinatorLayout parent,
FooterBarLayout child,
View dependency) {
//We are watching changes in the AppBarLayout
return getDependentView((ViewPager) dependency) instanceof AppBarLayout;
}
//This is called for each change to a dependent view
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent,
FooterBarLayout child,
View dependency) {
int offset = -getDependentView((ViewPager) dependency).getTop();
child.setTranslationY(offset);
return true;
}
private View getDependentView(ViewPager viewPager) {
int index = viewPager.getCurrentItem();
MainPagerAdapter adapter = ((MainPagerAdapter)viewPager.getAdapter());
ContactsFragment fragment = (ContactsFragment) adapter.getItem(index);
if(fragment.getView() == null) {
return null;
}
return fragment.getView().findViewById(R.id.appBarLayout);
}
}
Here is FooterBarLayout :
#CoordinatorLayout.DefaultBehavior(FooterBarBehavior.class)
public class FooterBarLayout extends RelativeLayout {
public FooterBarLayout(Context context) {
super(context);
}
public FooterBarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FooterBarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
It has been used in the layout as follow:
<?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="wrap_content"
android:layout_height="wrap_content"
tools:context=".ui.MainActivity">
<androidx.viewpager.widget.ViewPager
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.sample.android.contact.widget.FooterBarLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.sample.android.contact.widget.ListenableTabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:background="#drawable/rectangle_shape"
app:tabIndicatorHeight="2dp"
app:tabSelectedTextColor="#color/color1" />
<ImageView
android:id="#+id/triangle"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_above="#+id/tab_layout"
android:layout_marginBottom="#dimen/dimen_triangle_bottom_margin"
android:src="#drawable/triangle_shape"
tools:ignore="ContentDescription" />
</com.sample.android.contact.widget.FooterBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
I expect that FooterBarLayout follow AppBarLayout in the fragment and hide/show based on scroll direction. The idea is taken from : https://github.com/devunwired/coordinated-effort/blob/master/app/src/main/java/com/example/android/coordinatedeffort/behaviors/FooterBarBehavior.java
But there will be no scrolling in FooterBarLayout. Do you guys have any idea to resolve this?
I am having a nestedscrollview with content like some linearlayouts and textviews.
I am using a floatingactionbutton library for some reasons, as well. So I can't use any behavior for it.
I don't know how I should handle the scrollchangelistener from scrollview to hide and show the fab dynamically like a behavior.
Any suggestions how to hide and show the fab while scrolling?
Simple add this code below to your NestedScrollView ScrollChangeListener:
NestedScrollView nsv = v.findViewById(R.id.nsv);
nsv.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY > oldScrollY) {
fab.hide();
} else {
fab.show();
}
}
});
Create FabScrollBehavior class
public class FabScrollBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
private int toolbarHeight;
public FabScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.toolbarHeight = AppUtil.getToolbarHeight(context);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View 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 true;
}
}
Where AppUtil.getToolbarHeight(context) is -
public static int getToolbarHeight(Context context) {
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
new int[]{R.attr.actionBarSize});
int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
styledAttributes.recycle();
return toolbarHeight;
}
then in your layout add to FloatingActionButton layout_behavior:
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab_task_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_accepted"
app:layout_behavior="pass.to.your.FabScrollBehavior.Class"
app:theme="#style/Widget.AppTheme.Fab"/>
The whole layout looks like
<?xml version="1.0" encoding="utf-8"?>
<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:animateLayoutChanges="true"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/Widget.AppTheme.AppBarOverlay">
<include
layout="#layout/include_layout_toolbar_scroll"/>
</android.support.design.widget.AppBarLayout>
<include layout="#layout/include_layout_content_with_nestedscroll"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab_task_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_accepted"
app:layout_behavior="pass.to.FabScrollBehavior.Class"
app:theme="#style/Widget.AppTheme.Fab"/>
</android.support.design.widget.CoordinatorLayout>
Implemented from https://mzgreen.github.io/2015/02/15/How-to-hideshow-Toolbar-when-list-is-scroling(part1)/
define variable type int in your Activity or fragment to set previous Scroll from ScrollView then use this method to listen change scroll in ScrollView Class
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
// previousScrollY this variable is define in your Activity or Fragment
if (scrollView.getScrollY() > previousScrollY && floatingActionButton.getVisibility() == View.VISIBLE) {
floatingActionButton.hide();
} else if (scrollView.getScrollY() < previousScrollY && floatingActionButton.getVisibility() != View.VISIBLE) {
floatingActionButton.show();
}
previousScrollY = scrollView.getScrollY();
}
});
will work done all version of android
After spending such time i have found the solution for it.
It may work in all situations. Though it is a hack not the proper solution but you can apply it to make this thing work.
As we know setOnScrollChangeListener will only work if minimum api 23, so what if my minimum api level is less then 23.
So I found out solution from stack overflow that we can use getViewTreeObserver().addOnScrollChangedListener for that so this will be compatible solution for all devices.
Now let's move to the final solution of over problem "Hide fab button when nested scroll view scrolling and Show fab button when nested scroll view in ideal state"
So for that we can use Handler with postDelayed to slove this issue.
Define on variable in you context private int previousScrollY = 0;
Then use getViewTreeObserver().addOnScrollChangedListener to your nested scroll view like this.
NESTEDSCROLLVIEW.getViewTreeObserver().addOnScrollChangedListener(new
ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (NESTEDSCROLLVIEW.getScrollY() == previousScrollY) {
FABBUTTON.setVisibility(View.VISIBLE);
} else {
FABBUTTON.setVisibility(View.INVISIBLE);
}
}
}, 10);
previousScrollY = NESTEDSCROLLVIEW.getScrollY();
}
});
Now you are ready to go....
You can use this listener to observe and hide FAB when scrolling.
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (nestedScrollView != null) {
if (nestedScrollView.getChildAt(0).getBottom() <= (nestedScrollView.getHeight() + nestedScrollView.getScrollY())) {
fab.setVisibility(View.INVISIBLE);
} else {
fab.setVisibility(View.VISIBLE);
}
}
}
});
I am working with new FloatingActionButton and CoordinatorLayout in design support library. When I try to add custom Coordinator Behavior on FloatActionButton it work perfect on Lollipop+, but on Pre-Lollipop there are some weird gap margin on FloatActionButton
My main activity layout
<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"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="#android:drawable/ic_dialog_email"
app:layout_behavior="com.test.fabdemo.FloatingActionButtonBehavior" />
</android.support.design.widget.CoordinatorLayout>
with custom CoordinatorLayout behavior
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
public FloatingActionButtonBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton button, View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton floatingActionButton, View dependency) {
if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) floatingActionButton.getLayoutParams();
int bottomMargin = lp.bottomMargin;
int distanceToScroll = floatingActionButton.getHeight() + bottomMargin;
float ratio = ViewCompat.getY(appBarLayout) / (float) appBarLayout.getTotalScrollRange();
ViewCompat.setTranslationY(floatingActionButton, -distanceToScroll * ratio);
return true;
}
return false;
}
}
Result in emulator 4.0.3 with and without behavior
After some research I found out that FAB's margin is managed by default Behavior, so make custom Behavior inherit from FloatActionButton.Behavior will solve the problem
public class FloatingActionButtonBehavior extends FloatingActionButton.Behavior {
public FloatingActionButtonBehavior(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton button, View dependency) {
return dependency instanceof AppBarLayout || super.layoutDependsOn(parent, button, dependency);
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton floatingActionButton, View dependency) {
if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) floatingActionButton.getLayoutParams();
int bottomMargin = lp.bottomMargin;
int distanceToScroll = floatingActionButton.getHeight() + bottomMargin;
float ratio = ViewCompat.getY(appBarLayout) / (float) appBarLayout.getTotalScrollRange();
ViewCompat.setTranslationY(floatingActionButton, -distanceToScroll * ratio);
return true;
}
return super.onDependentViewChanged(parent, floatingActionButton, dependency);
}
}
I want to achieve parallax scrolling in android without using external library.Is there anyway to achieve this same as in google play store.
Thanks
To implement that in a RecyclerView:
public class MyScrollListener extends RecyclerView.OnScrollListener {
private int totalScrollDistance = 0;
private View parallax;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
totalScrollDistance += dy;
if (parallax == null) {
parallax = recyclerView.getChildAt(0);
}
parallax.setTranslationY(totalScrollDistance/2);
}
}
Setup:
mRecyclerView.setOnScrollListener(new MyScrollListener());
To implement that in a ScrollView, you have to implement a OnScrollListener:
public class MyScrollView extends ScrollView {
private int lastScrollY;
private OnScrollListener listener;
public void setOnScrollListener(OnScrollListener listener) {
this.listener = listener;
}
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
int scrollY = getScrollY();
if (lastScrollY != scrollY) {
lastScrollY = scrollY;
// update when lastScrollY != scrollY and keep sending message till lastScrollY == scrollY
handler.sendMessageDelayed(handler.obtainMessage(), 5);
}
if (listener != null) {
listener.onScroll(scrollY);
}
}
};
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
lastScrollY = getScrollY();
if (listener != null) {
listener.onScroll(lastScrollY);
}
if (ev.getAction() == MotionEvent.ACTION_UP) {
//might still scrolling after touching, use a handler to handle it
handler.sendMessageDelayed(handler.obtainMessage(), 5);
}
return super.onTouchEvent(ev);
}
public interface OnScrollListener {
void onScroll(int dy);
}
}
Then do what we did in OnScrollListener of RecyclerView:
scrollView = (MyScrollView) findViewById(R.id.my_scrollview);
final View parallax = ((ViewGoup) scrollView.getChildAt(0)).getChildAt(0);
scrollView.setOnScrollListener(new MyScrollView.OnScrollListener() {
#Override
public void onScroll(int dy) {
parallax.setTranslationY(dy/2);
}
});
For me the following method worked out .
In your android layout mention the below code:`
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:background="#color/white"
>
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
>
//place the widgets that you want to collapse .....(here i have used an
ImageView)
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="#+id/imgViewFirst"
android:src="#drawable/firstImage"
android:scaleType="fitXY"/>
</LinearLayout>
</android.support.design.widget.CollapsingToolbarLayout>
//place the widgets that you want to pin after scrolling up.....
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#color/lightGrey"/>
</android.support.design.widget.AppBarLayout>
//place the widgets you want to show after you scrolled upwards...(here i have
used an ImageView)
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="#+id/imgViewSecond"
android:src="#drawable/secondImage"
android:scaleType="fitXY"/>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
</LinearLayout>`