I've just installed the FutureSimple library for Floating Action Buttons, and I think it's just beautiful. Although it doesn't contain the disappear on scroll logic, I love it.
However, I would like to implement a Quick Return pattern and I'm looking for a simple way to do it.
Basically, I set up an ObservableListView listener like so :
listView.setScrollViewCallbacks(new ObservableScrollViewCallbacks() {
#Override
public void onScrollChanged(int i, boolean b, boolean b2) { }
#Override
public void onDownMotionEvent() { }
#Override
public void onUpOrCancelMotionEvent(ScrollState scrollState) { }
});
And I want to know if there's a simple way to make my button move (with a quick button.animate().translationY(xx)) with this listener.
So that whenever I scroll down, it disappears, and then it reappears when I scroll up.
I've looked at differents implementations, but I haven't quite understood the jist of them (especially the "official" fab library from makovkastar).
PS : I'm using the FutureSimple library because it has menus.
Thank you very much, in advance, for your much wanted help :) !
I solved my problem a few weeks ago, and I figured I should probably answer my own question here :)
After several days of research, I had found that the best way to implement this is by using the new RecyclerView introduced by Android. You can handle small pixel changes in scrolling with RecyclerView, but with ListView you would almost never be able to do that (only can handle change of "items" scrolled).
I've implemented a ScrollView Listener to my RecyclerView like this :
public abstract class HidingScrollListener extends RecyclerView.OnScrollListener {
private static final int HIDE_THRESHOLD = 20;
private int scrolledDistance = 0;
private boolean controlsVisible = true;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
//show views if first item is first visible position and views are hidden
if (firstVisibleItem == 0) {
if(!controlsVisible) {
onShow();
controlsVisible = true;
}
} else {
if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
onHide();
controlsVisible = false;
scrolledDistance = 0;
} else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
onShow();
controlsVisible = true;
scrolledDistance = 0;
}
}
if((controlsVisible && dy>0) || (!controlsVisible && dy<0)) {
scrolledDistance += dy;
}
}
public abstract void onHide();
public abstract void onShow();
This code does MORE than just show or hide FABs on scroll, it actually handles a pretty perfect "action bar disappears on scrolling" effect. But, I don't have time to suit this code to your needs, but it does work for our case, here.
To get back to our code, after creating our listener, we can add it to our FABs by implementing its interfaces :
recyclerView.setOnScrollListener(new HidingScrollListener() {
#Override
public void onHide() {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) fam.getLayoutParams();
int fabBottomMargin = lp.bottomMargin;
fam.animate().translationY(fam.getHeight()+fabBottomMargin).setInterpolator(new AccelerateInterpolator(2)).start();
}
#Override
public void onShow() {
toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2));
fam.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
}
});
And VoilĂ !
Related
I use a Floating Action Button in my project Android, and I hide it when the list scrolls to the bottom and I shows when it scrolls to the top, through the implementation of a OnScrollListener on my Recyclerview.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
boolean isSignificantDelta = Math.abs(dy) > mScrollThreshold;
if (isSignificantDelta) {
if (dy > 0) {
onScrollUp();
} else {
onScrollDown();
}
}
}
Now, I would like to hide this fab when my list is not scrollable, for that my last element is completely visible.
Scrolled's method is not called when my list is empty, or contains few items and is not scrollable because of his size.
Do you have a tip to call this method because this seems be my solution to do what I want to do ?
Check below code
public abstract class HideShowScrollListener extends RecyclerView.OnScrollListener {
private static final int HIDE_THRESHOLD = 20;
private int scrolledDistance = 0;
private boolean controlsVisible = true;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
onHide();
controlsVisible = false;
scrolledDistance = 0;
} else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
onShow();
controlsVisible = true;
scrolledDistance = 0;
}
if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) {
scrolledDistance += dy;
}
}
public abstract void onHide();
public abstract void onShow();
}
in your activity define below code,
here floatingAdd is floating button.
recyclerView.addOnScrollListener(new HideShowScrollListener() {
#Override
public void onHide() {
floatingAdd.animate().setInterpolator(new AccelerateDecelerateInterpolator()).scaleX(0).scaleY(0);
}
#Override
public void onShow() {
floatingAdd.animate().setInterpolator(new AccelerateDecelerateInterpolator()).scaleX(1).scaleY(1);
}
});
This doesn't seem like a right way to do, but I didn't found any better solution.
In getItemCount method I return array.size()+2 so recycler view creates two more items (that's just the right amount for me, you might need another number). In onCreateViewHolder method I check at what position view should be created. And if it's out of bounds of my array of items - I just disable all widgets inside (buttons, editTexts etc.) and set its visibility to invisible (not gone since I need it to occupy some space).
I also have callback onItemMove that is called when user reorders items with drag. Inside that I check that new position is not out of bounds of my array. Of course drag is disabled for the last two items.
So as I said, this is a workaround, but a very simple one. And I didn't notice any downsides of it. Hope this helps!
I have layout like that:
I would like to implement that functionality: When user ScrollDown RecyclerView two things which he see are Toolbar and FilterPanel. The title and image is hidden, when he scroll up till to the beggining of the recyclerview the title and image appear.
There is my HiddingScrollListener:
public abstract class HidingScrollListener extends RecyclerView.OnScrollListener {
private static final float HIDE_THRESHOLD = 10;
private int scrolledDistance = 0;
private boolean controlsVisible = true;
public HidingScrollListener() {
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
onHide();
controlsVisible = false;
scrolledDistance = 0;
} else if (scrolledDistance < -HIDE_THRESHOLD && controlsVisible) {
onShow();
controlsVisible = true;
scrolledDistance = 0;
}
if((controlsVisible && dy>0) || (!controlsVisible && dy<0)) {
scrolledDistance += dy;
}
}
public abstract void onShow();
public abstract void onHide();
}
And my implementation of this listener in activity:
mRecyclerView.addOnScrollListener(new HidingScrollListener() {
#Override
public void onShow() {
}
#Override
public void onHide() {
mTitle.setVisibility(View.GONE);
mImageView.setVisibility(View.GONE);
}
});
I don't know how to catch when RecyclerView is in the beggining and how to show title and image in this case. And I have a problem: How to pin FilterPanel under the Toolbar when I scroll down?
Anyway, thank you!
You should use the CoordinatorLayout from the android design support library.
This layout coordinates between it's children events, most commonly scroll events. Here is a tutorial: https://guides.codepath.com/android/Handling-Scrolls-with-CoordinatorLayout
I implemented a collapsingtoolbar layout with a recyclerview as shown in the sample code attached. My issue is that, when I fling the list downward, it does not go all the way to the top.
What happens is that, the scrolling stops right at the point where the AppBarLayout is supposed to end.
The effect that I want is upon flinging the list downward, the list will go all the way to the top AND reveal/expand the AppBarLayout
My minSdk is 14. Any help or suggestion is greatly appreciated.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout>
<android.support.design.widget.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<LinearLayout
app:layout_collapseMode="parallax">
//some elements
</LinearLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
app:layout_behavior="#string/appbar_scrolling_view_behavior"/> //value android.support.design.widget.AppBarLayout$ScrollingViewBehavior
<android.support.v7.widget.Toolbar
app:popupTheme="#style/AppTheme.PopupOverlay"
app:layout_collapseMode="parallax" />
I had similar problem and I used a simple trick to expand AppBarLayout when RecyclerView fling to top (you need to have support library >= 23.x.x)
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
int firstVisiblePosition = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
if (firstVisiblePosition == 0) {
mAppBarLayout.setExpanded(true, true);
}
}
}
});
You can fully expand or collapse the App Bar with the setExpanded() method. One implementation could involve overriding dispatchTouchEvent() in your Activity class, and auto-collapsing/expanding your App Bar based on whether it is collapsed past the halfway point:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
float per = Math.abs(mAppBarLayout.getY()) / mAppBarLayout.getTotalScrollRange();
boolean setExpanded = (per <= 0.5F);
mAppBarLayout.setExpanded(setExpanded, true);
}
return super.dispatchTouchEvent(event);
}
In respect to automatically scrolling to the last position on a fling, I have put some code on GitHub that shows how to programmatically smooth scroll to a specific location that may help. Calling a scroll to list.size() - 1 on a fling for instance could replicate the behaviour. Parts of this code by the way are adapted from the StylingAndroid and Novoda blogs:
public class RecyclerLayoutManager extends LinearLayoutManager {
private AppBarManager mAppBarManager;
private int visibleHeightForRecyclerView;
public RecyclerLayoutManager(Context context) {
super(context);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
View firstVisibleChild = recyclerView.getChildAt(0);
final int childHeight = firstVisibleChild.getHeight();
int distanceInPixels = ((findFirstVisibleItemPosition() - position) * childHeight);
if (distanceInPixels == 0) {
distanceInPixels = (int) Math.abs(firstVisibleChild.getY());
}
//Called Once
if (visibleHeightForRecyclerView == 0) {
visibleHeightForRecyclerView = mAppBarManager.getVisibleHeightForRecyclerViewInPx();
}
//Subtract one as adapter position 0 based
final int visibleChildCount = visibleHeightForRecyclerView/childHeight - 1;
if (position <= visibleChildCount) {
//Scroll to the very top and expand the app bar
position = 0;
mAppBarManager.expandAppBar();
} else {
mAppBarManager.collapseAppBar();
}
SmoothScroller smoothScroller = new SmoothScroller(recyclerView.getContext(), Math.abs(distanceInPixels), 1000);
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
public void setAppBarManager(AppBarManager appBarManager) {
mAppBarManager = appBarManager;
}
private class SmoothScroller extends LinearSmoothScroller {
private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
private final float distanceInPixels;
private final float duration;
public SmoothScroller(Context context, int distanceInPixels, int duration) {
super(context);
this.distanceInPixels = distanceInPixels;
float millisecondsPerPx = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
this.duration = distanceInPixels < TARGET_SEEK_SCROLL_DISTANCE_PX ?
(int) (Math.abs(distanceInPixels) * millisecondsPerPx) : duration;
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return RecyclerLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected int calculateTimeForScrolling(int dx) {
float proportion = (float) dx / distanceInPixels;
return (int) (duration * proportion);
}
}
}
Edit:
AppBarManager in the above code snippet refers to an interface used to communicate with the AppBarLayout in an Activity. Collapse/expand app bar methods do just that, with animations. The final method is used to calculate the number of RecyclerView rows visible on screen:
AppBarManager.java
public interface AppBarManager {
void collapseAppBar();
void expandAppBar();
int getVisibleHeightForRecyclerViewInPx();
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements AppBarManager{
#Override
public void collapseAppBar() {
mAppBarLayout.setExpanded(false, true);
}
#Override
public void expandAppBar() {
mAppBarLayout.setExpanded(true, true);
}
#Override
public int getVisibleHeightForRecyclerViewInPx() {
if (mRecyclerFragment == null) mRecyclerFragment =
(RecyclerFragment) getSupportFragmentManager().findFragmentByTag(RecyclerFragment.TAG);
int windowHeight, appBarHeight, headerViewHeight;
windowHeight = getWindow().getDecorView().getHeight();
appBarHeight = mAppBarLayout.getHeight();
headerViewHeight = mRecyclerFragment.getHeaderView().getHeight();
return windowHeight - (appBarHeight + headerViewHeight);
}
Id like to achieve a similar effect as the one you can see in Google Play store, where by scrolling the content the Toolbar goes off-screen as you scroll.
This works fine with the CoordinatorLayout (1) introduced at #io15, however: If you stop the scroll "mid-way" the Toolbar remains on screen, but is cut in half: I want it to animate off-screen, just like in the Google Play store. How can I achieve that?
Now the Android Support Library 23.1.0 has a new scroll flag SCROLL_FLAG_SNAP which allows you to achieve this effect.
AppBarLayout supports a number of scroll flags which affect how children views react to scrolling (e.g. scrolling off the screen). New to this release is SCROLL_FLAG_SNAP, ensuring that when scrolling ends, the view is not left partially visible. Instead, it will be scrolled to its nearest edge, making fully visible or scrolled completely off the screen.
Activity Layout file :
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize"
android:clipToPadding="false"/>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
</FrameLayout>
Now inside the activity, setup Toolbar and RecyclerView. Assign OnScrollListener to RecyclerView
recyclerView.setOnScrollListener(new MyScrollListener(this));
Extend MyScrollListerner from RecyclerView.OnScrollListener.
public abstract class MyScrollListener extends RecyclerView.OnScrollListener {
private static final float TOOLBAR_HIDE_THRESHOLD = 10;
private static final float TOOLBAR_SHOW_THRESHOLD = 70;
private int mToolbarOffset = 0;
private boolean mControlsVisible = true;
private int mToolbarHeight;
private int mTotalScrolledDistance;
public MyScrollListener(Context context) {
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
new int[]{R.attr.actionBarSize});
mToolbarHeight = (int) styledAttributes.getDimension(0, 0);
styledAttributes.recycle();
return toolbarHeight;
mToolbarHeight = Utils.getToolbarHeight(context);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
if(mTotalScrolledDistance < mToolbarHeight) {
setVisible();
} else {
if (mControlsVisible) {
if (mToolbarOffset > TOOLBAR_HIDE_THRESHOLD) {
setInvisible();
} else {
setVisible();
}
} else {
if ((mToolbarHeight - mToolbarOffset) > TOOLBAR_SHOW_THRESHOLD) {
setVisible();
} else {
setInvisible();
}
}
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
clipToolbarOffset();
onMoved(mToolbarOffset);
if((mToolbarOffset <mToolbarHeight && dy>0) || (mToolbarOffset >0 && dy<0)) {
mToolbarOffset += dy;
}
if (mTotalScrolledDistance < 0) {
mTotalScrolledDistance = 0;
} else {
mTotalScrolledDistance += dy;
}
}
private void clipToolbarOffset() {
if(mToolbarOffset > mToolbarHeight) {
mToolbarOffset = mToolbarHeight;
} else if(mToolbarOffset < 0) {
mToolbarOffset = 0;
}
}
private void setVisible() {
if(mToolbarOffset > 0) {
onShow();
mToolbarOffset = 0;
}
mControlsVisible = true;
}
private void setInvisible() {
if(mToolbarOffset < mToolbarHeight) {
onHide();
mToolbarOffset = mToolbarHeight;
}
mControlsVisible = false;
}
public abstract void onMoved(int distance);
public abstract void onShow();
public abstract void onHide();
}
Overriding the AppBarLayout seems to be a better solution, as there are two possible scroll-events - of the entire CoordinatorLayout, and of the RecyclerView/NestedScrollView
See this answer as a possible working code:
https://stackoverflow.com/a/32110089/819355
I want to achieve the scrolling technique used in Google Play Store app. The app portrays tabs below action bar. Once the content is scrolled up, it pushes the action bar up and finally hides it. At that point the tabs remain sticky at the top.
I tried the following code, but the flickering is there -
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
private static final int SCROLL_DIRECTION_UP = 1;
private static final int SCROLL_DIRECTION_DOWN = 2;
private int verticalDirection;
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState == RecyclerView.SCROLL_STATE_SETTLING) {
if (verticalDirection == SCROLL_DIRECTION_UP && getActivity().getActionBar().isShowing()) {
getActivity().getActionBar().hide();
} else if (newState == SCROLL_DIRECTION_DOWN && !getActivity().getActionBar().isShowing()) {
getActivity().getActionBar().show();
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
verticalDirection = SCROLL_DIRECTION_UP;
} else {
verticalDirection = SCROLL_DIRECTION_DOWN;
}
}
});
Also, I'm not using ACTION_BAR_OVERLAY as I didn't find any justification to use it in my use case.
Can anyone point me to any resource regarding this?
Now you can easily achieve this using the official Android Design library.
Check this example:
https://github.com/chrisbanes/cheesesquare