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!
Related
I have a problem regarding RecyclerView duplicating its items on scroll to top.
My RecyclerView populates its View by taking values from an online database on scroll down to a certain threshold. I am well aware of RecyclerView's behavior on reusing the Views, but I don't want that to happen as it'll get confusing due to having Views with different items inside.
I've searched around SO for the solution. Some said that I have to override getItemId like :
#Override
public long getItemId(int id) {
return id;
}
But they don't elaborate more on that.
Tried using setHasStableIds(true); but it's not working. When I scroll down to populate the RecyclerView, then scroll quickly back up, the first item still shows the last item I scrolled to, or any other random item.
I have this in onBindViewHolder :
if(loading)
{
// Do nothing
}
else {
((ObjectViewHolder) holder).progressBar.setVisibility(View.GONE);
((ObjectViewHolder) holder).postListWrapper.setVisibility(View.VISIBLE);
Uri userImageUri = Uri.parse(mDataset.get(position).author_avatar);
...
// The rest of the code
}
Does it have to do with the error I'm getting? The loading is changed to false when the Fragment containing RecyclerView finished getting value from the database.
Here's the RecyclerView onScrollListener :
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstVisibleItem = manager.findFirstCompletelyVisibleItemPosition();
if(firstVisibleItem < 1 )
{
swipeRefreshLayout.setEnabled(true);
}else
{
swipeRefreshLayout.setEnabled(false);
}
totalItemCount = manager.getItemCount();
lastVisibleItem = manager.findLastVisibleItemPosition();
int visibleThreshold = 2;
if(isLoading == false)
{
if (totalItemCount <= lastVisibleItem + visibleThreshold) {
if(lobiAdapter.getItemCount() > 0)
{
if (lobiAdapter.getItemCount() < 5)
{
setIsLoaded();
}else{
// End has been reached
// Do something
PostListAPI postListAPI = new PostListAPI();
postListAPI.query.user_id = userId;
postListAPI.query.post_count = String.valueOf(counter);
postListAPI.query.flag = "load";
NewsFeedsAPIFunc newsFeedsAPIFunc = new NewsFeedsAPIFunc(BottomLobiFragment.this.getActivity());
newsFeedsAPIFunc.delegate = BottomLobiFragment.this;
setIsLoading();
newsFeedsAPIFunc.execute(postListAPI);
}
}
else {
setIsLoaded();
}
}
}
}
});
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've seen some apps with a RecyclerView and a Floating Action Button and when you scroll down the RecyclerView the Floating Action Button will scroll off from the bottom or just minimize and disappear. I have created a RecyclerView and a FloatingActionButton in a CoordinatorLayout using the design support library. But the FloatingActionButton doesn't do anything when I scroll. Is there a way to add an attribute in the layout XML file to achieve this, or can I write some Java codes to do this? And how?
for doing this you have to create custom RecyclerView.OnScrollListener class..
here is what I did.
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);
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();
And in your MainActivity
rv.setOnScrollListener(new HidingScrollListener() {
#Override
public void onShow() {
fam.animate().translationY(0)
.setInterpolator(new DecelerateInterpolator(2)).start();
}
#Override
public void onHide() {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) fam
.getLayoutParams();
int fabMargin = lp.bottomMargin;
fam.animate().translationY(fam.getHeight() + fabMargin)
.setInterpolator(new AccelerateInterpolator(2)).start();
}
});
Where rv would be your RecyclerView and fam would be your FAB.
Hope it will help you.
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'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Ă !