I'm developing an android app with Android Studio and I'm having problems using a floating button on top of a recycler view.
What I want to achive is to have a floating button on top of my recycler view and when I scroll to the bottom of the recycler view the button should not cover the last item on my list.
If I add padding or margin to my recycler view this is what i get:
And without padding nor margin this is the result:
My goal is to get this when scrolling to the last item:
And this when I am not in the last item:
This is the code I currently have:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
tools:context=".frontend.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/activityList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animationCache="true"
android:background="#color/white"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/addRoutine"
style="#style/Widget.AppCompat.Button"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="#drawable/round_button"
app:iconPadding="0dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I would really appreciate if someone could tell how this is achived.
Thank you!
Simply attach ItemDecoration with the recycler view to achieve this.
Step 1: Add this Item Decoration class
class BottomItemDecoration : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
if(parent.getChildAdapterPosition(view) == state.itemCount - 1) // Check if it's the last item
outRect.bottom = 80 // Space(in pixels) to give after the last item for that floating button
}
}
Step 2: Attach BottomItemDecoration with the recycler view
recyclerView.addItemDecoration(BottomItemDecoration())
No change is required in the layout XML file.
I think you can create 2 ViewType in your RecyclerView's Adapter with one is the actual content, the another is a blank one. In the Adapter, you override the method getItemViewType(int position) (This method is used to decide which view holder to use). Note that: in this method, we only return the second view holder (the blank one) when the Recycler view scrolls to the last item (position == yourList.size() -1). Here an example:
#Override
public int getItemViewType(int position) {
if (position == yourList.size() - 1)
return CONTENT_VIEW_TYPE; //the actual content View Holder
else return BLANK_VIEW_TYPE; //the blank View Holder
}
I think you can use this approach,
possibly duplicate: How to allow scrolling the last item of recyclerview above of the FAB?
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy){
if (dy > 0) {
fab.hide();
return;
}
if (dy < 0) {
fab.show();
}
}
});
Related
I have simple grid layout with 2 spans, and my RecyclerView item layout looks like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="#+id/categoryCardView"
android:layout_width="170dp"
android:layout_height="100dp"
app:cardBackgroundColor="#color/colorPrimary"
app:cardCornerRadius="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
What I want is basically even space of all sides, so I set Item Decoration like this:
recylcerView.addItemDecoration(SpacingItemDecorator(16))
class SpacingItemDecorator (private val padding: Int): RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
// outRect.right = padding
// outRect.top = padding * 2
outRect.top = padding
outRect.right = padding/2
outRect.left = padding/2
}
}
But as I expected, I'm getting the wrong behavior. The top/bottom is okay, but if for example, I pass 16 padding to SpacingItemDecorator(as I said I've 2 items in a row), the first item will get 8 on the left as expected, but between the item, space will be 16 (8 from the right of the first item, 8 from the left of the second item).
But the weirdest thing is that the second items also get 16 from the right, instead of just 8(right padding)
My questions are:
Why my second item in the row will get 16 padding from the right instead of just 8 as the first item get from the left?
How can I set equal padding to all sizes?
To get 8dp padding for every RecyclerView item You can set RecyclerView padding to 4dp:
<androidx.recyclerview.widget.RecyclerView
android:padding="4dp"
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
/>
Now to Your ItemDecorator add this:
class SpacingItemDecorator (private val padding: Int) : RecyclerView.ItemDecoration()
{
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
)
{
super.getItemOffsets(outRect, view, parent, state)
outRect.top = padding
outRect.bottom = padding
outRect.left = padding
outRect.right = padding
}
}
And now in onCreate (or wherever You set RecylcerView) You can add this item decorator with padding set to 4:
val x = (resources.displayMetrics.density * 4).toInt() //converting dp to pixels
recyclerView.addItemDecoration(SpacingItemDecorator(x)) //setting space between items in RecyclerView
Result:
Just set your cardview width layout to Match parent, with a margin of 8dp for example. And a height with wrap_content. When dealing with recyclerViews. It's better avoid setting a specific measurement for width and height. Trust me, it will be way easier, and look better.
P.s. setting the dimension of a layout inside your activity/fragment has an effect on performance. Its also better to announce/define the UI inside you xml file.
I am using GridLayoutManager for RecyclerView.
There are conditions where i need to draw 1, 2, 3 or 4 items per row. Like following.
I am able to achieve this by doing following.
//RecyclerView
override fun getItemViewType(position: Int): Int {
if(mDataList?.get(position)?.totalColumns == 5){
return COLUMNS_5
}
else if(mDataList?.get(position)?.totalColumns == 4){
return COLUMNS_4
}
}
On Fragment class
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
//If there are 6 items in 1 row, then return 1
switch(homeTabLayoutRecyclerAdapter.getItemViewType(position)){
case COLUMNS_5:
return 5;
case COLUMNS_4:
return 4;
The issue is, i am able to get the layout, i shared above, but i need to center all of the items. Like following:
Following is my recyclerview code.
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvItems"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/guideline32" />
I am creating a simple view where on the top I have some elements and below a recyclerView. When I scroll it down, would like to scroll the whole screen, not the only recycler.
I have achieved it with NestedScrollView, however, now the problem appears. Items in the list will be pretty heavy and in this configuration, all the items are bind at the same time(call of onBindViewHolder).
any ideas how to make them recycle and solve this problem?
Here is my xml file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView 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"
android:background="#android:color/darker_gray"
android:orientation="vertical"
tools:context="com.gkuziel.testkotlin.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/ic_available_stores_default" />
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="test text" />
<android.support.v7.widget.RecyclerView
android:id="#+id/list_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:isScrollContainer="false"
android:nestedScrollingEnabled="false">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
Update:
The found a sweet solution: you add a complex header as ItemDecoration, its great cause your adapter can stay untouched, you just add sth like this:
recyclerView.addItemDecoration(dividerItemDecoration);
the only drawback of this solution is i couldn't make this header clickable (in my case it contains another recyclerView), however I know some people achieved it as well.
For this moment I decided implement heterogeneous recyclerview, with 1 instance of header type and the rest of simple row types.
What is important, the header type is fully binded once in HeaderViewHolder constructor and onBindViewHolder looks like this:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is HeaderViewHolder) {
//do nothing
Log.d("ProductAdapter", "Binding: Header")
} else if (holder is ItemViewHolder) {
Log.d("ProductAdapter", "Binding: " + position.toString())
val searchItem = items!![position - 1]
//here the proper binding is going on
}
}
You can try setting the recyclerview layout manager's method canScrollVertical to false and it won't respond to any touch inner scroll events.
override below method and return false.
boolean canScrollVertically()
here it is how to set.
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
// Lookup the recyclerview in activity layout
RecyclerView listTest = (RecyclerView) findViewById(R.id.list_test);
// Attach the adapter to the recyclerview to populate items
listTest.setAdapter(adapter);
// Set layout manager to position the items
listTest.setLayoutManager(new LinearLayoutManager(this){
#Override
public boolean canScrollVertically(){
return false;
}
});
// That's all!
}
I have construction where there are two items which are static and recyclerview with draggable items. Both types of view are wrapped by LinearLayout. LinearLayout is wrapped by NestedScrollView.
XML explanation:
<NestScrollView>
<LinearLayout>
<View></View>
<View></View>
<RecyclerView></RecyclerView>
</LinearLayout>
</NestScrollView>
Pic explanation:
Implementation of dragging items I took from this tutorial: https://medium.com/#ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd#.yc7me2ikf
The problem is the RecyclerView doesn't scroll when I drag his children out of screen. (However if I don't have NestedScrollView as a parent, RecyclerView works as expected.)
I searched every problem related to this but I didn't find any solution.
Putting recyclerView inside NestedScrollView is not a good practice. I recommend you to use your other views as RecyclerView items. I'm sure that will fix your problem as you've said "However if I don't have NestedScrollView as a parent, RecyclerView works as expected."
Make the NestedScrollView and RecyclerView play nice together by adding android:fillViewport="true" to the recycler
When you start to drag your item or once you reach the edge of the screen you can also turn off the recycler's nested behaviour with mRecyclerView.setNestedScrollingEnabled(false). This will cause the nested view itself to scroll so that the user continues down the recyclerview.
Once you're finished set nested scrolling to true again.
Alternatively you will need to look at delegating who gets the touch events like I outlined for another question here. For your sample it might be as simple as:
When drag starts call nestedScrollView.requestDisallowInterceptTouchEvent(true);
When drag ends requestDisallowInterceptTouchEvent(false) to reenable scrollview
If the requestDisallowInterceptTouchEvent fails to work, then you can create a subclass of NestedScrollView and override onInterceptTouchEvent like on my linked answer
I have faced the same problem. I use the other views as the recycler view.
First of all we have to return the two views in getItemViewType().
public int getItemViewType(int position)
{
if(position == 0)
{
return HEADERVIEW;
}
return LISTVIEW;
}
Now, we are adding the other view as the HeaderView in the list so we are going to tweak the list count:
#Override public int getItemCount()
{
return arrayList.size()+1;
}
Now bind the views correctly in onBindViewHolder on the basis on the getItemViewType()
#Override public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewHolder, int i)
{
if(viewHolder.getItemViewType() == HEADERVIEW )
{
HeaderViewHolder headerViewHolder = (HeaderViewHolder)viewHolder;
headerViewHolder.headertextview.SetText("LongText");
}
else
{
MyHolder myHolder = (MyHolder) viewHolder;
myHolder.name.setText(arrayList.get(i-1).name);
}
Now in the Item Touch Listener Callback we have to tweak the onMove() method:
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
// get the viewHolder's and target's positions in your adapter data, swap them
if(viewHolder.getItemViewType() != target.getItemViewType())
{
return false;
}
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if(dragFrom == -1)
{
dragFrom = fromPosition;
}
dragTo = toPosition;
if(dragFrom != -1 && dragTo != -1 && dragFrom != dragTo)
{
reallyMoved(dragFrom, dragTo);
dragFrom = dragTo = -1;
}
// and notify the adapter that its dataset has changed
adapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
Now if all the logic is right then we move the item and call onreallyMoved() method:
private void reallyMoved(int dragFrom, int dragTo)
{
if(dragFrom == 0 || dragTo == arrayList.size())
{
return;
}
Collections.swap(arrayList, dragFrom-1, dragTo-1);
}
If you want to know more have a look at my blog post.
Another solution is to use a linearLayout instead the NestedScrollView. At its root, put the recyclerView, with parameter : sethasFixedSize(true) in your Java file, and with match_parent height as well in xml.
recap :
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RecyclerView
...
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
don't forget to set recyclerView.setHasFixedSize(true); in your Java file
Go with something like:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.v4.widget.NestedScrollView
android:id="#+id/view_noRecord"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<TextView
android:id="#+id/tv_no_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginBottom="#dimen/_20sdp"
android:gravity="center_horizontal"
android:padding="#dimen/_10sdp"
android:text="#string/no_data_found"
android:visibility="gone"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.v4.widget.NestedScrollView>
<android.support.v7.widget.RecyclerView
android:id="#+id/common_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadeScrollbars="true"
android:scrollbars="vertical"
android:visibility="gone"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</FrameLayout>
Or try to this put content from your layout as a header of your RecyclerView
<LinearLayout>
<View></View>
<View></View>
</LinearLayout>
I'm implementing a "parallax" effect which consists of a list view and an image view above.
When the user scrolls downwards, the offset is subtracted from the height of the image view. This works fine. I'm doing this in the scroll listener of the list view. To really update the listview and imageview in realtime I need to call requestLayout().
The problem is, that this method is asynchronous and if I scroll slowly it flickers extremely. If I just swipe quickly it looks very nice. Is there an other solution to solve this?
ScrollListener
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
View c = view.getChildAt(0);
int top = (c == null) ? 0 : (-c.getTop() + view.getFirstVisiblePosition() * c.getHeight());
//Log.d(TAG, "Offset = " + top);
mImageView.getLayoutParams().height = 400 - top;
mImageView.requestLayout();
}
Layout
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context="xxx.fragments.NewsFragment">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#android:color/holo_blue_light"
android:id="#+id/advertisement"
/>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/news_feed">
</ListView>
</TableLayout>
If you just want the ImageView to scroll with the list, then you can simply add it as a header view:
listView.addHeaderView(mImageView);
Make sure you call this method before calling setAdapter. Also make sure your ImageView doesn't already have a parent, so inflate it separately or just create one in code using new ImageView.