I have created a custom subclass of AppCompatButton called SquareButton, which forces a button to be square. The code for that subclass was found here: https://stackoverflow.com/a/36991823/7648952.
This button works fine and displays when the layout it's contained in is inflated outside of a RecyclerView, however when used with a RecyclerView the button does not display. When I change my layout and code to use a normal Button, the Button displays, so there doesn't seem to be anything wrong with the way I'm using RecyclerView. I have no idea why this might be.
SquareButton.java:
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.AppCompatButton;
import android.util.AttributeSet;
public class SquareButton extends AppCompatButton {
public SquareButton(Context context) {
super(context);
}
public SquareButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int size = width > height ? height : width;
setMeasuredDimension(size, size);
}
}
Screenshot of the SquareButton working when inflated outside of a RecyclerView:
Screenshot of the SquareButton not displaying inside of RecyclerView:
Screenshot of a regular Button working inside of RecyclerView:
It seems to me that this behavior is odd. Any help would be much appreciated.
Try this code block:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if(width > height){
setMeasuredDimension(getMeasuredHeight(), getMeasuredHeight());
}else {
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
}
}
In this usage, you will set widthMeasureSpec or heightMeasureSpec instead of direct width or height value.
Related
I have an imageview and i want to scale it with it's frame!
I used imageView.setScaleX() but this method only scales image within and image view X, Y, width and Height are the same.
What must i do to resize imageview with image?
You can add the property scaleType to the ImageView XML like this:
android:scaleType="fitXY"
More info:
https://robots.thoughtbot.com/android-imageview-scaletype-a-visual-guide
https://developer.android.com/reference/android/widget/ImageView.ScaleType.html
if you want to height change automatically when width changed, use this custom ImageView.
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
import java.lang.reflect.Field;
public class AspectRatioImageView extends ImageView {
public AspectRatioImageView(Context context) {
super(context);
}
public AspectRatioImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AspectRatioImageView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * getDrawable().getIntrinsicHeight()
/ getDrawable().getIntrinsicWidth();
int maxHeight;
try {
Field f = ImageView.class.getDeclaredField("mMaxHeight");
f.setAccessible(true);
maxHeight = (Integer) f.get(this);
} catch (NoSuchFieldException e) {
maxHeight = Integer.MAX_VALUE;
}
setMeasuredDimension(width, height > maxHeight ? maxHeight : height);
} catch (Exception e) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
Thanks to #saeid . I've replaced setScale with setWidth and solved my problem.
I am trying to make a relative layout bounded within a circle i.e the relative layout should be like the square shown in the figure below.
I am trying to set width and height of the layout as:
√((diameter)²/2) which is about 70 %
(source: yogaflavoredlife.com)
public class SquareLayout extends RelativeLayout {
public SquareLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int originalWidth = MeasureSpec.getSize(widthMeasureSpec);
int originalHeight = MeasureSpec.getSize(heightMeasureSpec);
int required = Math.min(originalWidth, originalHeight) * 7 / 10;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(required, required);
}
}
What I am getting is a rectangular layout instead of square layout:
Can anyone guide me where I am going wrong?
Sample usage:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.widget.SquareLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F55C5C">
</com.example.widget.SquareLayout>
</RelativeLayout>
Here's how I got the solution. First I created a square frame to hold all the layouts.
public class SquareFrame extends FrameLayout {
public SquareFrame(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int originalWidth = MeasureSpec.getSize(widthMeasureSpec);
int originalHeight = MeasureSpec.getSize(heightMeasureSpec);
int required = Math.min(originalWidth, originalHeight);
super.onMeasure(
MeasureSpec.makeMeasureSpec(required, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(required, MeasureSpec.EXACTLY));
}
}
Then inserted all layouts within that square frame.
<com.example.widget.SquareFrame
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#5CF5FC">
<com.example.widget.SquareLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F55C5C">
</com.example.widget.SquareLayout>
</com.example.widget.SquareFrame>
Here is what I got
a square, not a rectangle.
I have a fragment and below this fragment a container which (sometimes) contains a GridWiew. How can I scroll the whole screen on GridView’s scroll?
Firstly you will have to add scrollView as a parent which will be parent to both the fragment and gridView. In that scenario scrolling content based on gridview is not a desirable solution as it will result in 2 scroll . Instead you will have to disable scrolling of gridview and add height to gridview.This would though remove the recycling feature.
GridView of fixed height
package com.mytestapp
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.GridView;
import android.content.Context;
public class ExpandableHeightGridView extends GridView {
ExpandableHeightGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
ExpandableHeightGridView(Context context) {
super(context);
}
public ExpandableHeightGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
You ScrollView should be the root of the hierarchy if you want the children to get scrolled.
I am trying to make a custom view that is square, using the width as the height. I am also using a pre-defined layout which I inflate as it's UI. As soon as I overrode onMeasure, the custom view no longer appears. Here is my code:
public class MyView extends RelativeLayout{
public MyView(Context context) {
super(context);
addView(setupLayout(context));
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
addView(setupLayout(context));
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
addView(setupLayout(context));
}
private View setupLayout(Context context) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View myView = inflater.inflate(R.layout.view_layout, null);
return myView;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(widthMeasureSpec));
}
}
I have 2 questions:
How do I override onMeasure so that it draws my view the way I am expecting it to?
Is there any way I can make this more efficient in terms of the view hierarchy (i.e. not be putting a RelativeLayout inside a RelativeLayout)
You can use this code from Jan Němec's answer to a similar question :
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
public class SquareLayout extends LinearLayout {
public SquareLayout(Context context) {
super(context);
}
public SquareLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (width > (int)(mScale * height + 0.5)) {
width = (int)(mScale * height + 0.5);
} else {
height = (int)(width / mScale + 0.5);
}
super.onMeasure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
);
}
}
Or try to use this library project.
I'm trying to display 8 items inside a gridview. Sadly, the gridview height is always too little, so that it only shows the first row, and a little part of the second.
Setting android:layout_height="300dp" makes it work. wrap_content and fill_parent apparently not.
My grid view:
<GridView
android:id="#+id/myId"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:horizontalSpacing="2dp"
android:isScrollContainer="false"
android:numColumns="4"
android:stretchMode="columnWidth"
android:verticalSpacing="20dp" />
My items resource:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:minHeight="?android:attr/listPreferredItemHeight" >
<ImageView
android:id="#+id/appItemIcon"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="#android:drawable/ic_dialog_info"
android:scaleType="center" />
<TextView
android:id="#+id/appItemText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="My long application name"
android:gravity="center_horizontal"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
The issue does not seem related to a lack of vertical space.
What can I do ?
After (too much) research, I stumbled on the excellent answer of Neil Traft.
Adapting his work for the GridView has been dead easy.
ExpandableHeightGridView.java:
package com.example;
public class ExpandableHeightGridView extends GridView
{
boolean expanded = false;
public ExpandableHeightGridView(Context context)
{
super(context);
}
public ExpandableHeightGridView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ExpandableHeightGridView(Context context, AttributeSet attrs,
int defStyle)
{
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
Include it in your layout like this:
<com.example.ExpandableHeightGridView
android:id="#+id/myId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:horizontalSpacing="2dp"
android:isScrollContainer="false"
android:numColumns="4"
android:stretchMode="columnWidth"
android:verticalSpacing="20dp" />
Lastly you just need to ask it to expand:
mAppsGrid = (ExpandableHeightGridView) findViewById(R.id.myId);
mAppsGrid.setExpanded(true);
After using the answer from #tacone and making sure it worked, I decided to try shorting down the code. This is my result. PS: It is the equivalent of having the boolean "expanded" in tacones answer always set to true.
public class StaticGridView extends GridView {
public StaticGridView(Context context) {
super(context);
}
public StaticGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StaticGridView(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();
}
}
Another similar approach that worked for me, is to calculate the height for one row and then with static data (you may adapt it to paginate) you can calculate how many rows you have and resize the GridView height easily.
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);
}
Use this code after setting the adapter and when the GridView is drawn or you will get height = 0.
gridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (!gridViewResized) {
gridViewResized = true;
resizeGridView(gridView, numItems, numColumns);
}
}
});
Found tacones answer helpfull... so i ported it to C# (Xamarin)
public class ExpandableHeightGridView: GridView
{
bool _isExpanded = false;
public ExpandableHeightGridView(Context context) : base(context)
{
}
public ExpandableHeightGridView(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public ExpandableHeightGridView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
{
}
public bool IsExpanded
{
get { return _isExpanded; }
set { _isExpanded = value; }
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (IsExpanded)
{
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.MakeMeasureSpec( View.MeasuredSizeMask, MeasureSpecMode.AtMost);
base.OnMeasure(widthMeasureSpec,expandSpec);
var layoutParameters = this.LayoutParameters;
layoutParameters.Height = this.MeasuredHeight;
}
else
{
base.OnMeasure(widthMeasureSpec,heightMeasureSpec);
}
}
}
Jacob R solution in Kotlin:
class ExpandableHeightGridView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : GridView(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST)
super.onMeasure(widthMeasureSpec, expandSpec)
layoutParams.height = measuredHeight
}
}
After adding GridView to RecyclerView I got a full-size GridView (all rows are visible), as expected.
Just calculate the height for AT_MOST and set to on measure. Here GridView Scroll will not work so. Need to use Vertical Scroll View explicitly.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightSpec;
if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
heightSpec = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
}
else {
// Any other height should be respected as is.
heightSpec = heightMeasureSpec;
}
super.onMeasure(widthMeasureSpec, heightSpec);
}