I am trying to create a textview that will always be squared so I have implemented this custom class.
public class SquareTextView extends TextView {
public SquareTextView(Context context) {
super(context);
}
public SquareTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int max = Math.max(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(max, max);
}
}
Here is an example layout that illustrates the problem:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp">
<com.mypackage.SquareTextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="right|top"
android:background="#000"
android:gravity="center"
android:padding="4dp"
android:text="1"
android:textColor="#FFF"/>
</LinearLayout>
Here is an image of this
This works great in getting the view squared, however, it seems like the gravity gets messed up. With this, the text seems to always be in the top left corner. How can I have a TextView that will always be squared but still keep the gravity or at least be able to center the text?
EDIT: After some testing I have noticed that if you set the width or height to a specific dp size the gravity seems to be working again. So it probably has to do with the WRAP_CONTENT attribute. Will that be handled in another way in the onmeasure method that could cause my own method to not work as expected?
Hope you have already got the answer by now. If not, you can use this:
public class TextAlphaSquareTextView extends AppCompatTextView {
private int mTextAlpha = 0;
private boolean isSquare = false;
public TextAlphaSquareTextView(Context context) {
super(context);
init(null);
}
public TextAlphaSquareTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public TextAlphaSquareTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
if (attrs == null) {
} else {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TextAlphaSquareTextView);
mTextAlpha = a.getInteger(R.styleable.TextAlphaSquareTextView_textAlpha, 100);
isSquare = a.getBoolean(R.styleable.TextAlphaSquareTextView_squareMode, false);
a.recycle();
if(mTextAlpha < 0 || mTextAlpha > 100)
throw new IllegalArgumentException("Alpha range should be b/w 0 to 100 (in percentage)");
else {
setAlphaOnTextColor();
}
}
setText(getText());
}
void setAlphaOnTextColor() {
int alpha = ((255 * mTextAlpha) / 100);
setTextColor(getTextColors().withAlpha(alpha));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (isSquare) {
int width = this.getMeasuredWidth();
int height = this.getMeasuredHeight();
int size = Math.max(width, height);
int widthSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(widthSpec, heightSpec);
}
}
}
you need to call super.onMeasure() again with EXACT spec and the calculated size, since setMeasureDimension() seems to be ignoring the gravity.
Related
I have made a custom TabLayout with a ViewPager and am using the TabLayout in scrollable mode:
I need it to be scrollable as the number of dates can vary to as many as 15-20:
<com.example.project.recommendedapp.CustomScrollableTabLayout
android:id="#+id/tab_layout_movie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:elevation="6dp"
android:minHeight="?attr/actionBarSize"
app:tabGravity="fill"
app:tabMode="scrollable"
app:tabTextAppearance="?android:textAppearanceMedium"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
The custom class i used as per another similar question:
TabLayout not filling width when tabMode set to 'scrollable'
The custom tablayout class is:
package com.example.project.recommendedapp;
import android.content.Context;
import android.graphics.Point;
import android.support.design.widget.TabLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import java.lang.reflect.Field;
public class CustomScrollableTabLayout extends TabLayout {
private Context mContext;
Point size;
public CustomScrollableTabLayout(Context context) {
super(context);
mContext=context;
size = new Point();
}
public CustomScrollableTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext=context;
size = new Point();
}
public CustomScrollableTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext=context;
size = new Point();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
try {
if (getTabCount() == 0) {
return;
}else {
Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
display.getSize(size);
int width = size.x;
Field field = TabLayout.class.getDeclaredField("mScrollableTabMinWidth");
field.setAccessible(true);
field.set(this, (width / getTabCount()));
Log.d("FragmentCreate",String.valueOf(width / getTabCount()));
}
} catch (Exception e) {
Log.e("FragmentCreate","Error while setting width",e);
}
}
}
I looked all over for an answer to this exact problem. In my case I was dynamically adding and removing tabs, so I wanted it to fill the screen when there were only a few tabs, but start scrolling when there were too many rather than shrinking them or putting the titles on two lines. Using the following custom tab layout finally got it working for me. It was key to set the minimum width before calling super.onMeasure().
public class CustomTabLayout extends TabLayout {
public CustomTabLayout(Context context) {
super(context);
}
public CustomTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup tabLayout = (ViewGroup)getChildAt(0);
int childCount = tabLayout.getChildCount();
if( childCount != 0 ) {
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
int tabMinWidth = displayMetrics.widthPixels/childCount;
for(int i = 0; i < childCount; ++i){
tabLayout.getChildAt(i).setMinimumWidth(tabMinWidth);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Set the tab mode to scrollable in the xml.
<com.package.name.CustomTabLayout
android:id="#+id/my_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable">
Here is my solution.
public class MyTabLayout extends TabLayout {
public MyTabLayout(Context context) {
super(context);
}
public MyTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup tabLayout = (ViewGroup)getChildAt(0);
int childCount = tabLayout.getChildCount();
int widths[] = new int[childCount+1];
for(int i = 0; i < childCount; i++){
widths[i] = tabLayout.getChildAt(i).getMeasuredWidth();
widths[childCount] += widths[i];
}
int measuredWidth = getMeasuredWidth();
for(int i = 0; i < childCount; i++){
tabLayout.getChildAt(i).setMinimumWidth(measuredWidth*widths[i]/widths[childCount]);
}
}
}
My solution is slightly different, since I tried the one above and it didn't work on some devices. I've noticed that if all tabs are visible on the screen and if we set tabMode to fixed, TabLayout fills its given width. When we use scrollable, TabLayout acts like its tabGravity is set to center.
So, I calculate the sum of widths of all tabs in TabLayout and if it is lower than measured width I set its tabMode to MODE_FIXED, otherwise to MODE_SCROLLABLE:
public class CustomTabLayout extends TabLayout {
private static final String TAG = CustomTabLayout.class.getSimpleName();
public CustomTabLayout(Context context) {
super(context);
}
public CustomTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getTabCount() == 0)
return;
try {
ViewGroup tabLayout = (ViewGroup)getChildAt(0);
int widthOfAllTabs = 0;
for (int i = 0; i < tabLayout.getChildCount(); i++) {
widthOfAllTabs += tabLayout.getChildAt(i).getMeasuredWidth();
}
setTabMode(widthOfAllTabs <= getMeasuredWidth() ? MODE_FIXED : MODE_SCROLLABLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}
For those landing here from google like I did here is my 2021 Kotlin solution.
It achieves the following which I couldn't do from other answers:
If not many tabs they expand to fill the whole width.
If lots of tabs they are scrollable so they aren't squished.
Works correctly even if tabLayout is not the full width of the screen.
To achieve this I created a subclass of TabLayout that overrides onMeasure
class ScalableTabLayout : TabLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val tabLayout = getChildAt(0) as ViewGroup
val childCount = tabLayout.childCount
if (childCount != 0) {
val widthPixels = MeasureSpec.getSize(widthMeasureSpec)
val tabMinWidth = widthPixels / childCount
var remainderPixels = widthPixels % childCount
tabLayout.forEachChild {
if (remainderPixels > 0) {
it.minimumWidth = tabMinWidth + 1
remainderPixels--
} else {
it.minimumWidth = tabMinWidth
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
And then used it in my layout file:
<com.package.name.ScalableTabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabMode="scrollable" />
both tabMaxWidth="0dp" and tabMode="scrollable" are required
For Android Image View .. how to scale 1081*373 Image Button to match the width of Image View and be fixed height ??
i tried match parent for width if image button but it' details not appear ... look like Zoomed image ?? and can't use Gravity ??
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/mainempty"
/>
<ImageButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/about"
/>
Try it!
public class MyBanner extends ImageView {
public MyBanner(Context context) {
super(context);
}
public MyBanner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyBanner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * 373 / 1081;
setMeasuredDimension(width, height);
}
}
in xml
<your.package.MyBanner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/about"/>
I have this code:
holder.ImageMain.setMaxHeight(holder.ImageMain.getWidth());
My intention is to have a square imageview if the image is big and horizontal. the code will set a height that is the same as the width.
But for some reason it's not working. The imageView has been long and horizontal.
Do you have any idea what's wrong with it?
You can try to use
android:adjustViewBounds= "true"
or to a more controled case, use instead a custom ImageView that in the onMeasure method adjust it size like you want. For example here are an example to create a 16/9 ImageView
public class SixteenNineImageView extends ImageView {
public SixteenNineImageView(Context context) {
super(context);
}
public SixteenNineImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SixteenNineImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SixteenNineImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = getMeasuredWidth();
final int height = (width * 9) / 16;
setMeasuredDimension(width, height);
}
}
I've created working custom view for imageView to be able scale down and up by percentages according to diplay screen dimension. In preview tab in AndroidStudio I do not see any errors, preview is working. However my custom view is not changing. When I set to 10% of total width which is .1 my custom view is not changing in preview. When I start my app, my custom view is working fine. I am not sure what am I doing wrong. I will be glad for tip. Thank you.
Here is my xml class:
<RelativeLayout 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">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/logo"
android:layout_centerInParent="true" />
<com.example.widgets.ImageViewWidthFix
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/blue_logo"
android:layout_centerInParent="true"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
app:percentageWidth=".3" />
</RelativeLayout>
Here is my java class:
public class ImageViewWidthFix extends ImageView {
private float percentageWidth;
private float percentageHeight;
public ImageViewWidthFix(Context context) {
super(context);
init(context, null);
}
public ImageViewWidthFix(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ImageViewWidthFix(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ImageViewWidthFix(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
layoutParams.width = (int) (percentageWidth == 1 ? layoutParams.width : displayMetrics.widthPixels * percentageWidth);
layoutParams.height = (int) (percentageHeight == 1 ? layoutParams.height : displayMetrics.heightPixels * percentageHeight);
setLayoutParams(layoutParams);
invalidate();
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ImageViewFixedDimension);
percentageWidth = a.getFloat(R.styleable.ImageViewFixedDimension_percentageWidth, 1);
percentageHeight = a.getFloat(R.styleable.ImageViewFixedDimension_percentageHeight, 1);
a.recycle();
}
}
}
You aren't calling setMeasuredDimension with the new dimensions of the view. That's going to cause problems. From the docs:
CONTRACT: When overriding this method, you must call
setMeasuredDimension(int, int) to store the measured width and height
of this view. Failure to do so will trigger an IllegalStateException,
thrown by measure(int, int). Calling the superclass' onMeasure(int,
int) is a valid use.
While you're not getting an exception because the superclass is calling it, you aren't correctly setting them either which will cause problems elsewhere.
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);
}