I have to implementing a RecylerView,it should wrap the content if its contain only item content height is less. If the item's increase like 250dp, it should be set to max heght(250dp) and that be able to scroll.How to achieve this.my parent layout is a Relative layout.
This is My Layout file
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom" />
</RelativeLayout>
Screen Shot if its only one item.This should be wrap_content.
you can programetically set height to RecylerView
if only one item... wrap content
ViewGroup.LayoutParams params=recycler_view.getLayoutParams();
params.height= RecyclerView.LayoutParams.WRAP_CONTENT;
recycler_view.setLayoutParams(params);
else
<dimen name="view_height">250dp</dimen>
float height= getResources().getDimension(R.dimen.view_height); //get height
ViewGroup.LayoutParams params_new=recycler_view.getLayoutParams();
params_new.height=height;
recycler_view.setLayoutParams(params_new);
1.) Create a class to handle setting maximum height to what is passed by the user:
public class OnViewGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
private Context context;
private int maxHeight;
private View view;
public OnViewGlobalLayoutListener(View view, int maxHeight, Context context) {
this.context = context;
this.view = view;
this.maxHeight = dpToPx(maxHeight);
}
#Override
public void onGlobalLayout() {
if (view.getHeight() > maxHeight) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = maxHeight;
view.setLayoutParams(params);
}
}
public int pxToDp(int px) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int dp = Math.round(px / (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
return dp;
}
public int dpToPx(int dp) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
return px;
}
}
2.) Attach this to the view and pass the maximum height in DP:
messageBody.getViewTreeObserver()
.addOnGlobalLayoutListener(
new OnViewGlobalLayoutListener(messageBody, 256, context)
);
I have an answer here:
https://stackoverflow.com/a/29178364/1148784
Just create a new class extending RecyclerView and override it's onMeasure method.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (maxHeight > 0){
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
switch (hMode){
case MeasureSpec.AT_MOST:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.AT_MOST);
break;
case MeasureSpec.UNSPECIFIED:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
break;
case MeasureSpec.EXACTLY:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.EXACTLY);
break;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
maxHeight is in pixels.
You can use this:
maxHeight = (int) getContext().getResources().getDisplayMetrics().density * 250;
to convert 250 dp into pixels
Related
I'm working on a custom view, which should have equal height and width.
I draw it in the following way:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
widthMeasureSpec = heightMeasureSpec;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
If I just add one view onto the screen, the width/height is ok. Though, if I add two of these views next to each other, the size of the views should be adjusted (each view: half width of the screen).
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<at.guger.widget.Light
android:id="#+id/lights_light1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="5dp" />
<at.guger.widget.Light
android:id="#+id/lights_light2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="5dp" />
</LinearLayout>
How can I do this?
Wouldn't it be easier to let the views take as much space as it can, but constrained them from the parent layoutmanager ? You can implement it with a recyclerview and your own LayoutManager overloading the measureChild method so that it would return the widthscreen divided by the number of elememnt in your adapter. Not a working example but something along the lines:
// setting up your recycler view
mRecyclerView = (RecyclerView) mView.findViewById(R.id.recycler);
// attaching your custom adapter
mRecyclerAdapter = new MyRecyclerAdapter(getActivity(), this);
mRecyclerView.setAdapter(statusRecyclerAdapter);
// setting the layoutmanager
mRecyclerLayoutManager = new MyRecyclerLayoutManager(
getActivity(),
LinearLayoutManager.HORIZONTAL, false);
mRecyclerView.setLayoutManager(mRecyclerLayoutManager);
Then you would create an innerclass MyRecyclerLayoutManager as follow:
private class MyRecyclerLayoutManager extends LinearLayoutManager {
MyRecyclerLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public void measureChild(View child, int widthUsed, int heightUsed) {
super.measureChild(child, widthUsed, heightUsed);
DisplayMetrics displaymetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int widthSpec = View.MeasureSpec.makeMeasureSpec((int) Math.round(displaymetrics.widthPixels / mRecyclerAdapter.size()), View.MeasureSpec.EXACTLY);
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
child.measure(widthSpec, heightSpec);
}
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
super.measureChildWithMargins(child, widthUsed, heightUsed);
DisplayMetrics displaymetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int widthSpec = View.MeasureSpec.makeMeasureSpec((int) Math.round(displaymetrics.widthPixels * 0.85), View.MeasureSpec.EXACTLY);
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
child.measure(widthSpec, heightSpec);
}
}
Here the important bit is at widthSpec which is window width divided by your recycleradapter size.
I wrote the following ViewGroup
public class myViewGroup extends ViewGroup{
List<View> qResult;
List<Point> qLoc;
ImageView qImage;
public QueryViewLayout(Context context){
super(context);
}
public QueryViewLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QueryViewLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
qResult = new LinkedList<View>();
qLoc = new LinkedList<Point>();
qImage = null;
}
public void addMainView(ImageBorderView view){
qImage = view;
super.removeAllViews();
super.addView(view);
}
public void addResultView(View result, Point loc){
super.addView(result);
qResult.add(result);
qLoc.add(loc);
}
/**
* Any layout manager that doesn't scroll will want this.
*/
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// Measurement will ultimately be computing these values.
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Only main view affects the layouts measure
if (qImage != null) {
if (qImage.getVisibility() != GONE) {
// Measure the child.
qImage.measure(widthMeasureSpec, heightMeasureSpec);
maxWidth = qImage.getMeasuredWidth();
maxHeight = qImage.getMeasuredHeight();
childState = qImage.getMeasuredState();
}
}
for (View child:qResult){
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)
child.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
}
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Report our final dimensions.
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
int parentLeft = left + getPaddingLeft();
int parentRight = right - getPaddingRight();
final int parentTop = top + getPaddingTop();
final int parentBottom = bottom - getPaddingBottom();
if (qImage == null) return;
qImage.layout(parentLeft, parentTop, parentRight, parentBottom);
Iterator<Point> loc = qLoc.iterator();
for (View child:qResult) {
Point p = loc.next();
if (child.getVisibility() != GONE) {
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
Point locOnView = qImage.projectOnView(p);
width = (width < (int) Math.max(parentRight - (int) locOnView.x, locOnView.x - parentLeft)) ?
width : (parentLeft + parentRight)/2;
height = (height < (int) Math.max(parentBottom - (int) locOnView.y, locOnView.y - parentTop)) ?
height : (parentBottom + parentTop)/2;
int x = (width < (parentRight - (int) locOnView.x)) ? (int) locOnView.x : (parentRight - width);
int y = (height < parentBottom - (int) locOnView.y) ? (int) locOnView.y : (parentBottom - height);
// Place the child.
child.layout(x, y, x + width, y + height);
}
}
}
}
It is supposed to show some arbitrary view on top of an image, given a location for that view, when I use a GridView as the arbitrary view, even though I have defined a certain width for the GridView it is forced to have a width as large as the frame. In the measure phase I changed the mode to
MeasureSpec.AT_MOST
for both width and height of the overlay view, but this does not seem to work, can someone please help.
here is the xml where I, inflate the GridView from
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/result_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:columnWidth="#dimen/result_view_column_width"
android:numColumns="2"
android:verticalSpacing="2dp"
android:horizontalSpacing="2dp"
android:stretchMode="none"
android:gravity="center"
android:layout_margin = "2dp"
android:background="#drawable/solid_with_shadow" />
After a lot of trial and error, replacing
child.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
with
measureChild(child, MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
worked for me, I am not sure why, but a wild guess would be calling measure on a child does not read all the xml props, but measureChild(child, ...) does.
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();
}
}
}
I've tried to set my ImageView resized but it did not work either by bitmap or other methods I'm going to place my code so if can anyone help me how to size the ImageView to fit inside the table row thnx.
public class Test extends Activity
{
private TableLayout Table1;
protected void onCreate(Bundle savedInstanceState)
{
int[] ImageArray={R.raw.hospital_image,R.raw.hotel_image};
super.onCreate(savedInstanceState);
setContentView(R.layout.favorites);
Table1 = (TableLayout) findViewById(R.id.Table);
TableRow.LayoutParams tableRowParams = new TableRow.LayoutParams(
TableRow.LayoutParams.WRAP_CONTENT,
TableRow.LayoutParams.WRAP_CONTENT, 1.0f);
for(int i=0;i<ImageArray.length;i++)
{
TableRow TR = new TableRow(this);
TR.setLayoutParams(tableRowParams);
ImageView img=new ImageView(this);
//What should i do here to resize it ???
Drawable d = getResources().getDrawable(ImageArray[i]);
img.setImageDrawable(d);
TR.addView(img, new TableRow.LayoutParams(
TableRow.LayoutParams.WRAP_CONTENT,
TableRow.LayoutParams.WRAP_CONTENT, 1.0f));
Table1.addView(TR);
}
}
My XMl only holds these :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" >
<TableLayout
android:id="#+id/Table1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TableLayout>
</ScrollView>
</RelativeLayout>
try this,
public class ResizableImageView extends ImageView {
public ResizableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ResizableImageView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable d = getDrawable();
if (d == null) {
super.setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
return;
}
int imageHeight = d.getIntrinsicHeight();
int imageWidth = d.getIntrinsicWidth();
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
float imageRatio = 0.0F;
if (imageHeight > 0) {
imageRatio = imageWidth / imageHeight;
}
float sizeRatio = 0.0F;
if (heightSize > 0) {
sizeRatio = widthSize / heightSize;
}
int width;
int height;
if (imageRatio >= sizeRatio) {
// set width to maximum allowed
width = widthSize;
// scale height
height = width * imageHeight / imageWidth;
} else {
// set height to maximum allowed
height = heightSize;
// scale width
width = height * imageWidth / imageHeight;
}
setMeasuredDimension(width, height);
}
}
use it in your layout as you would plain old ImageView, just change the tag, like this,
<com.example.ResizableImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:padding="1px"
android:scaleType="fitCenter"
android:src="..." />
setLayoutParams() should do the trick, in your case I believe you would need TableRow.LayoutParams, like so:
ImageView img = new ImageView(this);
img.setLayoutParams(new TableRow.LayoutParams(w, h));
Edit
Try to set the Drawable before the setLayoutParams:
ImageView img = new ImageView(this);
Drawable d = getResources().getDrawable(ImageArray[i]);
img.setImageDrawable(d);
img.setLayoutParams(new TableRow.LayoutParams(w, h));
Update : I solved this issue by using the method described in this answer
I'm a bit stuck with this issue, which I think should be pretty simple.
So my app downloads an image, and renders the bitmap in an ImageView, a child element of a RelativeLayout.
I would like the ImageView to fit the parent width, and to adapt it's size to keep the aspect ratio.
Here is my XML :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RelativeLayout android:id="#+id/banner" android:layout_width="fill_parent" android:layout_height="wrap_content"></RelativeLayout>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello"
/>
</LinearLayout>
And the code :
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
RelativeLayout banner = (RelativeLayout) findViewById(R.id.banner);
ImageView imgV = new ImageView(this);
imgV.setScaleType(ImageView.ScaleType.CENTER_CROP);
// I tried all the scale types : CENTER_INSIDE : same effect, FIT_CENTER : same effect...
imgV.setBackgroundColor(0x00FFFF00);
imgV.setAdjustViewBounds(Color.BLUE);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
banner.addView(imgV,params);
// Some code downloading the image stream
bitmap = BitmapFactory.decodeStream(stream);
imgV.setImageBitmap(bitmap);
}
Desired :
Result :
Thanks to #Julien and #js.
Here is complete solution of ImageView that will stretch bitmap height preserving aspect ratio even if bitmap is smaller than ImageView.
public class ResizableImageView extends ImageView {
public ResizableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
Drawable d = getDrawable();
if(d!=null){
// ceil not round - avoid thin vertical gaps along the left/right edges
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth());
setMeasuredDimension(width, height);
}else{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
You can use this class in your xml layouts instead ImageView.
<com.example.ResizableImageView
android:id="#+id/banner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/banner" />
You're probably looking for android:adjustViewBounds="true" in xml or imageView.setAdjustViewBounds(true) in Java.
As I mentioned in a comment, I subclassed the ImageView. I found my code, here you go :
protected class ResizableImageView extends ImageView
{
private Bitmap mBitmap;
// Constructor
public ResizableImageView(Context context)
{
super(context);
}
// Overriden methods
#Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
if(mBitmap != null)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * mBitmap.getHeight() / mBitmap.getWidth();
setMeasuredDimension(width, height);
}
else
{
super.onMeasure(widthMeasureSpec,
heightMeasureSpec);
}
}
#Override
public void setImageBitmap(Bitmap bitmap)
{
mBitmap = bitmap;
super.setImageBitmap(bitmap);
}
}
I think you should change your imgV width to "match_parent"
You should change the scale type to imgV.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
I made a slight change to #Seraphim S's solution to account for cases where the image may be wide, and the view's bounds also wide (for example, rotating the device to landscape mode).
public class ResizableImageView extends ImageView {
public ResizableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable d = getDrawable();
if (d != null) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (width >= height) {
width = (int) Math.ceil((float) height * (float) d.getIntrinsicWidth() / (float) d.getIntrinsicHeight());
} else {
height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth());
}
setMeasuredDimension(width, height);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}