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
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!
in my application for show and hide some widgets i found this below code on this site, but i cant implementing that on my fragment, for example:
public abstract class HidingScrollListener extends RecyclerView.OnScrollListener {
private static final int HIDE_THRESHOLD = 20;
private int mScrolledDistance = 0;
private boolean mControlsVisible = true;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
if (firstVisibleItem == 0) {
if(!mControlsVisible) {
onShow();
mControlsVisible = true;
}
} else {
if (mScrolledDistance > HIDE_THRESHOLD && mControlsVisible) {
onHide();
mControlsVisible = false;
mScrolledDistance = 0;
} else if (mScrolledDistance < -HIDE_THRESHOLD && !mControlsVisible) {
onShow();
mControlsVisible = true;
mScrolledDistance = 0;
}
}
if((mControlsVisible && dy>0) || (!mControlsVisible && dy<0)) {
mScrolledDistance += dy;
}
}
public abstract void onHide();
public abstract void onShow();
}
My Fragment:
public class FragmentMarketDetail extends Fragment implements ObservableHorizontalScrollView.OnScrollListener {
private ScrollView scrollViewTest;
private Context context;
public static FragmentMarketDetail newInstance() {
FragmentMarketDetail fragmentFirst = new FragmentMarketDetail();
return fragmentFirst;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_online_categories, container, false);
scrollViewTest = (ScrollView) view.findViewById(R.id.scrollViewTest);
scrollViewTest.setOnScrollChangeListener(context);
return view;
}
#Override
public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY) {
Log.e("-----> onScrollChanged", x + "");
}
#Override
public void onEndScroll(ObservableHorizontalScrollView scrollView) {
Log.e("----->onEndScroll ", "");
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
context = activity;
}
}
I get error for this line: scrollViewTest.setOnScrollChangeListener(context);
You cannot set a onScrollChangedListener to a ScrollView directly below API23.
you may however use this
scrollView.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int scrollX = rootScrollView.getScrollX(); //for horizontalScrollView
int scrollY = rootScrollView.getScrollY(); //for verticalScrollView
//DO SOMETHING WITH THE SCROLL COORDINATES
}
});
see onScrollListener for a ScrollView
Edit
scrollViewTest.setOnScrollChangeListener(context);
In the above line you are attempting to set the Context as a listener which is not possible, as you Fragment implements ObservableHorizontalScrollView.OnScrollListener you should call
scrollViewTest.setOnScrollChangeListener(this);
Recyclerview comes with its own scroll listener which has the following methods :
void onScrollStateChanged(RecyclerView recyclerView, int newState)
Callback method to be invoked when RecyclerView's scroll state changes.
void onScrolled(RecyclerView recyclerView, int dx, int dy)
Callback method to be invoked when the RecyclerView has been scrolled.
Is there any way to trigger loader to load more data when scroll reaches end of the list?
I have implemented this way:
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
GenerItem generItem=generItems.get(i);
Log.d("TAG","position "+i);
if(i==generItems.size()-1)
((GenerSearchActivity)mContext).onScroll(i);
viewHolder.bindValues(generItem);
}
Here onScroll() in Activity, will trigger the loader to load more data. What is the best way please suggest.
Make Next Call at the end of scroll
There are essentially 3 steps.
Notify when the list is scrolled
Make REST call (for NEXT page)
Add the result in the old list + Notify DataSet change
CALLBACK
But first, we need a Callback which would work as a bridge between RecyclerView.Adapter and Activity
public interface PaginationCallBack{
public void oadNextPage();
}
Implement this callback in Your Activity
class YourActivity extends AppCompatActivity implements PaginationCallBack{
int pageNum = 1;
#Override
public void loadNextPage(){
// Your implementation
}
}
Initialize Callback in RecyclerView.Adapter
class YourAdapter extends RecyclerView.Adapter{
private PaginationCallBack paginationCallBack;
public YourAdapter(PaginationCallBack paginationCallBack) {
this.paginationCallBack = paginationCallBack;
}
}
STEP 1
Add a condition in onBindViewHolder method and notify with a Callback.
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewholder, int position) {
if(position+1==images.size()){
paginationCallBack.loadNextPage(); // Callback
}
}
Step 2: Call NEXT Page
getImages(++pageNum) // YOUR REST method which page number
#Override
public void loadNextPage(){
getImages(++pageNumber) // REST call with next Page
}
Step 3 Add the result in old list and notify datasetChanged
public void getImages(int pageNum){
List<Images> newResults = //RESTCALL
imageList.addAll(newResults);
adapter.updateDataSet(imageList)
}
Where is updateDataSet(imageList) method?
Write this method inside RecyclerView.Adapter
public void updateDataSet(List<GalleryMedia> newImages){
if(newImages!=null){
images = newImages;
}
notifyDataSetChanged();
}
Full Code
RecyclerView Pagination
Result:
Here's a scroll listener which implements load more and quick return pattern:
public abstract class HidingScrollListener extends RecyclerView.OnScrollListener {
private static final float HIDE_THRESHOLD = 10;
private static final float SHOW_THRESHOLD = 70;
private int mToolbarOffset = 0;
private boolean mControlsVisible = true;
private int mToolbarHeight;
private int mTotalScrolledDistance;
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 4;
int firstVisibleItem, visibleItemCount, totalItemCount;
private LinearLayoutManager layoutManager;
public HidingScrollListener(Context context, LinearLayoutManager layoutManager) {
mToolbarHeight = Tools.getFooterHeight(context);
this.layoutManager = layoutManager;
}
#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 > HIDE_THRESHOLD) {
setInvisible();
}
else {
setVisible();
}
}
else {
if ((mToolbarHeight - mToolbarOffset) > 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;
}
// for load more
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// End has been reached
// Do something
loading = true;
onLoadMore();
}
}
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();
public abstract void onLoadMore();
}
And it's implementation is below:
HidingScrollListener scrollListener = new HidingScrollListener(activity, manager) {
#Override
public void onMoved(int distance) {
}
#Override
public void onShow() {
}
#Override
public void onHide() {
}
#Override
public void onLoadMore() {
// you can do your pagination here.
}
};
mRecyclerView.addOnScrollListener(scrollListener);
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