My GridView contains columns of fixed width, with fixed horizontal spacing. If there are not enough columns to fill the screen horziontally, I would like my GridView's width to wrap to its contents, and to center vertical in the screen.
However, regardless of the number of columns I use, the GridView's width grows to fill the screen. The attached image shows this, where the green GridView fills the screen horizontally, despite having only 3 columns and its width being set to "wrap_content".
public class Temp extends Activity
{
private GridView grid;
private int columnWidth = 80;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
View view = getLayoutInflater().inflate(R.layout.gridview, null);
grid = (GridView) view.findViewById(R.id.grid);
grid.setColumnWidth(columnWidth);
grid.setAdapter(new GridAdapter());
setContentView(view);
}
class GridAdapter extends BaseAdapter
{
public GridAdapter()
{
}
public int getCount()
{
return 3;
}
public Object getItem(int position)
{
return null;
}
public long getItemId(int position)
{
return position;
}
public View getView (int position, View convertView, ViewGroup parent)
{
View ret;
if (convertView == null)
{
ret = new ImageView(Temp.this);
ret.setLayoutParams(new GridView.LayoutParams(columnWidth, 100));
ret.setBackgroundColor(Color.WHITE);
}
else
{
ret= convertView;
}
return ret;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#FF0000"
android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android:id="#+id/grid"
android:background="#00FF00"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numColumns="auto_fit"
android:verticalSpacing="2dip"
android:horizontalSpacing="2dip"
android:stretchMode="columnWidth"
android:gravity="center">
</GridView>
</RelativeLayout>
GridView is extremely annoying with this kind of stuff to say the least. In your case, the issue is that saying auto_fit is essentially telling GridView to always fit it horizontally unfortunately. What you could try is to center the individual ImageViews in the row. But then this requires you to change how you have it set up. Rather than have the columns auto fit, just have 1 item per row, but inflate a LinearLayout that has the orientation as horizontal. Then center the Linear Layout with the ImageViews also in it in each row. Hopefully that provides some ideas.
Simply add two empty views at left and right of GridView with weight = 1 and assign 0.5 weight to GridView. Eg.
<LinearLayout
android:layout_width="fill_parent"
android:gravity="center"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<GridView
android:id="#+id/myGridView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numColumns="2"
android:scrollbars="none"
android:verticalSpacing="10dp" >
</GridView>
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
The layout suggested above did not work for me.
In the end I found it easiest to achieve the results you want by programatically setting the width and X location of the grid -- using the setX(float) method to center the grid.
I know there is an old question, but this is the answer:
public class GridViewEx extends GridView {
//private int mRequestedNumColumns = 0;
public GridViewEx(Context context) {
super(context);
}
public GridViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
}
public GridViewEx(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int numcol=getNumColumns();
int numitems=getAdapter().getCount();
if(numitems<numcol){
int width = (numitems * getColumnWidth())
+ ((numitems -1) * getHorizontalSpacing())
+ getListPaddingLeft() + getListPaddingRight();
setMeasuredDimension(width, getMeasuredHeight());
}
}
}
And when you add or delete elements from your adapter you have to call:
adapter.notifyDataSetChanged();
gridView.invalidate()
Related
I build customView extending LinearLayout and having simple 3 childrens(2 TextView and ImageView). I create this view dynamically in code and adding it to parent LinearLayout. This view has background, so I can easily spot on the screen, that it is inflated correctly in its place, but any of child is not visible. I checked LayoutInspector and it shows that everything is setted correctly(text values to TextViews and picture to ImageView), but somehow when I try to locate them on inspector they are shown as little dot over my customView:
My CustomView is called DayTileView and this is square with gray background. As you can see on inspector on the left childrens are filled with content. Layout of View:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<merge>
<TextView
android:id="#+id/day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="#android:color/white"
/>
<TextView
android:id="#+id/dayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="#android:color/white"
/>
<ImageView
android:id="#+id/padlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="#drawable/ic_padlock"
/>
</merge>
</layout>
And its code:
public class DayTileView extends LinearLayout {
private DayTileBinding mBinding;
public DayTileView(Context context) {
super(context);
init();
}
public DayTileView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public DayTileView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mBinding = DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.day_tile, this, true);
setOrientation(VERTICAL);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
setMeasuredDimension(width, width);
}
public void setDay(int day, int month, int year) {
DateTime settedDay = new DateTime().withYear(year).withMonthOfYear(month).withDayOfMonth(day);
mBinding.day.setText(String.valueOf(day));
String dayName = settedDay.dayOfWeek().getAsText();
mBinding.dayName.setText(dayName);
boolean isWeekend = settedDay.dayOfWeek().get() == 6 || settedDay.dayOfWeek().get() == 7;
setBackgroundColor(ContextCompat.getColor(getContext(), isWeekend ? R.color.weekend_bg : R.color.weekday_bg));
}
}
Its use in another CustomView which is also LinearLayout but wiht horizontal orientation (PlannedDayView on inspector):
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<merge>
<*.customViews.DayTileView
android:id="#+id/dayTile"
android:layout_width="0dp"
android:layout_weight="0.2"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="#+id/container"
android:orientation="vertical"
android:layout_weight="1.2"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
</merge>
</layout>
Has anyone any idea what could be casuing this (childs out of view)? When I replace merge for LinearLayout with vertical orientation and same background everything in Design mode of layout is visible correctly, so it should work.
EDIT:
I found out, that if I set during View initalization Padding Top to 10px then dot is moving down. So it looks like from some reasons Android didn't made to inflate correctly TextViews and ImageView
I found out what was the problem:
I overrided onMeasure and didn't measure child Views. Earlier I was using such code to make square View not square ViewGroup.
Corrected code:
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
setMeasuredDimension(width, width);
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
After setting correct width and height for View I must measure whole view with new MeasureSpec
How do I keep gridView from needing to scroll by auto adjusting it's height? I would like all items, no matter how many items I add to the gridView to remain on screen without scrolling. Is this possible?
Here is my UI so far.
<?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="#drawable/bg"
android:orientation="vertical">
<GridView
android:id="#+id/gv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/tv_header"
android:fadingEdge="none"
android:focusable="false"
android:focusableInTouchMode="false"
android:gravity="center"
android:listSelector="#00000000"
android:numColumns="auto_fit"
android:stretchMode="columnWidth" />
</LinearLayout>
I did try adding a weightSum to the root and weight to gridView but it still requires scrolling.
Update: I also tried using a custom gridview. This did not work, but here is my attempt anyway.
public class CustomGridView extends GridView {
public CustomGridView(Context context) {
super(context);
}
public CustomGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST));
getLayoutParams().height = getMeasuredHeight();
}
}
Thanks in advance!
I have found answer to this. You can set the height of each item in the adapter by using
view.setLayoutParams(new GridView.LayoutParams(GridView.AUTO_FIT, resizeValue));
resizeValue is the size that you want to adjust your rows to. To get resizeValue you can pass to the adapter mResizeValue based on the calculations relative to your device screen size. Something like
resizevalue = this.getResources().getDisplayMetrics().heightPixels / (NUM_COLS);
I figured out some other ways of calculating the height of each row based on screen size and then doing something similar, however, this requires that you do these calculations after you set your adapter and then update the changes to the adapter. It seems less efficient but I will share that methodology as well.
private void resizeGridView(GridView gridView, int items, int columns) {
ViewGroup.LayoutParams params = gridView.getLayoutParams();
int oneRowHeight = gridView.getHeight();
int rows = (int) (items / columns);
params.height = oneRowHeight * rows;
gridView.setLayoutParams(params);
}
Then after you set your adapter use
gridView.getViewTreeObserver().addOnGlobalLayoutListener(new
ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (!gridViewResized) {
gridViewResized = true;
resizeGridView(gridView, numItems, numColumns);
}
}
});
I used custom listview . Content comes from dynamic I want to same listview height same as content.
I used wrap_content but this does not help If I remove scrollview then its work
Code. "The vertically scrolling ScrollView should not contain another vertically scrolling widget (ListView)"
<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:background="#drawable/background_img"
android:scrollbars="none" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/lstsemtrack"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#f2e4e4"
android:dividerHeight="1dip"
>
</ListView>
Item list
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dip"
android:scrollbars="none" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="4" >
<TextView
android:id="#+id/txtBiology"
style="#style/sem_rowtext"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:text="Biology" />
<TextView
android:id="#+id/txtClass"
style="#style/sem_rowtext"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Biology - 101" />
<TextView
android:id="#+id/txtGrade"
style="#style/sem_rowtext"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Grade" />
<TextView
android:id="#+id/txtGrade"
style="#style/sem_rowtext"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Remove" />
</LinearLayout>
</LinearLayout>
output like below I want same as content no scrollview
Use this code
public class MyListView extends ListView {
public MyListView (Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView (Context context) {
super(context);
}
public MyListView (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
You shouldn't put a ListView inside a ScrollView because ListView itself is a view group that displays a list of scrollable items. If you use it inside scrollview it will not receive scroll events because they all are handled by the parent ScrollView. Using a ListView to make it not scroll is extremely expensive and goes against the whole purpose of ListView. You should NOT do this. Just use a LinearLayout instead.
However, if you really want to do this you can have a look at this: How can I put a ListView into a ScrollView without it collapsing?
In your parent layout set the height to wrap_content. This should work.
Other suggestions will not work, because there is no way to determine the height of the content of the ListView until after it's drawn into the screen (unless of course you have a fixed height, then use that as your ListView's height also).
What you can do is set the ListView's height AFTER drawing the elements inside it. You can do this in onWindowFocusChanged in your activity. As an example:
public void onWindowFocusChanged(boolean hasFocus) {
// get content height
int contentHeight = listView.getChildAt(0).getHeight();
// set listview height
LayoutParams lp = listView.getLayoutParams();
lp.height = contentHeight;
listView.setLayoutParams(lp);
}
Use Own Custom ListView
Create a class CustomListView.java and extend ListView like this..
public class CustomListView extends ListView {
private android.view.ViewGroup.LayoutParams params;
private int prevCount = 0;
public CustomListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas)
{
if (getCount() != prevCount)
{
int height = getChildAt(0).getHeight() + 1 ;
prevCount = getCount();
params = getLayoutParams();
params.height = getCount() * height;
setLayoutParams(params);
}
super.onDraw(canvas);
}
}
Then use it in xml
<yourPackageName.CustomListView
android:id="#+id/lstsemtrack"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#f2e4e4"
android:dividerHeight="1dip">
</yourPackageName.ListView>
As noted by Permita you should not have a ListView in a Scrollview. You should use for example a LinearLayout instead.
In some cases you have already made custom adapters you want to reuse. In that case these code snippets may be useful.
Old view:
...
<ListView android:id="#+id/myListView" ... />
...
New view:
...
<LinearLayout
android:orientation="vertical"
android:id="#+id/myLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...
Old code: (in dialog/fragment/activity)
YourAdapter listAdapter;
...
ListView listView = (ListView)rootView.findViewById(R.id.myListView);
...
if (listView != null) {
if (listAdapter == null) {
listAdapter = new YourAdapter(...);
listView.setAdapter(listAdapter);
} else {
listAdapter.notifyDataSetChanged();
}
}
New code: (doing some "listview"-stuff ourselves, to reuse views if possible)
YourAdapter listAdapter;
...
LinearLayout linearLayout = (LinearLayout) rootView.findViewById(R.id.myLinearLayout);
...
if (linearLayout != null) {
if (listAdapter == null) listAdapter = new YourAdapter(...);
for (int pos = 0; pos < listAdapter.getCount(); pos++) {
View existingView = linearLayout.getChildAt(pos);
if (existingView != null) listAdapter.getView(pos, existingView, linearLayout);
else linearLayout.addView(listAdapter.getView(pos, null, linearLayout));
}
while (linearLayout.getChildCount() > listAdapter.getCount()) linearLayout.removeViewAt(listAdapter.getCount());
}
UPDATE:
Or just add this to your adapter and call it instead of setAdapter on the list view and and notifyDataUpdated on the adapter:
public void updateOnLinearView(ViewGroup parentView) {
// Note reusing child views if there are any
for (int pos = 0; pos < getCount(); pos++) {
View existingView = parentView.getChildAt(pos);
if (existingView != null) getView(pos, existingView, parentView);
else parentView.addView(getView(pos, null, parentView));
}
while (parentView.getChildCount() > getCount())
parentView.removeViewAt(getCount());
}
couple of things to note here..
Do not use ListView inside the ScrollView
You will need to dynamically calculate the listView items to get the height for it.
Here is a function to do that..
public void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
if (listItem instanceof ViewGroup)
listItem.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
You will need to call the same in
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
setListViewHeightBasedOnChildren(listview)
}
try wrapping your list view with a LinearLayout and set the LinearLayout's height to wrap_content.
The problem is the "ListView" inside "ScrollView"
ListView have already an automatic scroll, so you can delete it
Use android:layout_height="match_parent", it will work I think.
Does the ViewPager have to be the only object present inside the activity layout?
I'm trying to implement something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/reader_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<android.support.v4.view.ViewPager
android:id="#+id/page_viewer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Gallery
android:id="#+id/miniatures_gallery"
android:layout_width="match_parent"
android:layout_height="wrap_content" /></LinearLayout>
Where should I have a big pager scrolling in the top (and I have it) and a smaller gallery scrolling under that.
This shows me only the pager and not the gallery.
Any suggestion?
The ViewPager does not support wrap_content as it (usually) never have all its children loaded at the same time, and can therefore not get an appropriate size (the option would be to have a pager that changes size every time you have switched page).
You can however set a precise dimension (e.g. 150dp) and match_parent works as well.
You can also modify the dimensions dynamically from your code by changing the height-attribute in its LayoutParams.
Assign layout weight to view pager as 1 & height = 0dp instead of wrap_content
<android.support.v4.view.ViewPager
android:id="#+id/page_viewer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
I solved the problem with a couple of hacks. Here is what it involves:
First, I needed a layout that would ignore ViewPager's height limit. Used it as a parent layout for ViewPager items.
public class TallLinearLayout extends LinearLayout {
public TallLinearLayout(Context context) {
super(context);
}
public TallLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TallLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
}
}
I then wrote the logic for resizing ViewPager:
private class ViewPagerContentWrapper implements OnGlobalLayoutListener {
private ViewPager mViewPager;
public ViewPagerContentWrapper(ViewPager viewPager) {
mViewPager = viewPager;
}
#Override
public void onGlobalLayout() {
int position = mViewPager.getCurrentItem();
check(position);
check(position + 1);
}
private void check(int position) {
ViewGroup vg = (ViewGroup) mViewPager.getChildAt(position);
View v = vg == null ? null : vg.getChildAt(0);
if (v != null) {
int height = v.getHeight();
if (height > mViewPager.getHeight()) {
resize(height);
}
}
}
private void resize(int height) {
mViewPager.setLayoutParams(
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
height
)
);
}
}
Which I registered as global layout listener:
viewPager.getViewTreeObserver().addOnGlobalLayoutListener(new ViewPagerContentWrapper(viewPager));
I would like the items of my GridView to be square. There are 2 columns and the items width is fill_parent (e.g. they take as much horizontal space as possible. The items are custom views.
How do I make the items height to be equal to their variable width?
There is a simpler solution when GridView columns are stretched. Just override the onMeasure of the GridView item layout with...
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
Or if you want to extend a View just do something really simple like:
public class SquareImageView extends ImageView
{
public SquareImageView(final Context context)
{
super(context);
}
public SquareImageView(final Context context, final AttributeSet attrs)
{
super(context, attrs);
}
public SquareImageView(final Context context, final AttributeSet attrs, final int defStyle)
{
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
{
final int width = getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec);
setMeasuredDimension(width, width);
}
#Override
protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh)
{
super.onSizeChanged(w, w, oldw, oldh);
}
}
It's worth noting that super.onMeasure() is not needed. onMeasured requirement is that you must call setMeasuredDimension.
I'm not sure this is possible with the current widgets. Your best bet might be to put your custom view in a custom "SquareView". This view could just contain 1 child view, and force the height to equal the width when its onLayout method is called.
I never tried to do something like that, but I think it shouldn't be too difficult. An alternative (and maybe slightly easier) solution might be to subclass your custom view's root layout (like, if it's a LinearLayout, make a SquareLinearLayout), and use that as a container instead.
edit : Here's a basic implementation which seems to work for me :
package com.mantano.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class SquareView extends ViewGroup {
public SquareView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onLayout(boolean changed, int l, int u, int r, int d) {
getChildAt(0).layout(0, 0, r-l, d-u); // Layout with max size
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View child = getChildAt(0);
child.measure(widthMeasureSpec, widthMeasureSpec);
int width = resolveSize(child.getMeasuredWidth(), widthMeasureSpec);
child.measure(width, width); // 2nd pass with the correct size
setMeasuredDimension(width, width);
}
}
It's designed to have a unique child, but I ignored all of the checks for the sake of simplicity. The basic idea is to measure the child with the width/height parameters set by the GridView (in my case, it uses numColumns=4 to calculate the width), and then do a second pass with the final dimensions, with height=width...
The layout is just a plain layout, which layouts the unique child at (0, 0) with the desired dimensions (right-left, down-up).
And here's the XML used for the GridView items :
<?xml version="1.0" encoding="utf-8"?>
<com.mantano.widget.SquareView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="wrap_content">
<LinearLayout android:id="#+id/item" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:orientation="horizontal"
android:gravity="center">
<TextView android:id="#+id/text" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Hello world" />
</LinearLayout>
</com.mantano.widget.SquareView>
I used a LinearLayout inside the SquareView in order to have it manage all the gravity possibilities, margins, etc.
I'm not sure how well (or bad) this widget would react to orientation and dimension changes, but it seems to work correctly.
It ends up being fairly easy to get the grid item square.
In your "GetView" method of your grid adapter, just do:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
GridView grid = (GridView)parent;
int size = grid.getRequestedColumnWidth();
TextView text = new TextView(getContext());
text.setLayoutParams(new GridView.LayoutParams(size, size));
// Whatever else you need to set on your view
return text;
}
I don`t know if it is the best way, but I accomplish that making my custom view to override onSizeChanged that way:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (getLayoutParams() != null && w != h) {
getLayoutParams().height = w;
setLayoutParams(getLayoutParams());
}
}
I use this custom view inside a LinearLayout and a GridView and in both it is showing square!
Try
<GridView
...
android:stretchMode="columnWidth" />
You can also play with other modes
This worked for me!
If you are like me and you can't use getRequestedColumnWidth() because your minSdk is lower than 16, I suggest this option.
in your fragment:
DisplayMetrics dm = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
int size = dm.widthPixels / nbColumns;
CustomAdapter adapter = new CustomAdapter(list, size, getActivity());
in your adapter (in getView(int position, View convertView, ViewGroup parent))
v.setLayoutParams(new GridView.LayoutParams(GridView.AUTO_FIT, size));
fragment.xml:
<GridView
android:id="#+id/gridView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:horizontalSpacing="3dp"
android:verticalSpacing="3dp"
android:numColumns="4"
android:stretchMode="columnWidth" />
custom_item.xml: (for example)
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/picture"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
Hope it will help someone!
if you set a static number of columns in the xml then you can take the width of the view and divide it by the number of columns.
if you are using auto_fit then it's going to be a bit tricky to get the column count ( there is no getColumnCount() method ), btw this question should help you somehow.
this is the code I'm using into the getView(...) method of the adapter with a fixed number of columns:
item_side = mFragment.holder.grid.getWidth() / N_COL;
v.getLayoutParams().height = item_side;
v.getLayoutParams().width = item_side;
This is what I am doing to show square cells in a GridView. I am not sure if you want square cells or something else.
GridView grid = new GridView( this );
grid.setColumnWidth( UIScheme.cellSize );
grid.setVerticalSpacing( UIScheme.gap );
grid.setStretchMode( GridView.STRETCH_COLUMN_WIDTH );
grid.setNumColumns( GridView.AUTO_FIT );
And then the view which I am creating for each cell I have to do the following to it:
cell.setLayoutParams( new GridView.LayoutParams(iconSize, iconSize) );
Based on SteveBorkman's answer, which is API 16+:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
GridView grid = (GridView)parent;
int size = grid.getColumnWidth();
if (convertView == null){
convertView = LayoutInflater.from(context).inflate(R.layout.your_item, null);
convertView.setLayoutParams(new GridView.LayoutParams(size, size));
}
//Modify your convertView here
return convertView;
}
Along the lines of #Gregory's answer, I had to wrap my image in a LinearLayout in order to keep GridView from manipulating its dimensions from square. Note that the outer LinearLayout should be set to have the dimension of the image, and the width of the GridView column should be the width of the image PLUS any margin. For example:
<!-- LinearLayout wrapper necessary to wrap image button in order to keep square;
otherwise, the grid view distorts it into rectangle.
also, margin top and right allows the indicator overlay to extend top and right.
NB: grid width is set in filter_icon_page.xml, and must correspond to size + margin
-->
<LinearLayout
android:id="#+id/sport_icon_lay"
android:layout_width="60dp"
android:layout_height="60dp"
android:orientation="vertical"
android:layout_marginRight="6dp"
android:layout_marginTop="6dp"
>
<ImageButton
android:id="#+id/sport_icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:maxHeight="60dp"
android:maxWidth="60dp"
android:scaleType="fitCenter"
/>
</LinearLayout>
and the GridView like:
<GridView
android:id="#+id/grid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
android:columnWidth="66dp"
android:numColumns="auto_fit"
android:verticalSpacing="25dp"
android:horizontalSpacing="24dp"
android:stretchMode="spacingWidth"
/>
In your activity
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
valX = displaymetrics.widthPixels/columns_number;
in the CustomAdapter of the GridView
v.setLayoutParams(new GridView.LayoutParams(GridView.AUTO_FIT, YourActivity.valX));