I am using cardview inside a RecyclerView and to make the RecyclerView scroll horizontal i have initialized the view with a layout manager like below:
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
At the moment the items in the view are scrolling endlessly/smooth. I would like it to stop when one item is shown on screen, almost like a snappy effect. Can this be achieved?
Thanks in advance.
I used this class:
SnappyRecyclerView
package icn.premierandroid.misc;
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
public class SnappyRecyclerView extends RecyclerView {
// Use it with a horizontal LinearLayoutManager
// Based on http://stackoverflow.com/a/29171652/4034572
public SnappyRecyclerView(Context context) {
super(context);
}
public SnappyRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public SnappyRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean fling(int velocityX, int velocityY) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
// views on the screen
int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);
// distance we need to scroll
int leftMargin = (screenWidth - lastView.getWidth()) / 2;
int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
int leftEdge = lastView.getLeft();
int rightEdge = firstView.getRight();
int scrollDistanceLeft = leftEdge - leftMargin;
int scrollDistanceRight = rightMargin - rightEdge;
if (Math.abs(velocityX) < 1000) {
// The fling is slow -> stay at the current page if we are less than half through,
// or go to the next page if more than half through
if (leftEdge > screenWidth / 2) {
// go to next page
smoothScrollBy(-scrollDistanceRight, 0);
} else if (rightEdge < screenWidth / 2) {
// go to next page
smoothScrollBy(scrollDistanceLeft, 0);
} else {
// stay at current page
if (velocityX > 0) {
smoothScrollBy(-scrollDistanceRight, 0);
} else {
smoothScrollBy(scrollDistanceLeft, 0);
}
}
return true;
} else {
// The fling is fast -> go to next page
if (velocityX > 0) {
smoothScrollBy(scrollDistanceLeft, 0);
} else {
smoothScrollBy(-scrollDistanceRight, 0);
}
return true;
}
}
#Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
// If you tap on the phone while the RecyclerView is scrolling it will stop in the middle.
// This code fixes this. This code is not strictly necessary but it improves the behaviour.
if (state == SCROLL_STATE_IDLE) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
// views on the screen
int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);
// distance we need to scroll
int leftMargin = (screenWidth - lastView.getWidth()) / 2;
int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
int leftEdge = lastView.getLeft();
int rightEdge = firstView.getRight();
int scrollDistanceLeft = leftEdge - leftMargin;
int scrollDistanceRight = rightMargin - rightEdge;
if (leftEdge > screenWidth / 2) {
smoothScrollBy(-scrollDistanceRight, 0);
} else if (rightEdge < screenWidth / 2) {
smoothScrollBy(scrollDistanceLeft, 0);
}
}
}
}
in XML (put your package route to the class e.g. mine is icn.premierandroid.misc.SnappyRecyclerView:
<packagename.SnappyRecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/recycler_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scrollbars="none"
android:layout_weight="0.34" />
You shouldn't need to change anything if you have a RecyclerView initialized in your class already.
Like so:
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
// LinearLayoutManager is used here, this will layout the elements in a similar fashion
// to the way ListView would layout elements. The RecyclerView.LayoutManager defines how
// elements are laid out.
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(mLayoutManager);
This should satisfy full width of screen elements only, as you've asked for.
What you are looking for, is the snapping effect.
I've not personally used this class but I believe this would work for you.
https://gist.github.com/lauw/fc84f7d04f8c54e56d56
What this does is, extend the current Android's recyclerview and adds snapping functionality to it.
Add this class to your project and replace the recyclerview with your current recyclerview.
You can enabled snapping of your items to the screen using the setSnapEnabled() method.
I have a PreferenceFragment whose contents are defined in XML. These contents include a child PreferenceScreen. When I click on the PreferenceScreen, the new screen is rendered successfully, but it has no animation (it just shows up on screen, even before the Material ripple is finished on Lollipop devices).
Even worse, if I had any complex layout going on (for example, the PreferenceFragment was in a tab on one side of the screen) that layout is blown away and replaced with a full-screen fragment.
I suspect that if I find the callback or event that occurs when a PreferenceScreen is clicked, I can solve both problems, but I don't really know where to start.
I did this for a slide in right-to-left animation of the PreferenceFragment...
I extended the PreferenceFragment and overridden the onCreateView() like this:
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
AnimRelativeLayout animRelativeLayout = new AnimRelativeLayout(getActivity());
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
animRelativeLayout.setLayoutParams(layoutParams);
animRelativeLayout.addView(view);
return animRelativeLayout;
}
where AnimRelativeLayout.java is
public class AnimRelativeLayout extends RelativeLayout {
private IMovementListener mMovementListener;
public AnimRelativeLayout(Context context) {
super(context);
this.initMembers();
}
public AnimRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.initMembers();
}
public AnimRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.initMembers();
}
private void initMembers() {
this.mMovementListener = new SimpleMovementListener();
}
public float getXFraction() {
final WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
return (width == 0) ? 0 : getX() / (float) width;
}
public void setXFraction(float xFraction) {
final WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
this.mMovementListener.onSetXFraction(xFraction);
setX((width > 0) ? (xFraction * width) : 0);
}
public float getYFraction() {
final WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int height = size.y;
return (height == 0) ? 0 : getY() / (float) height;
}
public void setYFraction(float yFraction) {
final WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int height = size.y;
this.mMovementListener.onSetYFraction(yFraction);
setY((height > 0) ? (yFraction * height) : 0);
}
public interface IMovementListener {
public void onSetXFraction(float pXFraction);
public void onSetYFraction(float pYFraction);
}
private class SimpleMovementListener implements IMovementListener {
#Override
public void onSetXFraction(float pXFraction) {
}
#Override
public void onSetYFraction(float pYFraction) {
}
}
and then did this:
pFragmentManager.beginTransaction()
.setCustomAnimations(
R.animator.slide_in_right_to_left, R.animator.slide_out_right_to_left,
R.animator.slide_in_left_to_right, R.animator.slide_out_left_to_right
)
.replace(R.id.fragment_container, fragment)
.commit();
and an example for R.animator.slide_in_right_to_left is:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:propertyName="xFraction"
android:valueFrom="1.0"
android:valueTo="0"
android:valueType="floatType"/>
</set>
How I can get screen width and height for use this value on device with android version 2.2(API lv8).
I use this code:
public static Point Maximum(Context context)
{
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int width = display.getWidth(); // deprecated
int height = display.getHeight(); // deprecated
Point temp=new Point();
return temp;
}
And when i want check by print Max point to textview:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView txt= (TextView)findViewById(R.id.text1);
txt.setText(Math.Maximum(this).toString());
}
Point is (0,0). What problem?
Try this code. But this API is deprecated in the new SDK versions you can use this.
Display display = activity.getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
Get Device Width and Height, return as point. Hope u like this.
public static Point Maximum(){
Display display = activity.getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
Point temp=new Point();
temp.set(width, height);
return temp;
}
Try this code:
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int height = displaymetrics.heightPixels;
int width = displaymetrics.widthPixels;
Try this for getting your screen width. Similarly you can get screen height or can customize the same for returning both in a single function.
#SuppressWarnings("deprecation")
#SuppressLint("NewApi")
private int getScreenWidth() {
int width = 400;
int height = 600; // just intialising it to default value in case if there is any problem getting screen width
WindowManager wm = (WindowManager) activity // activity is the context if you are using this method in adapter. Otherwise in Activity you can simply ignore this
.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
if (android.os.Build.VERSION.SDK_INT >= 13) {
Point size = new Point();
display.getSize(size);
width = size.x;
height = size.y;
} else {
width = display.getWidth();
height= display.getHeight();
}
return width;//or height
}
Using the new GridLayoutManager: https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager.html
It takes an explicit span count, so the problem now becomes: how do you know how many "spans" fit per row? This is a grid, after all. There should be as many spans as the RecyclerView can fit, based on measured width.
Using the old GridView, you would just set the "columnWidth" property and it would automatically detect how many columns fit. This is basically what I want to replicate for the RecyclerView:
add OnLayoutChangeListener on the RecyclerView
in this callback, inflate a single 'grid item' and measure it
spanCount = recyclerViewWidth / singleItemWidth;
This seems like pretty common behavior, so is there a simpler way that I'm not seeing?
Personaly I don't like to subclass RecyclerView for this, because for me it seems that there is GridLayoutManager's responsibility to detect span count. So after some android source code digging for RecyclerView and GridLayoutManager I wrote my own class extended GridLayoutManager that do the job:
public class GridAutofitLayoutManager extends GridLayoutManager
{
private int columnWidth;
private boolean isColumnWidthChanged = true;
private int lastWidth;
private int lastHeight;
public GridAutofitLayoutManager(#NonNull final Context context, final int columnWidth) {
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1);
setColumnWidth(checkedColumnWidth(context, columnWidth));
}
public GridAutofitLayoutManager(
#NonNull final Context context,
final int columnWidth,
final int orientation,
final boolean reverseLayout) {
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1, orientation, reverseLayout);
setColumnWidth(checkedColumnWidth(context, columnWidth));
}
private int checkedColumnWidth(#NonNull final Context context, final int columnWidth) {
if (columnWidth <= 0) {
/* Set default columnWidth value (48dp here). It is better to move this constant
to static constant on top, but we need context to convert it to dp, so can't really
do so. */
columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
context.getResources().getDisplayMetrics());
}
return columnWidth;
}
public void setColumnWidth(final int newColumnWidth) {
if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
columnWidth = newColumnWidth;
isColumnWidthChanged = true;
}
}
#Override
public void onLayoutChildren(#NonNull final RecyclerView.Recycler recycler, #NonNull final RecyclerView.State state) {
final int width = getWidth();
final int height = getHeight();
if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
final int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = width - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = height - getPaddingTop() - getPaddingBottom();
}
final int spanCount = Math.max(1, totalSpace / columnWidth);
setSpanCount(spanCount);
isColumnWidthChanged = false;
}
lastWidth = width;
lastHeight = height;
super.onLayoutChildren(recycler, state);
}
}
I don't actually remember why I choosed to set span count in onLayoutChildren, I wrote this class some time ago. But the point is we need to do so after view get measured. so we can get it's height and width.
EDIT 1: Fix error in code caused to incorrectly setting span count. Thanks user #Elyees Abouda for reporting and suggesting solution.
EDIT 2: Some small refactoring and fix edge case with manual orientation changes handling. Thanks user #tatarize for reporting and suggesting solution.
I accomplished this using a ViewTreeObserver to get the width of the RecylcerView once rendered and then getting the fixed dimensions of my CardView from resources and then setting the span count after doing my calculations. It is only really applicable if the items you are displaying are of a fixed width. This helped me automatically populate the grid regardless of screen size or orientation.
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int viewWidth = mRecyclerView.getMeasuredWidth();
float cardViewWidth = getActivity().getResources().getDimension(R.dimen.cardview_layout_width);
int newSpanCount = (int) Math.floor(viewWidth / cardViewWidth);
mLayoutManager.setSpanCount(newSpanCount);
mLayoutManager.requestLayout();
}
});
Well, this is what I used, fairly basic, but gets the job done for me. This code basically gets the screen width in dips and then divides by 300 (or whatever width you're using for your adapter's layout). So smaller phones with 300-500 dip width only display one column, tablets 2-3 columns etc. Simple, fuss free and without downside, as far as I can see.
Display display = getActivity().getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float density = getResources().getDisplayMetrics().density;
float dpWidth = outMetrics.widthPixels / density;
int columns = Math.round(dpWidth/300);
mLayoutManager = new GridLayoutManager(getActivity(),columns);
mRecyclerView.setLayoutManager(mLayoutManager);
I extended the RecyclerView and overrode the onMeasure method.
I set an item width(member variable) as early as I can,with a default of 1. This also updates on configuration changed. This will now have as many rows as can fit in portrait,landscape,phone/tablet etc.
#Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
int width = MeasureSpec.getSize(widthSpec);
if(width != 0){
int spans = width / mItemWidth;
if(spans > 0){
mLayoutManager.setSpanCount(spans);
}
}
}
A better way (imo) would be to define different span counts in (many) different values directories and let the device automatically select which span count to use. For example:
values/integers.xml -> span_count=3
values-w480dp/integers.xml -> span_count=4
values-w600dp/integers.xml -> span_count=5
I'm posting this just in case someone gets weird column width as in my case.
I'm not able to comment on #s-marks's answer due to my low reputation. I applied his solution solution but I got some weird column width, so I modified checkedColumnWidth function as follows:
private int checkedColumnWidth(Context context, int columnWidth)
{
if (columnWidth <= 0)
{
/* Set default columnWidth value (48dp here). It is better to move this constant
to static constant on top, but we need context to convert it to dp, so can't really
do so. */
columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
context.getResources().getDisplayMetrics());
}
else
{
columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, columnWidth,
context.getResources().getDisplayMetrics());
}
return columnWidth;
}
By converting the given column width into DP fixed the issue.
I conclusion above answers here
To accommodate orientation change on s-marks's answer, I added a check on width change (width from getWidth(), not column width).
private boolean mWidthChanged = true;
private int mWidth;
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
{
int width = getWidth();
int height = getHeight();
if (width != mWidth) {
mWidthChanged = true;
mWidth = width;
}
if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0
|| mWidthChanged)
{
int totalSpace;
if (getOrientation() == VERTICAL)
{
totalSpace = width - getPaddingRight() - getPaddingLeft();
}
else
{
totalSpace = height - getPaddingTop() - getPaddingBottom();
}
int spanCount = Math.max(1, totalSpace / mColumnWidth);
setSpanCount(spanCount);
mColumnWidthChanged = false;
mWidthChanged = false;
}
super.onLayoutChildren(recycler, state);
}
The upvoted solution is fine, but handles the incoming values as pixels, which can trip you up if you're hardcoding values for testing and assuming dp. Easiest way is probably to put the column width in a dimension and read it when configuring the GridAutofitLayoutManager, which will automatically convert dp to correct pixel value:
new GridAutofitLayoutManager(getActivity(), (int)getActivity().getResources().getDimension(R.dimen.card_width))
Set minimal fixed width of imageView (144dp x 144dp for example)
When you create GridLayoutManager, you need to know how much columns will be with minimal size of imageView:
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE); //Получаем размер экрана
Display display = wm.getDefaultDisplay();
Point point = new Point();
display.getSize(point);
int screenWidth = point.x; //Ширина экрана
int photoWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 144, this.getResources().getDisplayMetrics()); //Переводим в точки
int columnsCount = screenWidth/photoWidth; //Число столбцов
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, columnsCount);
recyclerView.setLayoutManager(gridLayoutManager);
After that you need to resize imageView in adapter if you have space in column. You may send newImageViewSize then inisilize adapter from activity there you calculate screen and column count:
#Override //Заполнение нашей плитки
public void onBindViewHolder(PhotoHolder holder, int position) {
...
ViewGroup.LayoutParams photoParams = holder.photo.getLayoutParams(); //Параметры нашей фотографии
int newImageViewSize = screenWidth/columnsCount; //Новый размер фотографии
photoParams.width = newImageViewSize; //Установка нового размера
photoParams.height = newImageViewSize;
holder.photo.setLayoutParams(photoParams); //Установка параметров
...
}
It works in both orientations. In vertical I have 2 columns and in horizontal - 4 columns. The result: https://i.stack.imgur.com/WHvyD.jpg
This is s.maks' class with a minor fix for when the recyclerview itself changes size. Such as when you deal with the orientation changes yourself (in the manifest android:configChanges="orientation|screenSize|keyboardHidden"), or some other reason the recyclerview might change size without the mColumnWidth changing. I also changed the int value it takes to be the resource of the size and allowed a constructor of no resource then setColumnWidth to do that yourself.
public class GridAutofitLayoutManager extends GridLayoutManager {
private Context context;
private float mColumnWidth;
private float currentColumnWidth = -1;
private int currentWidth = -1;
private int currentHeight = -1;
public GridAutofitLayoutManager(Context context) {
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1);
this.context = context;
setColumnWidthByResource(-1);
}
public GridAutofitLayoutManager(Context context, int resource) {
this(context);
this.context = context;
setColumnWidthByResource(resource);
}
public GridAutofitLayoutManager(Context context, int resource, int orientation, boolean reverseLayout) {
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1, orientation, reverseLayout);
this.context = context;
setColumnWidthByResource(resource);
}
public void setColumnWidthByResource(int resource) {
if (resource >= 0) {
mColumnWidth = context.getResources().getDimension(resource);
} else {
/* Set default columnWidth value (48dp here). It is better to move this constant
to static constant on top, but we need context to convert it to dp, so can't really
do so. */
mColumnWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
context.getResources().getDisplayMetrics());
}
}
public void setColumnWidth(float newColumnWidth) {
mColumnWidth = newColumnWidth;
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
recalculateSpanCount();
super.onLayoutChildren(recycler, state);
}
public void recalculateSpanCount() {
int width = getWidth();
if (width <= 0) return;
int height = getHeight();
if (height <= 0) return;
if (mColumnWidth <= 0) return;
if ((width != currentWidth) || (height != currentHeight) || (mColumnWidth != currentColumnWidth)) {
int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = width - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = height - getPaddingTop() - getPaddingBottom();
}
int spanCount = (int) Math.max(1, Math.floor(totalSpace / mColumnWidth));
setSpanCount(spanCount);
currentColumnWidth = mColumnWidth;
currentWidth = width;
currentHeight = height;
}
}
}
I like s.maks' answer but I found another edge case: If you set the height of the RecyclerView to WRAP_CONTENT it may happen that the height of the recyclerview is calculated incorrectly based on an outdated spanCount value. The solution I found is a small modification of the proposed onLayoutChildren() method:
public void onLayoutChildren(#NonNull final RecyclerView.Recycler recycler, #NonNull final RecyclerView.State state) {
final int width = getWidth();
final int height = getHeight();
if (columnWidth > 0 && (width > 0 || getOrientation() == HORIZONTAL) && (height > 0 || getOrientation() == VERTICAL) && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
final int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = width - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = height - getPaddingTop() - getPaddingBottom();
}
final int spanCount = Math.max(1, totalSpace / columnWidth);
if (getSpanCount() != spanCount) {
setSpanCount(spanCount);
}
isColumnWidthChanged = false;
}
lastWidth = width;
lastHeight = height;
super.onLayoutChildren(recycler, state);
}
Set spanCount to a large number (which is the max number of column) and set a custom SpanSizeLookup to the GridLayoutManager.
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int i) {
return SPAN_COUNT / (int) (mRecyclerView.getMeasuredWidth()/ CELL_SIZE_IN_PX);
}
});
It's a bit ugly, but it work.
I think a manager like AutoSpanGridLayoutManager would be the best solution, but i didn't find anything like that.
EDIT : There is a bug, on some device it add blank space to the right
Here's the relevant parts of a wrapper I've been using to auto-detect the span count. You initialize it by calling setGridLayoutManager with a R.layout.my_grid_item reference, and it figures out how many of those can fit on each row.
public class AutoSpanRecyclerView extends RecyclerView {
private int m_gridMinSpans;
private int m_gridItemLayoutId;
private LayoutRequester m_layoutRequester = new LayoutRequester();
public void setGridLayoutManager( int orientation, int itemLayoutId, int minSpans ) {
GridLayoutManager layoutManager = new GridLayoutManager( getContext(), 2, orientation, false );
m_gridItemLayoutId = itemLayoutId;
m_gridMinSpans = minSpans;
setLayoutManager( layoutManager );
}
#Override
protected void onLayout( boolean changed, int left, int top, int right, int bottom ) {
super.onLayout( changed, left, top, right, bottom );
if( changed ) {
LayoutManager layoutManager = getLayoutManager();
if( layoutManager instanceof GridLayoutManager ) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
LayoutInflater inflater = LayoutInflater.from( getContext() );
View item = inflater.inflate( m_gridItemLayoutId, this, false );
int measureSpec = View.MeasureSpec.makeMeasureSpec( 0, View.MeasureSpec.UNSPECIFIED );
item.measure( measureSpec, measureSpec );
int itemWidth = item.getMeasuredWidth();
int recyclerViewWidth = getMeasuredWidth();
int spanCount = Math.max( m_gridMinSpans, recyclerViewWidth / itemWidth );
gridLayoutManager.setSpanCount( spanCount );
// if you call requestLayout() right here, you'll get ArrayIndexOutOfBoundsException when scrolling
post( m_layoutRequester );
}
}
}
private class LayoutRequester implements Runnable {
#Override
public void run() {
requestLayout();
}
}
}
So I have a gridview of expandable views (user taps on them and they animate to expand). The thing is that my gridview only grows the row height when I click on the second column. In the first picture, you can see I pressed the first item in the list and it expanded but the gridview did not update its rows' heights. In the second picture, I clicked on the second item in the second column and it and the gridview expanded as intended. It seems the gridview is only measuring based on the second column. How can I make it compare it to the first column and pick the larger one? thanks.
EDIT:
This is even happening with plain layouts. The row width is always the second column's height, even if the first column in that row is taller.
I've had the same problem, to fix it I had to extend GridView and use a couple of dirty hacks...
public class AutoGridView extends GridView {
public AutoGridView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
}
public AutoGridView(Context context, AttributeSet attrs){
super(context, attrs);
}
public AutoGridView(Context context){
super(context);
}
#Override
protected void layoutChildren(){
/*This method is called during onLayout. I let the superclass make the hard
part, then I change the top and bottom of visible items according to their
actual height and that of their siblings*/
final int childrenCount = getChildCount();
if(childrenCount <= 0){ //nothing to do, just call the super implementation
super.layoutChildren();
return;
}
/*GridView uses the bottom of the last child to decide if and how to scroll.
UGLY HACK: ensure the last child bottom is equal to the lowest one.
Since super implementation might invalidate layout I must be sure the old
value is restored after the call*/
View v = getChildAt(childrenCount - 1);
int oldBottom = v.getBottom();
super.layoutChildren();
v.setBottom(oldBottom);
for(int i = 0; i < getNumColumns(); ++i){
int top = getListPaddingTop();
for(int j = i; j < childrenCount; j += getNumColumns()){
v = getChildAt(j);
/*after setTop v.getHeight() returns a different value,
that's why I'm saving it...*/
int viewHeight = v.getHeight();
v.setTop(top);
top += viewHeight;
v.setBottom(top);
top += getVerticalSpacing();
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int childrenCount = getChildCount();
if(getAdapter() != null && childrenCount > 0){
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = getSize(widthMeasureSpec);
int heightSize = getSize(heightMeasureSpec);
int heightMode = getMode(heightMeasureSpec);
int maxHeight = heightSize;
for(int i = 0; i < getNumColumns(); ++i){
int columnHeight = 0;
for(int j = i; j < childrenCount; j += getNumColumns())
columnHeight += getChildAt(j).getMeasuredHeight() + getVerticalSpacing();
maxHeight = Math.max(maxHeight, columnHeight);
}
getChildAt(childrenCount-1).setBottom(maxHeight); // for the scrolling hack
int height = heightSize;
if(heightMode == UNSPECIFIED)
height = maxHeight;
else if(heightMode == AT_MOST)
height = Math.min(maxHeight, heightSize);
setMeasuredDimension(width, height);
}
}
}
I know, probably for you this answer is too late, but I hope it can help whoever is stumbling upon your question like I did ^^