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" />
Related
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();
}
}
});
I get 2 types of data with Firebase and with RadioButton I sort the displayed data. Everything seems to work, but still there is an empty space when one of the data types is hidden. Tell me how to hide / show data correctly.
That's what I get
Adapter:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int
viewType) {
switch (viewType) {
case Person.PersonType.TYPE_1:
View userType1 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.driver_row, parent, false);
return new PersonType1ViewHolder(userType1);
case Person.PersonType.TYPE_2:
View userType2 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fare_row, parent, false);
return new PersonType2ViewHolder(userType2);
}
return super.onCreateViewHolder(parent, viewType);
}
#Override
protected void populateViewHolder(RecyclerView.ViewHolder viewHolder,
Person model,
int position) {
if (viewHolder instanceof PersonType1ViewHolder) {
((PersonType1ViewHolder) viewHolder).time.setText("Full time: " +
model.getTime());
if (activate) {
viewHolder.itemView.setVisibility(View.GONE);
} else {
viewHolder.itemView.setVisibility(View.VISIBLE);
}
return;
}
if (viewHolder instanceof PersonType2ViewHolder) {
((PersonType2ViewHolder) viewHolder).time.setText("Full time2: " +
model.getTime());
if (activate2) {
viewHolder.itemView.setVisibility(View.GONE);
} else {
viewHolder.itemView.setVisibility(View.VISIBLE);
}
private class PersonType1ViewHolder extends RecyclerView.ViewHolder {
TextView time;
PersonType1ViewHolder(View itemView) {
super(itemView);
time = itemView.findViewById(R.id.tv_time);
}
}
private class PersonType2ViewHolder extends RecyclerView.ViewHolder {
TextView time;
PersonType2ViewHolder(View itemView) {
super(itemView);
time = itemView.findViewById(R.id.tv_time_two);
}
}
Row layouts are the same - wrap_content
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
MainActivity XML
<RelativeLayout 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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_trip_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/radio_button_group" />
<Button
android:id="#+id/add_trip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:onClick="addTrip"
android:text="add" />
<RadioGroup
android:id="#+id/radio_button_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#id/add_trip"
android:layout_centerHorizontal="true"
android:orientation="horizontal">
<RadioButton
android:id="#+id/rb_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="all" />
<RadioButton
android:id="#+id/rb_driver"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="driver" />
<RadioButton
android:id="#+id/rb_fare"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fare" />
</RadioGroup>
</RelativeLayout>
I don’t know how correct this is, but I just programmatically set the parameters
viewHolder.itemView.setLayoutParams(new RecyclerView.LayoutParams(0, 0));
I also encountered issue like this, turns out it's the problem with the override method getItemCount() in the adapter. I guess if you specified the number of getItemCount(), RecyclerView will reserve a place for the ViewHolder even if you hide it. This is the code of how i solve it.
override fun getItemViewType(position: Int): Int {
when {
!importantImages.isNullOrEmpty() && !otherImages.isNullOrEmpty() -> {
return when (position) {
0 -> Important.ordinal
else -> Other.ordinal
}
}
otherImages.isNullOrEmpty() -> {
return Important.ordinal
}
importantImages.isNullOrEmpty() -> {
return Other.ordinal
}
}
return 0
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (ViewType.values()[viewType]) {
Important -> {
val layoutInflater = LayoutInflater
.from(parent.context)
.inflate(R.layout.layout_important_list, parent, false)
ImportantViewHolder(layoutInflater)
}
Other -> {
val layoutInflater = LayoutInflater
.from(parent.context)
.inflate(R.layout.layout_other_list, parent, false)
OtherViewHolder(layoutInflater)
}
}
}
override fun getItemCount(): Int = when {
!importantImages.isNullOrEmpty() && !otherImages.isNullOrEmpty() -> {
2
}
importantImages.isNullOrEmpty() && otherImages.isNullOrEmpty() -> {
0
}
else -> {
1
}
}
Hope it helps! :)
I have this problem too, I found that if add a container outside with wrap_content, then it works.
<FrameLayout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/v_root"
android:layout_width="match_parent"
android:layout_height="48dp">
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
In java code:
v_root.setVisibility(View.GONE);
v_root in code means your old view root.
1.Why does it happen?
viewHolder.itemView.setVisibility(View.GONE); This will not work in RecyclerView.
itemView is a child view in RecyclerView. Unlike the other ViewGroup (FrameLayout,etc), RecyclerView ignores childen's visibility during layout process.
2.How to solve it?
Option1 Use a regular view group such as FrameLayout to wrap your real layout, and hide your real layout: findViewById(R.id.layout_to_hide).setVisibility(View.GONE)
<FrameLayout android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout android:id="#+id/layout_to_hide"
android:layout_width="match_parent"
android:layout_height="wrap_content">
//Put here your views
</LinearLayout>
</FrameLayout>
Option2 Change the itemView’s heigh to 0 so we can't see it
holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(0, 0));
Option3 If this item shouldn't be in List, then simply don't let it in adapter data, or remove it.
3.Learn more
1. Why the other view group such as FrameLayout works fine with children's visibibilty?
They don't compute the child's layout params if it's GONE
2. Why RecyclerView doesn't?
RecyclerView still compute the child's layout params if it's GONE so it still occupies spaces.
The question is not very clear.
But I can assure you that with View.GONE, the view becomes invisible without leaving spaces. While with View.INVISIBLE, the view leaves an invisible space.
If it doesn't work, it means that something is wrong with your layout.
I have an Activity that contains a Fragment. Fragment contains a RecyclerView, displaying CardViews.
CardView is very simple, just a TextView and a CustomView. This is the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="100dp">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="100dp"
android:id="#+id/card_current_heat_cardview"
android:layout_gravity="center_vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:weightSum="4">
<TextView
android:id="#+id/card_current_heat_textview"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textStyle="bold"
android:textColor="#000000"
android:background="#android:color/holo_red_dark"
android:gravity="center_vertical"/>
<com.amige.vm2.CurrentHeatChartView
android:id="#+id/card_current_heat_chart_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"/>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
TextView should use 1/4 of the width, and the CustomView 3/4.
Im having trouble understanding why weights are ignored the first time the RecyclerView loads
Image of RecyclerView loaded for the first time
But if I scroll up and down, the layout works as expected for some Cards
Image of RecyclerView scrolled up and down
I also have another Activity (no fragment involved here) that has a RecyclerView and IS using this same CardView Layout and it works perfectly!!
I dont know if it has to do with the Fragment...
What am I missing?
Thanks!
EDIT
This the Adapter code
public class MainAdapter extends RecyclerView.Adapter<MyFragment.MainAdapter.ViewHolder>
{
public MainAdapter()
{
}
#Override
public MyFragment.MainAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_current_heat, parent, false);
return new MyFragment.MainAdapter.ViewHolder(v);
}
#Override
public void onBindViewHolder(MyFragment.MainAdapter.ViewHolder holder, int position)
{
if (arrayList.size() == 2)
{
if (position == 0)
{
holder.variableNameTextView.setText(“Var Name”);
holder.currentHeatChartView.reloadChartWithSamples(arrayList.get(0));
}
else
{
if ((position - 1) < arrayList.get(1).size())
{
HashMap variableHashMap = (HashMap) arrayList.get(1).get(position - 1);
holder.variableNameTextView.setText(variableHashMap.get("VarName") != null ? (String)variableHashMap.get("VarName") : "");
holder.currentHeatChartView.reloadChartWithVariableValuesAndColors(variableHashMap, colorGradientsArrayList.get((position - 1) % colorGradientsArrayList.size()));
}
}
}
}
#Override
public int getItemCount()
{
if (arrayList.size() == 2)
{
if (arrayList.get(1).size() > 0)
{
return 1 + arrayList.get(1).size();
}
else
{
return 1;
}
}
return 0;
}
public class ViewHolder extends RecyclerView.ViewHolder
{
TextView variableNameTextView;
CurrentHeatChartView currentHeatChartView;
public ViewHolder(View v)
{
super(v);
this.variableNameTextView = (TextView) v.findViewById(R.id.card_current_heat_textview);
this.currentHeatChartView = (CurrentHeatChartView) v.findViewById(R.id.card_current_heat_chart_view);
}
}
}
I found the culprit.
There was a ConstraintLayout on top of the hierarchy of the Activity containing the Fragment. Since I was working with nested layouts, ConstraintLayout was not the right way to go.
I changed it to RelativeLayout and the cards are now rendered properly.
I think your layout inflation is wrong.
View view = View.inflate(mContext, R.layout.your_layout, null);
If you do like that change your code like this.
View view = LayoutInflater.from(mContext).(R.layout.your_layout, parent, false);
Hope it helps:)
I am trying to implement scrollable list of buttons.
As shown in screenshot above, there is 3 buttons shown on the screen. I am trying create a list of buttons, that user can scroll them 3 at a time.
I tried 2 ways:
1- View Pager: I created 1 fragment for each 3 buttons.
The scroll works perfectly as I wanted, but when i need to add new button, it is a real overhead work.
2- RecyclerView: Which is works well but doesnt scroll 3 at a time.
So i want to show on screen only 3 items. When user scroll, it will scroll to the next 3 items.
Is it possible to do this with RecyclerView (which is easier to add to it)?
One solution could be using an adapter which shows 3 elements per item, then use the PageSnapHelper to get the paging effect:
The Adapter:
public class ButtonsRecyclerAdapter extends RecyclerView.Adapter<ButtonsRecyclerAdapter.ButtonsViewHolder> {
private List<Button> mButtons = new ArrayList<>();
#Override
public ButtonsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_buttons, null);
return new ButtonsViewHolder(layoutView);
}
#Override
public void onBindViewHolder(ButtonsViewHolder holder, int position) {
int startPosition = getItemCount() * position;
int endPosition = startPosition + 3;
for (int i = startPosition; i < endPosition || i < mButtons.size(); i++) {
Button neededButton = holder.getButton(i - position);
neededButton.setVisibility(View.VISIBLE);
//Do whant you want to do with it
}
}
#Override
public int getItemCount() {
return null != mButtons ? ((int) Math.ceil((double) mButtons.size() / 3)) : 0;
}
public class ButtonsViewHolder extends RecyclerView.ViewHolder {
Button mButtonFirst;
Button mButtonSecond;
Button mButtonThird;
public ButtonsViewHolder(View itemView) {
super(itemView);
mButtonFirst = (Button) itemView.findViewById(R.id.button_first);
mButtonSecond = (Button) itemView.findViewById(R.id.button_second);
mButtonThird = (Button) itemView.findViewById(R.id.button_third);
}
Button getButton(int index) {
switch (index) {
case 0:
return mButtonFirst;
case 1:
return mButtonSecond;
case 2:
return mButtonThird;
}
return null;
}
}
}
The list item layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="#+id/button_first"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"/>
<Button
android:id="#+id/button_second"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"/>
<Button
android:id="#+id/button_third"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"/>
</LinearLayout>
I'm doing a GridView (not dynamic), that contains 13 values and I want to put it on two columns and it looks like :
And this is my code :
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="5dp"
card_view:cardUseCompatPadding="true" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_gravity="center"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tvCard"
android:text="A"
android:layout_marginRight="#dimen/_7sdp"
android:textSize="#dimen/_27sdp"
android:layout_centerVertical="true"
android:layout_toLeftOf="#+id/ivCard"
android:layout_toStartOf="#+id/ivCard" />
<ImageView
android:id="#+id/ivCard"
android:layout_width="#dimen/_20sdp"
android:layout_height="#dimen/_28sdp"
android:src="#mipmap/ic_launcher"
android:scaleType="centerCrop"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
And I used Span why isn't putting it on the middle?
GridLayoutManager recycleLayoutManager = new GridLayoutManager(mContext, 2,GridLayoutManager.VERTICAL, false);
recycleLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
// grid items to take 1 column
if (mList.size() % 2 == 0) {
return 1;
} else {
return (position == mList.size()-1) ? 2 : 1;
}
}
});
Here it is a sample project implementing exactly your scenario.
Basically you have to wrap your cards with a layout having width to be defined programmatically in onCreateViewHolder method of the adapter.
In a 2 columns grid, width will be half of parent.
I faced the same problem : I have a GridLayout with two columns and an odd number of elements. The last element must be centered.
I solved it using a custom SpanSizeLookup and putting some margins on the last element :
In the RecyclerView / Activity :
val layoutManager = GridLayoutManager(context, 2)
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup()
{
override fun getSpanSize(position: Int): Int
{
return if (position == 0 || isLastRowAndShouldBeCentered(position)) 2 else 1
}
}
myRecyclerView.layoutManager = layoutManager
And in the Adapter :
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder
{
// Save the width of the container
this.containerWidth = parent.measuredWidth
// Create your ViewHolder here
}
override fun onBindViewHolder(holder: ViewHolder, position: Int)
{
...
/**
* This margin will be exactly 1/4 of the container width.
* This way, the element will be displayed as follows :
* [ BLANK 1/4 SPACE ] [ ELEMENT 1/2 WIDTH ] [ BLANK 1/4 SPACE ]
*/
if (isLastRowAndShouldBeCentered(position))
{
val params = viewHolder.view.layoutParams as GridLayoutManager.LayoutParams
val marginSize = this.containerWidth / 4 // Re-use the container width here
params.leftMargin = marginSize
params.rightMargin = marginSize
viewHolder.view.layoutParams = params
}
}
Obviously, isLastRowAndShouldBeCentered is yours to implement (returns a Boolean indicating whether we are on the last element and we have an odd number of elements)
Hope it can help someone !