Proper way override fixed size custom view - android

I have a custom view with Yellow background. I plan to add a Red background TextView, with match_parent for both width and height on it. Here's what I had done.
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout mainView = (LinearLayout)this.findViewById(R.id.screen_main);
RateAppBanner rateAppBanner = new RateAppBanner(this);
mainView.addView(rateAppBanner);
}
}
RateAppBanner.java
public class RateAppBanner extends LinearLayout {
public RateAppBanner(Context context) {
super(context);
setOrientation(HORIZONTAL);
LayoutInflater.from(context).inflate(R.layout.rate_app_banner, this, true);
this.setBackgroundColor(Color.YELLOW);
}
}
rate_app_banner.xml
<?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:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="#ffffffff"
android:background="#ffff0000"
android:text="Hello World" />
</LinearLayout>
Now, I would like to have a fixed width & height custom view. I realize, after I'm having fixed width & height custom view, the added TextView doesn't obey match_parent attribute.
Here's the change I had done on custom view.
RateAppBanner.java
public class RateAppBanner extends LinearLayout {
...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = 320;
int desiredHeight = 50;
desiredWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, desiredWidth, getResources().getDisplayMetrics());
desiredHeight = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, desiredHeight, getResources().getDisplayMetrics());
super.onMeasure(desiredWidth, desiredHeight);
//MUST CALL THIS
setMeasuredDimension(desiredWidth, desiredHeight);
}
I realize the added TextView doesn't match_parent anymore!
Now, we can see the Yellow custom view is having fixed size 320x50. I expect the Red TextView will fill up the entire custom view due to match_parent attribute.
However, that's not the case. I believe my implementation for custom view's onMeasure isn't correct. May I know what is the correct way to fix this?
The complete source code can be downloaded from abc.zip

After lots of trial and error and doing research work, final found answer.
You have set measurements for layout but not for child view, so for that you need to put this in onMeasure method,
super.onMeasure(
MeasureSpec.makeMeasureSpec(desiredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(desiredHeight, MeasureSpec.EXACTLY));
Reference link : Inflated children of custom LinearLayout don't show when overriding onMeasure
And finally it's working :)

Related

How to set RecyclerView Max Height

I want to set Max Height of RecylerView.I am able to set max height using below code.Below code makes height 60% of current screen.
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int a = (displaymetrics.heightPixels * 60) / 100;
recyclerview1.getLayoutParams().height = a;
But now problem is that, if it have no item then also its height is 60%.
So I want to set its height 0 when no item in it.
I want to achieve like something.
if(recyclerview's height > maxHeight)
then set recyclerview's height to maxHeight
else dont change the height of recyclerview.
How can i set it?
Please help me, I am stuck with it
ConstraintLayout offers maximum height for its children.
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="300dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Here is something, you need. Subclass the RecyclerView and override onMeasure as following:
#Override
protected void onMeasure(int widthSpec, int heightSpec) {
heightSpec = MeasureSpec.makeMeasureSpec(Utils.dpsToPixels(240), MeasureSpec.AT_MOST);
super.onMeasure(widthSpec, heightSpec);
}
Make sure, you give proper number of pixels as the first argument in makeMeasureSpec(). I personally needed RecyclerView, that is 240dps at most.
I think this will work at least it worked for me
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Id_const_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/Id_my_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/margin_top_space"
android:scrollbars="vertical"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="#+id/Id_const_layout"
app:layout_constraintEnd_toEndOf="#+id/Id_const_layout"
app:layout_constraintHeight_max="150dp"
app:layout_constraintHeight_min="30dp"
app:layout_constraintStart_toStartOf="#+id/Id_const_layout"
app:layout_constraintTop_toTopOf="#+id/Id_const_layout"
>
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
You can change the constraintHeight_max and constraintHeight_min according to your needs.
We can optimize #Fattum 's method a little bit.
We can use app:maxHeight to set the maxHeight. One can use dp or px as you like.
public class MaxHeightRecyclerView extends RecyclerView {
private int mMaxHeight;
public MaxHeightRecyclerView(Context context) {
super(context);
}
public MaxHeightRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public MaxHeightRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
mMaxHeight = arr.getLayoutDimension(R.styleable.MaxHeightScrollView_maxHeight, mMaxHeight);
arr.recycle();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMaxHeight > 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
values/attrs.xml
<declare-styleable name="MaxHeightScrollView">
<attr name="maxHeight" format="dimension" />
</declare-styleable>
After trying way too many complicated suggestions, I finally figured this out through trial and error.
First, wrap the RecyclerView in some sort of layout. In my case, I used a constraint layout, and I even had other elements in that layout above and below the RecyclerView. Set the height of the layout to auto adjust by setting it to 0dp, then set the default layout height constraint as wrap and define a layout max height constraint.
Then, on your RecyclerView, set the height to wrap_content and layout_constrainedHeight to true.
Here is what it should look like:
<android.support.constraint.ConstraintLayout
android:layout_width="600px"
android:layout_height="0dp"
app:layout_constraintHeight_default="wrap"
app:layout_constraintHeight_max="450px">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintTop_ToTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
Hope this helps!
write this statements inside if else
and let me know ,
ViewGroup.LayoutParams params=recyclerview.getLayoutParams();
params.height=100;
recyclerview.setLayoutParams(params);
ill add my 2 cents i needed a recycler view with a max height and min height and wrap content between the 2
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="#+id/group_card_recycler_view_holder"
app:layout_constraintHeight_default="wrap"
app:layout_constraintHeight_max="#dimen/group_recycler_view_height"
app:layout_constraintHeight_min="#dimen/card_sentence_height"
android:layout_marginTop="#dimen/activity_horizontal_margin_8dp"
app:layout_constraintTop_toBottomOf="#id/group_name_container"
app:layout_constraintBottom_toTopOf="#id/myButtonLayout">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constrainedHeight="true"
android:id="#+id/group_card_recycler_view"/>
</androidx.constraintlayout.widget.ConstraintLayout>
You can use WRAP_CONTENT in your RecyclerView. It will auto measure your recyclerview according to its content.
You can also calculate current height and set max height. So Recyclerview will use wrap_content attribute value until max height.
public static void getTotalHeightofRecyclerView(RecyclerView recyclerView) {
RecyclerView.Adapter mAdapter = recyclerView.getAdapter();
int totalHeight = 0;
for (int i = 0; i < mAdapter.getItemCount(); i++) {
View mView = recyclerView.findViewHolderForAdapterPosition(i).itemView
mView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
totalHeight += mView.getMeasuredHeight();
}
if (totalHeight > 100) {
ViewGroup.LayoutParams params = recyclerView.getLayoutParams();
params.height = 100;
recyclerView.setLayoutParams(params);
}
}
The simplest way is to use ConstraintLayout and setting height constraint on Recycleview.
<android.support.constraint.ConstraintLayout
android:id="#+id/CL_OUTER_RV_Choose_Categories "
android:layout_width="match_parent"
android:layout_height="200dp">
<android.support.v7.widget.RecyclerView
android:id="#+id/RV_Choose_Categories"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_constrainedHeight="true" >
</android.support.v7.widget.RecyclerView>
</android.support.constraint.ConstraintLayout>
Please note that before Lollipop, the recycleview will not scroll. As a solution, add the below code in the activity's oncreate.
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
ConstraintLayout CL_OUTER_RV_Choose_Categories = findViewById(R.id.CL_OUTER_RV_Choose_Categories);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) CL_OUTER_RV_Choose_Categories.getLayoutParams();
lp.height = LinearLayout.LayoutParams.WRAP_CONTENT;
}
This will ensure that height is fixed only on supported devices, and the complete recycle view is shown on older devices.
Do let me know if there is any better way.
If you have a RecyclerView designed to hold items of equal physical size and you want a no-brainer way to limit its height without extending RecyclerView, try this:
int maxRecyclerViewHeightPixels = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
MAX_RECYCLERVIEW_HEIGHT_DP,
getResources().getDisplayMetrics()
);
MyRecyclerViewAdapter myRecyclerViewAdapter = new MyRecyclerViewAdapter(getContext(), myElementList);
myRecyclerView.setAdapter(myRecyclerViewAdapter);
ViewGroup.LayoutParams params = myRecyclerView.getLayoutParams();
if (myElementList.size() <= MAX_NUMBER_OF_ELEMENTS_TO_DISPLAY_AT_ONE_TIME) {
myRecyclerView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
//LinearLayout must be replaced by whatever layout type encloses your RecyclerView
}
else {
params.height = maxRecyclerViewHeightPixels;
myRecyclerView.setLayoutParams(params);
}
This works both for Linear and Grid RecyclerViews, you just need to play with the numbers a bit to suit your taste. Don't forget to set the height to WRAP_CONTENT after you populate the RecyclerView.
You may also have to set the height again every time the size of myElementList changes, but that's not a big issue.
I just had this same problem, and wanted to share a new, up-to-date answer. This one works on at least Android 9 and Android Studio v. 4.0.
Wrap the RecyclerView in a ConstraintLayout, as some of the answers here have suggested. But it does not appear you can set the maximum height of that layout in XML. Instead, you must set it in code, as follows: Give the ConstraintLayout an ID, and then, in your onCreate or onViewCreated method for the activity or fragment, respectively, in question - for example, to set a max height of 200 dp:
findViewById<ConstraintLayout>(R.id.[YOUR ID]).maxHeight = 200 // activity
view.findViewById<ConstraintLayout>(R.id.[YOUR ID]).maxHeight = 200 // fragment
Annoyingly, ConstraintLayout does expose an XML attribute android:minHeight but no android:maxHeight.
Even more annoyingly, RecyclerView itself apparently comes with an XML attribute named android:maxHeight, but that attribute does not work.
You can just set a specific height to any value in the xml code and set the visibility to gone. Then you set the visibility to Visible in Java, when you want to inflate it. Here is an example;
.xml
android:visibility="gone"
.java
recyclerView.setVisibility(View.VISIBLE);

Custom ViewPager inside ObservableScrollView not measuring Height Properly

I have a CustomViewPager inside an ObservableScrollView which looks like this:
It seems to measure the fragment but does not measure the height of the fragment which is off the screen. So I can't actually scroll up.
This is the code for the CustomViewPager:
public class CustomViewPager extends ViewPager {
private View view;
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
View tab = getChildAt(getCurrentItem());
int width = getMeasuredWidth();
int tabHeight = tab.getMeasuredHeight();
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
int fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(tabHeight + fragmentHeight + (int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()), MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}
}
NOTE: If I add say + 1000 to heightMeasureSpec in super.onMeasure(widthMeasureSpec, heightMeasureSpec); then I can scroll the size of the fragment as well as any extra space. But obviously this is not a preferred solution.
Here is my XML file:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/infoBox"
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="240dp">
<!-- Code for other views -->
</LinearLayout>
<com.github.ksoichiro.android.observablescrollview.ObservableScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="266dp"
android:orientation="vertical">
<com.ui.customviewpager.CustomViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</com.github.ksoichiro.android.observablescrollview.ObservableScrollView>
What seems to be the issue here? It seems like I am not the only one with this issue.
I implemented some of this design from this link here
ScrollView child should not have margin for the scrollView to display properly.
It seems that the margin is not taken into account when the scrollView measure its child.
Try removing margin of LinearLayout (you can use padding to reproduce the effect).
Why dont you use ObservableScrollView inside the fragment and put the viewpager inside a TouchInterceptionFrameLayout (from the same lib), then set setTouchInterceptionViewGroup of the ObservableScrollView to be the one containing your viewpager and do the logic on the TouchInterceptionListener in your activity to either move it up and down with with animation in order to mimic a smooth scrolling effect.
I hope that is that your are looking for to achieve and hopefully it will help you.
Ps: See the guy example app and source (Link), it is a bit complex but im sure you will find something.
Hello i am not much aware of ObservableScrollView but according to your problem you are not able to find out height of fragment. After searching a lot i am able to find out tutorial to get the height of fragments.
here is the link
http://adanware.blogspot.in/2012/06/android-getting-measuring-fragment.html
Hope, this will help you.

Android: TextView inside ScrollView: How to limit height

I have put a TextView inside a ScrollView in Android:
<ScrollView
android:layout_width = "fill_parent"
android:layout_height = "wrap_content">
<TextView
android:layout_width = "wrap_content"
android:layout_height = "wrap_content" />
</ScrollView>
With this configuration, the scrollview grows to wrap the textview-content.
How can I achieve that the scrollview takes only as much space as needed (for few text), but at the same time limit the size to 100dp (for long texts) ?
If I set layout_height to 100dp, a lot of space is wasted when the text is short
If I set layout_height to wrap_content, the scrollview runs the risk to fill the whole screen, but I don't want the whole sreen to only contain this scrollview. It should be 100dp heigh as a maximum.
The solution which was found:
Thank you for alls answers. For all people that have the same issue, here goes the accepted solution:
Create a custom ScrollView class an use this class inside your xml file instead ScrollView. Then override onMeasure() method:
public class ScrollMyVew extends ScrollView {
public static final int maxHeight = 100; // 100dp
// default constructors
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(dpToPx(getResources(),maxHeight), MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private int dpToPx(Resources res, int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
}
}
This solution is taken from https://stackoverflow.com/a/23617530/3080611
Try something like this:
<ScrollView
android:id="#+id/myScrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"/>
<TextView
android:id="#+id/myTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="your_value_here"/>
</ScrollView>
Dynamically change the size of the TextView if the number of characters exceeds a certain amount.
Ex:
TextView v = (TextView) findViewById(R.id.sometext);
LayoutParams lp = v.getLayoutParams();
lp.height = 50;
v.setLayoutParams(lp);

Inflated children of custom LinearLayout don't show when overriding onMeasure

I'm trying to show a MyCustomLinearLayout which extends LinearLayout. I'm inflating the MyCustomLinearLayout with the android:layout_height="match_parent" attribute.
What I want is to show an ImageView in this MyCustomLinearLayout. The height of this ImageView should be match_parent, and the width should be equal to the height. I'm trying to accomplish this by overriding the onMeasure() method. What happens, is that MyCustomLinearLayout does become square like it should, but the ImageView is not showing.
Below my used code. Please note that this is an extremely simplified version of my problem. The ImageView will be replaced by a more complex component.
public MyCustomLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_myimageview, this);
setBackgroundColor(getResources().getColor(R.color.blue));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(height, height);
}
The view_myimageview.xml file:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<ImageView
android:id="#+id/view_myimageview_imageview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/ic_launcher" />
</merge>
So when I override the onMeasure() method, the ImageView is not showing, when I don't override the onMeasure() method, the ImageView shows, but way too small, because the MyCustomLinearLayout's width is too small.
That's not how you override the onMeasure method especially of a default SDK layout. Right now with your code you just made the MyCustomLinearLayout square assigning it a certain value. But, you didn't measured its children so they are sizeless and don't appear on the screen.
I'm not sure this would work but try this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = getMeasuredHeight();
super.onMeasure(MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY));
}
Of course this will basically do the work of onMeasure twice but the ImageView should be now visible filling it's parent. There are other solutions but your question is a bit scarce on details.

Making GridView items square

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));

Categories

Resources