How to center horizontal android recycle ListView - android

I am adding items in listview horizontally but if i have single item i want it to be in center horizontally.
I tried many solution from stack overflow but still help less.
I tried using relative layout(centerInParent, centerHorizontally)
gravity, layout_gravity property etc.
Please help.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:weightSum="10"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/noUser_layout"
android:orientation="vertical"
android:layout_weight="10"
android:visibility="gone"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/noMessageLogo"
android:src="#mipmap/eapp_icon"/>
<TextView android:text="#string/loading_Message" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp"
android:id="#+id/noMessage"
android:gravity="center"
android:fontFamily="sans-serif"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#android:color/black" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="#+id/parentLayout"
android:orientation="vertical"
android:layout_weight="3.8"
android:gravity="center|center_horizontal">
<TextView android:text="Parent" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:id="#+id/parentText"
android:gravity="center"
android:fontFamily="sans-serif"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#android:color/holo_blue_dark" />
<android.support.v7.widget.RecyclerView
android:id="#+id/parentList"
android:layout_width="wrap_content"
android:layout_centerHorizontal="true"
android:layout_height="wrap_content"
/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content" android:layout_height="0dp"
android:layout_gravity="center"
android:layout_weight="2.5"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:id="#+id/contactLayout"
android:layout_centerHorizontal="true"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:id="#+id/userImage"
android:layout_gravity="center"
android:src="#drawable/user_icon" />
<TextView android:text="Contact Name" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:id="#+id/name"
android:fontFamily="sans-serif"
android:textSize="15sp"
android:textStyle="bold"
android:layout_gravity="left"
android:textColor="#android:color/black"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="#+id/childrenLayout"
android:layout_centerHorizontal="true"
android:layout_weight="3.8"
android:orientation="vertical"
android:gravity="center|center_horizontal">
<TextView android:text="Child" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/childText"
android:gravity="center"
android:layout_margin="10dp"
android:fontFamily="sans-serif"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#android:color/holo_blue_dark" />
<android.support.v7.widget.RecyclerView
android:id="#+id/childList"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|center_horizontal" />
</LinearLayout>
</LinearLayout>

Use this layout manager on your RecyclerView
public class WrapContentLinearLayoutManager extends LinearLayoutManager {
public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}

I have used Window manager to get total width of screen and used to set to recycle view.
The following Layout Manager is for horizontal recycle view i.e. LinearLayoutManager
public class WrapHeightLinearLayoutManager extends LinearLayoutManager {
Context mContext;
public WrapHeightLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
mContext = context;
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
//width = width + mMeasuredDimension[0];
if (i == 0) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int widthWin = size.x;
int heightWin = size.y;
width = widthWin;
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
Logger.getInstanceLogger().printVerbose("WrapHeightLinearLayoutManager","width ==>"+width+" height==>"+height);
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}

This simple example solved my problem :)
A summarised code is here:
public class SpinnerWheelActivity extends Activity {
private static final int NUM_ITEMS = 3;
private static final String BUNDLE_LIST_PIXELS = "allPixels";
private float itemWidth;
private float padding;
private float firstItemWidth;
private float allPixels;
private int mLastPosition;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_spinner_wheel);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
itemWidth = getResources().getDimension(R.dimen.item_width);
padding = (size.x - itemWidth) / 2;
firstItemWidth = getResources().getDimension(R.dimen.padding_item_width);
allPixels = 0;
final RecyclerView items = (RecyclerView) findViewById(R.id.item_list);
LinearLayoutManager shopItemslayoutManager = new LinearLayoutManager(getApplicationContext());
shopItemslayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
items.setLayoutManager(shopItemslayoutManager);
// to center item
items.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
synchronized (this) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
calculatePositionAndScroll(recyclerView);
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
allPixels += dx;
}
});
ExtraItemsAdapter adapter = new ExtraItemsAdapter(NUM_ITEMS);
items.setAdapter(adapter);
if (NUM_ITEMS > 0) // init position
scrollListToPosition(items, 0);
}
#Override
protected void onResume() {
super.onResume();
//final RecyclerView items = (RecyclerView) findViewById(R.id.item_list);
//ViewTreeObserver vto = items.getViewTreeObserver();
//vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
// #Override
// public void onGlobalLayout() {
// items.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// calculatePositionAndScroll(items);
// }
//});
}
private void calculatePositionAndScroll(RecyclerView recyclerView) {
int expectedPosition = Math.round((allPixels + padding - firstItemWidth) / itemWidth);
// Special cases for the padding items
if (expectedPosition == -1) {
expectedPosition = 0;
} else if (expectedPosition >= recyclerView.getAdapter().getItemCount() - 2) {
expectedPosition--;
}
scrollListToPosition(recyclerView, expectedPosition);
}
private void scrollListToPosition(RecyclerView recyclerView, int expectedPosition) {
float targetScrollPos = expectedPosition * itemWidth + firstItemWidth - padding;
float missingPx = targetScrollPos - allPixels;
if (missingPx != 0) {
recyclerView.smoothScrollBy((int) missingPx, 0);
}
}
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
allPixels = savedInstanceState.getFloat(BUNDLE_LIST_PIXELS);
}
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putFloat(BUNDLE_LIST_PIXELS, allPixels);
}
/*---------- Adapters ----------*/
public class ExtraItemsAdapter extends RecyclerView.Adapter<ViewHolder> {
private static final int VIEW_TYPE_PADDING = 1;
private static final int VIEW_TYPE_ITEM = 2;
private final int mNumItems;
public ExtraItemsAdapter(int numItems) {
mNumItems = numItems;
}
#Override
public int getItemCount() {
return mNumItems + 2; // We have to add 2 paddings
}
#Override
public int getItemViewType(int position) {
if (position == 0 || position == getItemCount() - 1) {
return VIEW_TYPE_PADDING;
}
return VIEW_TYPE_ITEM;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
if (viewType == VIEW_TYPE_ITEM) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
return new ViewHolder(v);
} else {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_padding, parent, false);
return new ViewHolder(v);
}
}
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
// We bind the item to the view
holder.text.setText(String.valueOf(position));
}
}
}
/*---------- ViewHolders ----------*/
public class ViewHolder extends RecyclerView.ViewHolder {
TextView text;
public ViewHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(R.id.item_text);
}
}
}

Related

android-recycleview inside scrollview

In my activity , I want to put a slideShow above my page and then put recycleview after that, to do so ,I'v written this code in my layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
android:orientation="vertical"
android:padding="6dp">
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
card_view:cardBackgroundColor="#fff"
card_view:cardCornerRadius="3dp"
card_view:cardElevation="0dp">
<cn.trinea.android.view.autoscrollviewpager.AutoScrollViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="250dp" />
</android.support.v7.widget.CardView>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
the problem with this code is that when I run the activity , only the recycleview scrolls , I don't want that, I want if I scroll down ,the whole page scrolls down not just the recycle .
How can I make the whole page scroll ?
Three steps to do this:
Replace your scrollview with NestedScrollView with layout behaviour set to app:layout_behavior="#string/appbar_scrolling_view_behavior"
Keep all your widgets under a linear/relative layout(because nested scroll view accepts only one child)
3.create CustomLinearLayoutManager.java with this code:
public class CustomLinearLayoutManager extends LinearLayoutManager {
public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
set adapter to your recyclerview like this:
CustomLinearLayoutManager customLinearLayoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
updatesRecyclerView.setHasFixedSize(true);
updatesRecyclerView.setLayoutManager(customLinearLayoutManager);
updatesRecyclerView.setAdapter(yourAdapter);
updatesRecyclerView.setNestedScrollingEnabled(false);
That's it , it works.

Center a CardView in a RecyclerView with only one element

I'm using a RecyclerView that contains CardViews with a TextView and a ImageView (Every card represents a city). I also have a onClickListener on every card that leads me to a list of museum in the city. (The RecyclerView is populated by an ArrayList).
The list is a RecyclerView composed by the same Cardview that scrolls vertically.
When a city has only one museum, how can I display the unique CardView at the center of the screen?
This is the activity xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.bebbo203.mymuseum.MuseumActivity">
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/recyclerViewMuseum"
android:scrollbars="vertical"
android:scrollIndicators="none"
android:gravity="center_horizontal"
/>
</RelativeLayout>
And this is the RecyclerView xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
xmlns:card_view="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="150dp"
android:id="#+id/cardView"
card_view:cardCornerRadius="2dp"
card_view:cardUseCompatPadding="true"
android:gravity="center_horizontal"
android:animateLayoutChanges="true"
>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="150dp"
android:id="#+id/imageViewList"
android:layout_gravity="center_horizontal|top"
android:adjustViewBounds="true"
android:scaleType="centerCrop"/>
<TextView
android:id="#+id/textViewList"
android:layout_width="match_parent"
android:layout_height="150dp"
android:textSize="40sp"
android:textIsSelectable="false"
android:textAlignment="center"
android:gravity="fill"
android:textStyle="bold"
android:layout_weight="1"
android:layout_gravity="center_horizontal|top"/>
</FrameLayout>
</android.support.v7.widget.CardView>
</RelativeLayout>`
Thanks for helping.
So this is the MainActivity. A list of Cities. All is good here.
When I click on Parigi that has only one museum I wanted to show the single cardview at the center of the screen
(And if it's possible i would like to make the cardview starting from the center of the screen, not from the top. Like if the central cardview is always at the center when I open the activity. For example translating NationalGallery at the center mantaining the order of the other)
I've implemented simple HelloWorld app, which shows list of cities and based on how many museums it has - shows full-sized city-card or the centered, wrapped version of it.
(Yes, I'm not exactly good at arts :-) )
Here's how I did it.
TL;DR:
The crucial part is ItemDecoration: set proper items offset and you'll get what you need; Here's how I've done it:
RecyclerView recyclerViewMuseum = (RecyclerView)findViewById(R.id.recyclerViewMuseum);
recyclerViewMuseum.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
recyclerViewMuseum.setAdapter(adapter);
recyclerViewMuseum.addItemDecoration(new RecyclerView.ItemDecoration() {
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (view instanceof CityWithOneMuseumCardView) {
int totalWidth = parent.getWidth();
int cardWidth = getResources().getDimensionPixelOffset(R.dimen.small_card_width);
int sidePadding = (totalWidth - cardWidth) / 2;
sidePadding = Math.max(0, sidePadding);
outRect.set(sidePadding, 0, sidePadding, 0);
}
}
});
Here's my model - City and Museum classes:
public class Museum {
public String title;
public Museum(String title) {
this.title = title;
}
}
public class City {
public String title;
public int imageRes;
public List<Museum> museums = new ArrayList<>();
public City(String title, int imageRes) {
this.title = title;
this.imageRes = imageRes;
}
}
Then Views: CityWithManyMuseumsCardView and CityWithOneMuseumCardView. Both of them are using helper-interface IItemDisplayer.
public class CityWithOneMuseumCardView extends CardView implements IItemDisplayer<City> {
public CityWithOneMuseumCardView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.one_museum_layout, this);
}
#Override
public void displayItem(City city) {
TextView cityTitleTextView = (TextView)findViewById(R.id.cityTitleTextView);
cityTitleTextView.setText(city.title);
}
}
public class CityWithManyMuseumsCardView extends CardView implements IItemDisplayer<City> {
public CityWithManyMuseumsCardView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.many_museums_layout, this);
}
#Override
public void displayItem(City city) {
ImageView cityBackgroundImageView = (ImageView)findViewById(R.id.cityBackgroundImageView);
cityBackgroundImageView.setImageResource(city.imageRes);
TextView cityTitleTextView = (TextView)findViewById(R.id.cityTitleTextView);
cityTitleTextView.setText(city.title);
TextView cityNumberOrMuseumsTextView = (TextView)findViewById(R.id.cityNumberOrMuseumsTextView);
cityNumberOrMuseumsTextView.setText(String.valueOf(city.museums.size()));
}
}
public interface IItemDisplayer<TItem> {
public void displayItem(TItem item);
}
And their layouts :
<!-- One Museum card -->
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#BB2050AB"
android:layout_width="#dimen/small_card_width"
android:layout_height="200dp">
<TextView
android:background="#AA000000"
android:textColor="#FFFFFF"
android:text="Only one museum available"
android:textSize="16sp"
android:padding="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/cityTitleTextView"
android:layout_gravity="bottom"
android:background="#AAFFFFFF"
android:textColor="#000000"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="48dp" />
</FrameLayout>
<!-- Many museums card -->
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="240dp">
<ImageView
android:id="#+id/cityBackgroundImageView"
android:scaleType="fitXY"
android:layout_width="500dp"
android:layout_height="match_parent" />
<TextView
android:id="#+id/cityNumberOrMuseumsTextView"
android:layout_gravity="top|end"
android:background="#AA000000"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:padding="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/cityTitleTextView"
android:layout_gravity="bottom"
android:background="#AA000000"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="48dp" />
</FrameLayout>
Then we need to create an adapter for our RecyclerView
CityAdapter.java
public class CityAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
final static int ITEM_TYPE_MANY_MUSEUMS = 0;
final static int ITEM_TYPE_ONE_MUSEUM = 1;
private List<City> items;
public CityAdapter(List<City> items) {
this.items = items;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
switch (viewType) {
case ITEM_TYPE_MANY_MUSEUMS:
return new ViewHolder(new CityWithManyMuseumsCardView(viewGroup.getContext()));
case ITEM_TYPE_ONE_MUSEUM:
return new ViewHolder(new CityWithOneMuseumCardView(viewGroup.getContext()));
default:
throw new IllegalArgumentException(String.format("Unexpected viewType: %d", viewType));
}
}
#Override
public int getItemViewType(int position) {
if (items == null || items.size() < position) {
throw new IllegalArgumentException("Wrong position!");
}
if (items.get(position).museums.size() > 1) {
return ITEM_TYPE_MANY_MUSEUMS;
} else if (items.get(position).museums.size() == 1){
return ITEM_TYPE_ONE_MUSEUM;
}
throw new IllegalArgumentException("Wrong number of museums!");
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((IItemDisplayer<City>) holder.itemView).displayItem(items.get(position));
}
#Override
public int getItemCount() {
return items.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
}
}
I've uploaded this project to my dropbox - feel free to check it out! Hope, it helps.
Can you try this:
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import java.lang.reflect.Field;
/**
* {#link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
* wrap the content regardless of {#link android.support.v7.widget.RecyclerView} layout parameters.
* <p/>
* Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for
* VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
* {#link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
* If animations are not used at all then a normal measuring procedure will run and child views will be measured during
* the measure pass.
*/
public class WrapContentLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {
private static boolean canMakeInsetsDirty = true;
private static Field insetsDirtyField = null;
private static final int CHILD_WIDTH = 0;
private static final int CHILD_HEIGHT = 1;
private static final int DEFAULT_CHILD_SIZE = 100;
private final int[] childDimensions = new int[2];
private final RecyclerView view;
private int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
private final Rect tmpRect = new Rect();
#SuppressWarnings("UnusedDeclaration")
public WrapContentLinearLayoutManager(Context context) {
super(context);
this.view = null;
}
#SuppressWarnings("UnusedDeclaration")
public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.view = null;
}
#SuppressWarnings("UnusedDeclaration")
public WrapContentLinearLayoutManager(RecyclerView view) {
super(view.getContext());
this.view = view;
this.overScrollMode = ViewCompat.getOverScrollMode(view);
}
#SuppressWarnings("UnusedDeclaration")
public WrapContentLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
super(view.getContext(), orientation, reverseLayout);
this.view = view;
this.overScrollMode = ViewCompat.getOverScrollMode(view);
}
public void setOverScrollMode(int overScrollMode) {
if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)
throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
if (this.view == null) throw new IllegalStateException("view == null");
this.overScrollMode = overScrollMode;
ViewCompat.setOverScrollMode(view, overScrollMode);
}
public static int makeUnspecifiedSpec() {
return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;
final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;
final int unspecified = makeUnspecifiedSpec();
if (exactWidth && exactHeight) {
// in case of exact calculations for both dimensions let's use default "onMeasure" implementation
super.onMeasure(recycler, state, widthSpec, heightSpec);
return;
}
final boolean vertical = getOrientation() == VERTICAL;
initChildDimensions(widthSize, heightSize, vertical);
int width = 0;
int height = 0;
// it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
// happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
// recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
// called whiles scrolling)
recycler.clear();
final int stateItemCount = state.getItemCount();
final int adapterItemCount = getItemCount();
// adapter always contains actual data while state might contain old data (f.e. data before the animation is
// done). As we want to measure the view with actual data we must use data from the adapter and not from the
// state
for (int i = 0; i < adapterItemCount; i++) {
if (vertical) {
if (!hasChildSize) {
if (i < stateItemCount) {
// we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
// we will use previously calculated dimensions
measureChild(recycler, i, widthSize, unspecified, childDimensions);
} else {
logMeasureWarning(i);
}
}
height += childDimensions[CHILD_HEIGHT];
if (i == 0) {
width = childDimensions[CHILD_WIDTH];
}
if (hasHeightSize && height >= heightSize) {
break;
}
} else {
if (!hasChildSize) {
if (i < stateItemCount) {
// we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
// we will use previously calculated dimensions
measureChild(recycler, i, unspecified, heightSize, childDimensions);
} else {
logMeasureWarning(i);
}
}
width += childDimensions[CHILD_WIDTH];
if (i == 0) {
height = childDimensions[CHILD_HEIGHT];
}
if (hasWidthSize && width >= widthSize) {
break;
}
}
}
if (exactWidth) {
width = widthSize;
} else {
width += getPaddingLeft() + getPaddingRight();
if (hasWidthSize) {
width = Math.min(width, widthSize);
}
}
if (exactHeight) {
height = heightSize;
} else {
height += getPaddingTop() + getPaddingBottom();
if (hasHeightSize) {
height = Math.min(height, heightSize);
}
}
setMeasuredDimension(width, height);
if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
|| (!vertical && (!hasWidthSize || width < widthSize));
ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
}
}
private void logMeasureWarning(int child) {
if (BuildConfig.DEBUG) {
Log.w("LinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
"To remove this message either use #setChildSize() method or don't run RecyclerView animations");
}
}
private void initChildDimensions(int width, int height, boolean vertical) {
if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
// already initialized, skipping
return;
}
if (vertical) {
childDimensions[CHILD_WIDTH] = width;
childDimensions[CHILD_HEIGHT] = childSize;
} else {
childDimensions[CHILD_WIDTH] = childSize;
childDimensions[CHILD_HEIGHT] = height;
}
}
#Override
public void setOrientation(int orientation) {
// might be called before the constructor of this class is called
//noinspection ConstantConditions
if (childDimensions != null) {
if (getOrientation() != orientation) {
childDimensions[CHILD_WIDTH] = 0;
childDimensions[CHILD_HEIGHT] = 0;
}
}
super.setOrientation(orientation);
}
public void clearChildSize() {
hasChildSize = false;
setChildSize(DEFAULT_CHILD_SIZE);
}
public void setChildSize(int childSize) {
hasChildSize = true;
if (this.childSize != childSize) {
this.childSize = childSize;
requestLayout();
}
}
private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) {
final View child;
try {
child = recycler.getViewForPosition(position);
} catch (IndexOutOfBoundsException e) {
if (BuildConfig.DEBUG) {
Log.w("LinearLayoutManager", "LinearLayoutManager doesn't work well with animations. Consider switching them off", e);
}
return;
}
final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();
final int hPadding = getPaddingLeft() + getPaddingRight();
final int vPadding = getPaddingTop() + getPaddingBottom();
final int hMargin = p.leftMargin + p.rightMargin;
final int vMargin = p.topMargin + p.bottomMargin;
// we must make insets dirty in order calculateItemDecorationsForChild to work
makeInsetsDirty(p);
// this method should be called before any getXxxDecorationXxx() methods
calculateItemDecorationsForChild(child, tmpRect);
final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);
final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically());
child.measure(childWidthSpec, childHeightSpec);
dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;
// as view is recycled let's not keep old measured values
makeInsetsDirty(p);
recycler.recycleView(child);
}
private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
if (!canMakeInsetsDirty) {
return;
}
try {
if (insetsDirtyField == null) {
insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
insetsDirtyField.setAccessible(true);
}
insetsDirtyField.set(p, true);
} catch (NoSuchFieldException e) {
onMakeInsertDirtyFailed();
} catch (IllegalAccessException e) {
onMakeInsertDirtyFailed();
}
}
private static void onMakeInsertDirtyFailed() {
canMakeInsetsDirty = false;
if (BuildConfig.DEBUG) {
Log.w("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
}
}
}
Put this WrapContentLinearLayoutManager in your recyclerview
mRecyclerView.setLayoutManager(new WrapContentLinearLayoutManager(activity));
And this in the xml (with the relative in parent)
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:id="#+id/recyclerViewMuseum"
android:scrollbars="vertical"
android:scrollIndicators="none"/>
This should work :)
1.Try set LayoutParams CENTER_IN_PARENT when recyclerview has 1 child
2.Or Use a FrameLayout as parent of recyclerView and set layout_gravity
3.Or just compute the space above cardview and set marginTop to recyclerView or setTranslationY to recyclerView
4.Or add a itemDecaration with space'height at Recylerview.
RelativeLayout.LayoutParams rLp = (RelativeLayout.LayoutParams) recyclerView.getLayoutParams();
if(data != null || data.size()>1){
rLp.removeRule(RelativeLayout.CENTER_IN_PARENT);
}else{
rLp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
}
recyclerView.setLayoutParams(rLp);
When recyclerView has only one child use this LayoutManager:
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
public class FullyLinearLayoutManager extends LinearLayoutManager {
private static final String TAG = FullyLinearLayoutManager.class.getSimpleName();
private MeasureEndListener mMeasureEndListener;
public FullyLinearLayoutManager(Context context) {
super(context);
}
private float divHeight =0;
public FullyLinearLayoutManager(Context context,float height) {
super(context);
divHeight = height;
}
public FullyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
Log.i(TAG, "onMeasure called. \nwidthMode " + widthMode
+ " \nheightMode " + heightSpec
+ " \nwidthSize " + widthSize
+ " \nheightSize " + heightSize
+ " \ngetItemCount() " + getItemCount());
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
// LogUtils.e(mMeasuredDimension[1]);
height = height + mMeasuredDimension[1];
if(i!= getItemCount()-1){
height += divHeight;
// LogUtils.e(divHeight + "xxx add"+DensityUtils.dp2px(divHeight));
}else{
height += 2*divHeight;
// LogUtils.e(divHeight+ "xxx no add"+ DensityUtils.dp2px(divHeight));
}
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
if(mMeasureEndListener!=null){
mMeasureEndListener.onMeasureEnd(width,height);
// new Thread().interrupt();
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
try {
View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsException
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
public interface MeasureEndListener{
void onMeasureEnd(int width,int height);
}
public void setMeasureEndListener(MeasureEndListener mMeasureEndListener){
this.mMeasureEndListener = mMeasureEndListener;
}
}
Try set height of RecyclerView when have only one item or more.
Refer this topic for to do that. When have only one item, set RecyclerView to WRAP_CONTENT. And when have more one item, set RecyclerView to MATCH_CONTENT. Good luck!
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="#dimen/margin_twenty"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginBottom="#dimen/margin_twenty">
</android.support.v7.widget.RecyclerView>

RecyclerView not showing anything with a sibling view

I posted this yesterday as well, but did not get any response. Can no one help with this? Basically, when the RecyclerView is only view of a tabbed fragment it works perfectly. However, once I want to include other views as sibling of this RecyclerView (wrapped in a linearlayout which is in turn wrapper in a NestedScrollView), the RecyclerView does not generate anything (or does now show?). The other view shows up though. So any idea what I might be doing wrong? Am I not passing the correct view / reference to the adapter?
StocksCompleteListFragment.java
public class StocksCompleteListFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
NestedScrollView sv = (NestedScrollView) inflater.inflate(
R.layout.fragment_stocks_completelist, container, false);
LinearLayout mylinear = (LinearLayout) sv.findViewById(R.id.mainLinear);
RecyclerView rv = (RecyclerView) mylinear.findViewById(R.id.recyclerview);
if (rv == null){
Toast.makeText(getActivity(),"Null", Toast.LENGTH_SHORT).show();
}
setupRecyclerView(rv);
return sv;
}
private void setupRecyclerView(RecyclerView recyclerView) {
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
recyclerView.setAdapter(new SimpleStringRecyclerViewAdapter(getActivity(),
getRandomSublist(Cheeses.sCheeseStrings, 30)));
}
private List<String> getRandomSublist(String[] array, int amount) {
ArrayList<String> list = new ArrayList<>(amount);
Random random = new Random();
while (list.size() < amount) {
list.add(array[random.nextInt(array.length)]);
}
return list;
}
public static class SimpleStringRecyclerViewAdapter
extends RecyclerView.Adapter<SimpleStringRecyclerViewAdapter.ViewHolder> {
private final TypedValue mTypedValue = new TypedValue();
private int mBackground;
private List<String> mValues;
public static class ViewHolder extends RecyclerView.ViewHolder {
public String mBoundString;
public final View mView;
public final ImageView mImageView;
public final TextView mTextView;
public ViewHolder(View view) {
super(view);
mView = view;
mImageView = (ImageView) view.findViewById(R.id.avatar);
mTextView = (TextView) view.findViewById(android.R.id.text1);
}
#Override
public String toString() {
return super.toString() + " '" + mTextView.getText();
}
}
public String getValueAt(int position) {
return mValues.get(position);
}
public SimpleStringRecyclerViewAdapter(Context context, List<String> items) {
context.getTheme().resolveAttribute(R.attr.selectableItemBackground, mTypedValue, true);
mBackground = mTypedValue.resourceId;
mValues = items;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item, parent, false);
view.setBackgroundResource(mBackground);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mBoundString = mValues.get(position);
holder.mTextView.setText(mValues.get(position));
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Context context = v.getContext();
Intent intent = new Intent(context, CheeseDetailActivity.class);
intent.putExtra(CheeseDetailActivity.EXTRA_NAME, holder.mBoundString);
context.startActivity(intent);
}
});
Glide.with(holder.mImageView.getContext())
.load(Cheeses.getRandomCheeseDrawable())
.fitCenter()
.into(holder.mImageView);
}
#Override
public int getItemCount() {
return mValues.size();
}
}
}
And here's the layout xml fragment_stocks_completelist.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="#+id/mainLinear"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/progressBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="4dp"
android:layout_marginTop="8dp"
android:background="#color/grayBackground"
android:gravity="center_vertical|center_horizontal"
android:padding="4dp"
android:visibility="gone">
<ProgressBar
android:id="#+id/progressBar1"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
You should use a custom LayoutManager - which measures its children (either LinearLayoutManager or GridLayoutManager):
public class CustomLayoutManager extends GridLayoutManager {
private static final String TAG = CustomLayoutManager.class.getCanonicalName();
// Number of columns to show in the grid
private static final int SPAN_COUNT = 2;
public CustomLayoutManager(final Context context) {
super(context, SPAN_COUNT);
}
private final int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(final RecyclerView.Recycler recycler, final RecyclerView.State state,
final int widthSpec, final int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.
UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(final RecyclerView.Recycler recycler, final int widthSpec,
final int heightSpec, final int[] measuredDimension) {
try {
View view = recycler.getViewForPosition(0);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}}

Recycler view wrap_content with drag and drop

I'm trying to create a to-do list just like "google keep" android app.
Basically, I need a scrolled layout with 2 lists: one of the checked items, and one of the unchecked ones.
The unchecked list should have an option to drag & drop the items in order to change their position.
My only problem is that I can't use width=wrap_content in recyclerview, and the layoutManager implementation that I found make the app crash when I try to drag & drop.
For the drag & drop I use this library.
My layout:
<ScrollView
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:fillViewport="true"
tools:context="views.fragments.ItemsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<AutoCompleteTextView
android:id="#+id/fragment_items_atv_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<Button
android:id="#+id/fragment_items_btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="#string/add" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/fragment_items_lv_unchecked_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v7.widget.RecyclerView
android:id="#+id/fragment_items_lv_checked_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/fragment_items_tv_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Empty list"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</ScrollView>
The layout manager I use
public class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context) {
super(context, VERTICAL, false);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
You can use ItemTouchHelper in supportv7, and implement simplecallback,like this:
mSimpleCallback = new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT|ItemTouchHelper.UP|ItemTouchHelper.DOWN, 0) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();//得到拖动ViewHolder的position
int toPosition = target.getAdapterPosition();//得到目标ViewHolder的position
List<NHUser.DescriptionEntity> datas = mItemAdapter.getData();
if (fromPosition < toPosition) {
//分别把中间所有的item的位置重新交换
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(datas, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(datas, i, i - 1);
}
}
mItemAdapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
};

Nested Recycler view height doesn't wrap its content

I have an application that manage collections of books (like playlists).
I want to display a list of collection with a vertical RecyclerView and inside each row, a list of book in an horizontal RecyclerView.
When i set the layout_height of the inner horizontal RecyclerView to 300dp, it is displayed correctly but when i set it to wrap_content, it doesn't display anything.
I need to use wrap_content because I want to be able to change the layout manager programmatically to switch between vertical and horizontal display.
Do you know what i'm doing wrong ?
My Fragment layout :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<com.twibit.ui.view.CustomSwipeToRefreshLayout
android:id="#+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/shelf_collection_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="10dp"/>
</LinearLayout>
</com.twibit.ui.view.CustomSwipeToRefreshLayout>
</LinearLayout>
Collection element layout :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFF">
<!-- Simple Header -->
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/empty_collection"
android:id="#+id/empty_collection_tv"
android:visibility="gone"
android:gravity="center"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/collection_book_listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/> <!-- android:layout_height="300dp" -->
</FrameLayout>
</LinearLayout>
Book list item :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="180dp"
android:layout_height="220dp"
android:layout_gravity="center">
<ImageView
android:id="#+id/shelf_item_cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxWidth="150dp"
android:maxHeight="200dp"
android:src="#drawable/placeholder"
android:contentDescription="#string/cover"
android:adjustViewBounds="true"
android:background="#android:drawable/dialog_holo_light_frame"/>
</FrameLayout>
Here is my Collection Adapter :
private class CollectionsListAdapter extends RecyclerView.Adapter<CollectionsListAdapter.ViewHolder> {
private final String TAG = CollectionsListAdapter.class.getSimpleName();
private Context mContext;
// Create the ViewHolder class to keep references to your views
class ViewHolder extends RecyclerView.ViewHolder {
private final TextView mHeaderTitleTextView;
private final TextView mHeaderCountTextView;
private final RecyclerView mHorizontalListView;
private final TextView mEmptyTextView;
public ViewHolder(View view) {
super(view);
mHeaderTitleTextView = (TextView) view.findViewById(R.id.collection_header_tv);
mHeaderCountTextView = (TextView) view.findViewById(R.id.collection_header_count_tv);
mHorizontalListView = (RecyclerView) view.findViewById(R.id.collection_book_listview);
mEmptyTextView = (TextView) view.findViewById(R.id.empty_collection_tv);
}
}
public CollectionsListAdapter(Context context) {
mContext = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
Log.d(TAG, "CollectionsListAdapter.onCreateViewHolder(" + parent.getId() + ", " + i + ")");
// Create a new view by inflating the row item xml.
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.shelf_collection, parent, false);
// Set the view to the ViewHolder
ViewHolder holder = new ViewHolder(v);
holder.mHorizontalListView.setHasFixedSize(false);
holder.mHorizontalListView.setHorizontalScrollBarEnabled(true);
// use a linear layout manager
LinearLayoutManager mLayoutManager = new LinearLayoutManager(mContext);
mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
holder.mHorizontalListView.setLayoutManager(mLayoutManager);
return holder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int i) {
Log.d(TAG, "CollectionsListAdapter.onBindViewHolder(" + holder.getPosition() + ", " + i + ")");
Collection collection = mCollectionList.get(i);
Log.d(TAG, "Collection : " + collection.getLabel());
holder.mHeaderTitleTextView.setText(collection.getLabel());
holder.mHeaderCountTextView.setText("" + collection.getBooks().size());
// Create an adapter if none exists
if (!mBookListAdapterMap.containsKey(collection.getCollectionId())) {
mBookListAdapterMap.put(collection.getCollectionId(), new BookListAdapter(getActivity(), collection));
}
holder.mHorizontalListView.setAdapter(mBookListAdapterMap.get(collection.getCollectionId()));
}
#Override
public int getItemCount() {
return mCollectionList.size();
}
}
And finally, the Book adapter :
private class BookListAdapter extends RecyclerView.Adapter<BookListAdapter.ViewHolder> implements View.OnClickListener {
private final String TAG = BookListAdapter.class.getSimpleName();
// Create the ViewHolder class to keep references to your views
class ViewHolder extends RecyclerView.ViewHolder {
public ImageView mCoverImageView;
public ViewHolder(View view) {
super(view);
mCoverImageView = (ImageView) view.findViewById(R.id.shelf_item_cover);
}
}
#Override
public void onClick(View v) {
BookListAdapter.ViewHolder holder = (BookListAdapter.ViewHolder) v.getTag();
int position = holder.getPosition();
final Book book = mCollection.getBooks().get(position);
// Click on cover image
if (v.getId() == holder.mCoverImageView.getId()) {
downloadOrOpenBook(book);
return;
}
}
private void downloadOrOpenBook(final Book book) {
// do stuff
}
private Context mContext;
private Collection mCollection;
public BookListAdapter(Context context, Collection collection) {
Log.d(TAG, "BookListAdapter(" + context + ", " + collection + ")");
mCollection = collection;
mContext = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
Log.d(TAG, "onCreateViewHolder(" + parent.getId() + ", " + i + ")");
// Create a new view by inflating the row item xml.
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.shelf_grid_item, parent, false);
// Set the view to the ViewHolder
ViewHolder holder = new ViewHolder(v);
holder.mCoverImageView.setOnClickListener(BookListAdapter.this); // Download or Open
holder.mCoverImageView.setTag(holder);
return holder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int i) {
Log.d(TAG, "onBindViewHolder(" + holder.getPosition() + ", " + i + ")");
Book book = mCollection.getBooks().get(i);
ImageView imageView = holder.mCoverImageView;
ImageLoader.getInstance().displayImage(book.getCoverUrl(), imageView);
}
#Override
public int getItemCount() {
return mCollection.getBooks().size();
}
}
#user2302510 solution works not as good as you may expected. Full workaround for both orientations and dynamically data changes is:
public class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
Update
Many issues relating to this feature in version 23.2.0 have been fixed in 23.2.1, update to that instead.
With the release of Support Library version 23.2, RecyclerView now supports that!
Update build.gradle to:
compile 'com.android.support:recyclerview-v7:23.2.1'
or any version beyond that.
This release brings an exciting new feature to the LayoutManager API: auto-measurement! This allows a RecyclerView to size itself based on the size of its contents. This means that previously unavailable scenarios, such as using WRAP_CONTENT for a dimension of the RecyclerView, are now possible. You’ll find all built in LayoutManagers now support auto-measurement.
This can be disabled via setAutoMeasurementEnabled() if need be. Check in detail here.
The code up above doesn't work well when you need to make your items "wrap_content", because it measures both items height and width with MeasureSpec.UNSPECIFIED. After some troubles I've modified that solution so now items can expand. The only difference is that it provides parents height or width MeasureSpec depends on layout orientation.
public class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
if (getOrientation() == HORIZONTAL) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
heightSpec,
mMeasuredDimension);
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
measureScrapChild(recycler, i,
widthSpec,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
recycler.bindViewToPosition(view, position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
Existing layout manager do not yet support wrap content.
You can create a new LayoutManager that extends the existing one and overrides onMeasure method to measure for wrap content.
As #yiğit mentioned, you need to override onMeasure(). Both #user2302510 and #DenisNek have good answers but if you want to support ItemDecoration you can use this custom layout manager.
And other answers cannot scroll when there are more items than can be displayed on the screen though. This one is using default implemantation of onMeasure() when there are more items than screen size.
public class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
// If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
if (height < heightSize && width < widthSize) {
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
} else {
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
// For adding Item Decor Insets to view
super.measureChildWithMargins(view, 0, 0);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view) , p.height);
view.measure(childWidthSpec, childHeightSpec);
// Get decorated measurements
measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
And if you want to use it with GridLayoutManager just extends it from GridLayoutManager and change
for (int i = 0; i < getItemCount(); i++)
to
for (int i = 0; i < getItemCount(); i = i + getSpanCount())
UPDATE March 2016
By Android Support Library 23.2.1 of a support library version. So all WRAP_CONTENT should work correctly.
Please update version of a library in gradle file.
compile 'com.android.support:recyclerview-v7:23.2.1'
This allows a RecyclerView to size itself based on the size of its contents. This means that previously unavailable scenarios, such as using WRAP_CONTENT for a dimension of the RecyclerView, are now possible.
you’ll be required to call setAutoMeasureEnabled(true)
Fixed bugs related to various measure-spec methods in update
Check https://developer.android.com/topic/libraries/support-library/features.html
This answer is based on the solution given by Denis Nek. It solves the problem of not taking decorations like dividers into account.
public class WrappingRecyclerViewLayoutManager extends LinearLayoutManager {
public WrappingRecyclerViewLayoutManager(Context context) {
super(context, VERTICAL, false);
}
public WrappingRecyclerViewLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
Rect outRect = new Rect();
calculateItemDecorationsForChild(view, outRect);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin + outRect.bottom + outRect.top;
recycler.recycleView(view);
}
}
}
An alternative to extend LayoutManager can be just set the size of the view manually.
Number of items per row height (if all the items have the same height and the separator is included on the row)
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mListView.getLayoutParams();
params.height = mAdapter.getItemCount() * getResources().getDimensionPixelSize(R.dimen.row_height);
mListView.setLayoutParams(params);
Is still a workaround, but for basic cases it works.
Here I have found a solution: https://code.google.com/p/android/issues/detail?id=74772
It is in no way my solution. I have just copied it from there, but I hope it will help someone as much as it helped me when implementing horizontal RecyclerView and wrap_content height (should work also for vertical one and wrap_content width)
The solution is to extend the LayoutManager and override its onMeasure method as #yigit suggested.
Here is the code in case the link dies:
public static class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context) {
super(context);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
measureScrapChild(recycler, 0,
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
int width = mMeasuredDimension[0];
int height = mMeasuredDimension[1];
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
case View.MeasureSpec.AT_MOST:
width = widthSize;
break;
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
case View.MeasureSpec.AT_MOST:
height = heightSize;
break;
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth();
measuredDimension[1] = view.getMeasuredHeight();
recycler.recycleView(view);
}
}
}
Used solution from #sinan-kozak, except fixed a few bugs. Specifically, we shouldn't use View.MeasureSpec.UNSPECIFIED for both the width and height when calling measureScrapChild as that won't properly account for wrapped text in the child. Instead, we will pass through the width and height modes from the parent which will allow things to work for both horizontal and vertical layouts.
public class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
if (getOrientation() == HORIZONTAL) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(heightSize, heightMode),
mMeasuredDimension);
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(widthSize, widthMode),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
// If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
if (height < heightSize && width < widthSize) {
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
} else {
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
// For adding Item Decor Insets to view
super.measureChildWithMargins(view, 0, 0);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom() + getDecoratedTop(view) + getDecoratedBottom(view) , p.height);
view.measure(childWidthSpec, childHeightSpec);
// Get decorated measurements
measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
`
Yes the workaround shown in all answer is correct , that is we need to customize the linear layout manager to calculate the height of its child items dynamically at run time. But all answers not working as expected .Please the below answer for custom layout manger with all orientation support.
public class MyLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {
private static boolean canMakeInsetsDirty = true;
private static Field insetsDirtyField = null;
private static final int CHILD_WIDTH = 0;
private static final int CHILD_HEIGHT = 1;
private static final int DEFAULT_CHILD_SIZE = 100;
private final int[] childDimensions = new int[2];
private final RecyclerView view;
private int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
private final Rect tmpRect = new Rect();
#SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(Context context) {
super(context);
this.view = null;
}
#SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.view = null;
}
#SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(RecyclerView view) {
super(view.getContext());
this.view = view;
this.overScrollMode = ViewCompat.getOverScrollMode(view);
}
#SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
super(view.getContext(), orientation, reverseLayout);
this.view = view;
this.overScrollMode = ViewCompat.getOverScrollMode(view);
}
public void setOverScrollMode(int overScrollMode) {
if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)
throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
if (this.view == null) throw new IllegalStateException("view == null");
this.overScrollMode = overScrollMode;
ViewCompat.setOverScrollMode(view, overScrollMode);
}
public static int makeUnspecifiedSpec() {
return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;
final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;
final int unspecified = makeUnspecifiedSpec();
if (exactWidth && exactHeight) {
// in case of exact calculations for both dimensions let's use default "onMeasure" implementation
super.onMeasure(recycler, state, widthSpec, heightSpec);
return;
}
final boolean vertical = getOrientation() == VERTICAL;
initChildDimensions(widthSize, heightSize, vertical);
int width = 0;
int height = 0;
// it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
// happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
// recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
// called whiles scrolling)
recycler.clear();
final int stateItemCount = state.getItemCount();
final int adapterItemCount = getItemCount();
// adapter always contains actual data while state might contain old data (f.e. data before the animation is
// done). As we want to measure the view with actual data we must use data from the adapter and not from the
// state
for (int i = 0; i < adapterItemCount; i++) {
if (vertical) {
if (!hasChildSize) {
if (i < stateItemCount) {
// we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
// we will use previously calculated dimensions
measureChild(recycler, i, widthSize, unspecified, childDimensions);
} else {
logMeasureWarning(i);
}
}
height += childDimensions[CHILD_HEIGHT];
if (i == 0) {
width = childDimensions[CHILD_WIDTH];
}
if (hasHeightSize && height >= heightSize) {
break;
}
} else {
if (!hasChildSize) {
if (i < stateItemCount) {
// we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
// we will use previously calculated dimensions
measureChild(recycler, i, unspecified, heightSize, childDimensions);
} else {
logMeasureWarning(i);
}
}
width += childDimensions[CHILD_WIDTH];
if (i == 0) {
height = childDimensions[CHILD_HEIGHT];
}
if (hasWidthSize && width >= widthSize) {
break;
}
}
}
if (exactWidth) {
width = widthSize;
} else {
width += getPaddingLeft() + getPaddingRight();
if (hasWidthSize) {
width = Math.min(width, widthSize);
}
}
if (exactHeight) {
height = heightSize;
} else {
height += getPaddingTop() + getPaddingBottom();
if (hasHeightSize) {
height = Math.min(height, heightSize);
}
}
setMeasuredDimension(width, height);
if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
|| (!vertical && (!hasWidthSize || width < widthSize));
ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
}
}
private void logMeasureWarning(int child) {
if (BuildConfig.DEBUG) {
Log.w("MyLinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
"To remove this message either use #setChildSize() method or don't run RecyclerView animations");
}
}
private void initChildDimensions(int width, int height, boolean vertical) {
if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
// already initialized, skipping
return;
}
if (vertical) {
childDimensions[CHILD_WIDTH] = width;
childDimensions[CHILD_HEIGHT] = childSize;
} else {
childDimensions[CHILD_WIDTH] = childSize;
childDimensions[CHILD_HEIGHT] = height;
}
}
#Override
public void setOrientation(int orientation) {
// might be called before the constructor of this class is called
//noinspection ConstantConditions
if (childDimensions != null) {
if (getOrientation() != orientation) {
childDimensions[CHILD_WIDTH] = 0;
childDimensions[CHILD_HEIGHT] = 0;
}
}
super.setOrientation(orientation);
}
public void clearChildSize() {
hasChildSize = false;
setChildSize(DEFAULT_CHILD_SIZE);
}
public void setChildSize(int childSize) {
hasChildSize = true;
if (this.childSize != childSize) {
this.childSize = childSize;
requestLayout();
}
}
private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) {
final View child;
try {
child = recycler.getViewForPosition(position);
} catch (IndexOutOfBoundsException e) {
if (BuildConfig.DEBUG) {
Log.w("MyLinearLayoutManager", "MyLinearLayoutManager doesn't work well with animations. Consider switching them off", e);
}
return;
}
final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();
final int hPadding = getPaddingLeft() + getPaddingRight();
final int vPadding = getPaddingTop() + getPaddingBottom();
final int hMargin = p.leftMargin + p.rightMargin;
final int vMargin = p.topMargin + p.bottomMargin;
// we must make insets dirty in order calculateItemDecorationsForChild to work
makeInsetsDirty(p);
// this method should be called before any getXxxDecorationXxx() methods
calculateItemDecorationsForChild(child, tmpRect);
final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);
final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically());
child.measure(childWidthSpec, childHeightSpec);
dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;
// as view is recycled let's not keep old measured values
makeInsetsDirty(p);
recycler.recycleView(child);
}
private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
if (!canMakeInsetsDirty) {
return;
}
try {
if (insetsDirtyField == null) {
insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
insetsDirtyField.setAccessible(true);
}
insetsDirtyField.set(p, true);
} catch (NoSuchFieldException e) {
onMakeInsertDirtyFailed();
} catch (IllegalAccessException e) {
onMakeInsertDirtyFailed();
}
}
private static void onMakeInsertDirtyFailed() {
canMakeInsetsDirty = false;
if (BuildConfig.DEBUG) {
Log.w("MyLinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
}
}
}
Android Support Library now handles WRAP_CONTENT property as well. Just import this in your gradle.
compile 'com.android.support:recyclerview-v7:23.2.0'
And done!
Based on the work of Denis Nek,
it works well if the sum of item's widths is smaller than the size of the container. other than that, it will make the recyclerview non scrollable and only will show subset of the data.
to solve this problem, i modified the solution alittle so that it choose the min of the provided size and calculated size. see below:
package com.linkdev.gafi.adapters;
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import com.linkdev.gafi.R;
public class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.c = context;
}
private Context c;
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
int widthDesired = Math.min(widthSize,width);
setMeasuredDimension(widthDesired, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}}
I have tried all solutions, they are very useful
but this only works fine for me
public class LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
if (getOrientation() == HORIZONTAL) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
heightSpec,
mMeasuredDimension);
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
measureScrapChild(recycler, i,
widthSpec,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
if (height < heightSize || width < widthSize) {
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
} else {
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
recycler.bindViewToPosition(view, position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
}
Simply wrap the content using RecyclerView with the Grid Layout
Image:
Recycler as GridView layout
Just use the GridLayoutManager like this:
RecyclerView.LayoutManager mRecyclerGrid=new GridLayoutManager(this,3,LinearLayoutManager.VERTICAL,false);
mRecyclerView.setLayoutManager(mRecyclerGrid);
You can set how many items should appear on a row (replace the 3).

Categories

Resources