center children in a custom RelativeLayout - android

I have defined a custom relative layout to force a square view in my layout. But I am having trouble centering the children in that relative layout.
My custom RelativeLayout is defined as follows:
public class SquareView extends RelativeLayout {
public SquareView(Context context) {
super(context);
}
public SquareView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int size;
size = Math.min(widthSize, heightSize);
setMeasuredDimension(size, size);
int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}
}
and my xml is as follows:
<com.mypackage.SquareView
android:id="#+id/gameboard_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:gravity="center_horizontal"
android:padding="0dp" >
<LinearLayout
android:id="#+id/gameboard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="#drawable/xmlgameboard"
android:gravity="center_horizontal"
android:orientation="vertical" >
</LinearLayout>
</com.mypackage.SquareView>
When I view the xml file in graphical view I see the SquareView (gameboard_container) filling all available space (not square), but the child LinearLayout (gameboard) is square.
However, the child LinearLayout (gameboard) is also left justified, where as I require it to be centered.
How would I fix this?
Thank you.

After 3 days of struggling with this I've managed to solve it. The solution was to simply nest my SquareView inside a frame layout. Then I could centre the SquareView using
android:layout_gravity="center_horizontal"

Related

How to fill screen with rotated webview?

I have opened a bug report at https://bugs.chromium.org/p/chromium/issues/detail?id=1144713
When I rotate the webview it does not fill the screen.
I want to rotate the webview because AndroidTV requires landscape mode... but we are gonna have the TV in a portrait setting.
i use fill_parent && match_parent but it does not work in either case.
here is a screen shot of Android Studio preview.
Also to note when i test it like this it seems like the webview matchs the screen size but doesn't adapt to the rotation... it just rotates the webview and does not resize it to the new dimensions
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="#drawable/game_bg"
android:orientation="vertical"
tools:context="com.surveyswithgames.app.wheel.KeypadActivity">
<FrameLayout
android:id="#+id/frameParent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:layout_marginLeft="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="2dp"
android:background="#android:color/transparent"
android:foregroundGravity="center"
android:keepScreenOn="true">
<WebView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/keypadWebView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:rotation="-90"
/>
</FrameLayout>
</RelativeLayout>
UPDATE:
Based on the answer https://stackoverflow.com/a/64672893/1815624 I used a custom WebView class, yet it seems like setMeasuredDimension is not functioning...
public class CustomWebView extends WebView {
public CustomWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public CustomWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomWebView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int desiredWidth = MeasureSpec.getSize(heightMeasureSpec);//getMeasuredHeight();
int desiredHeight = MeasureSpec.getSize(widthMeasureSpec);//getMeasuredWidth();
Log.e("CustomWebView", "desiredWidth: "+ desiredWidth);
Log.e("CustomWebView", "desiredHeight: "+ desiredHeight);
setMeasuredDimension(desiredWidth, desiredHeight);
}
}
UPDATE 2:
I had to use the code and layout suggested by the answer https://stackoverflow.com/a/64672893/1815624 and then it worked...Cheers
The issue that you are having is that rotation occurs post-layout. This means that the measurements occur for the landscape mode (width > height) then the view is rotated but the new width is the old height and the new height is the old width. That is what you are seeing and it is not a bug.
One way around this is to create a custom view that swaps the width and height and pivots at the origin -90 degrees then translates the whole view down by the desired height.
Here is the code for the custom view:
RotatedWebView.kt
class RotatedWebView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val desiredWidth = MeasureSpec.getSize(heightMeasureSpec)
val desiredHeight = MeasureSpec.getSize(widthMeasureSpec)
translationY = desiredWidth.toFloat()
setMeasuredDimension(desiredWidth, desiredHeight)
}
}
RotatedWebView.java
public class RotatedWebView extends WebView {
public RotatedWebView(Context context) {
super(context);
}
public RotatedWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RotatedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = MeasureSpec.getSize(heightMeasureSpec);
int desiredHeight = MeasureSpec.getSize(widthMeasureSpec);
setTranslationY(desiredWidth);
setMeasuredDimension(desiredWidth, desiredHeight);
}
}
The layout will look something like this:
activity_main.xml
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/frameParent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
android:layout_marginStart="2dp"
android:layout_marginLeft="2dp"
android:layout_marginTop="2dp"
android:background="#android:color/transparent"
android:foregroundGravity="center"
android:keepScreenOn="true">
<com.example.webviewrotation.RotatedWebView
android:id="#+id/keypadWebView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rotation="-90"
android:transformPivotX="0dp"
android:transformPivotY="0dp" />
</FrameLayout>
</RelativeLayout>
And this is the result as seen in an emulator:
It would probably be better to place all the view manipulation into the custom view instead of splitting it between the code and the XML. I assume that you will physically rotate the TV so the web page shows as you want.

Custom LinearLayout - childs not visible

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

Square layout on GridLayoutManager for RecyclerView

I try to make a grid-layout with square images. I thought that it must be possible to manipulate the GridLayoutManager by manipulating onMeasure to do a
super.onMeasure(recycler, state, widthSpec, widthSpec);
instead of
super.onMeasure(recycler, state, widthSpec, heightSpec);
but unfortunately, that didn't work.
Any ideas?
To have the square elements in my RecyclerView, I provide a simple wrapper for my root View element; I use the following SquareRelativeLayout in place of RelativeLayout.
package net.simplyadvanced.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
/** A RelativeLayout that will always be square -- same width and height,
* where the height is based off the width. */
public class SquareRelativeLayout extends RelativeLayout {
public SquareRelativeLayout(Context context) {
super(context);
}
public SquareRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(VERSION_CODES.LOLLIPOP)
public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Set a square layout.
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}
Then, in my XML layout for the adapter, I've just referenced the custom view as shown in the following. Though, you can do this programmatically also.
<?xml version="1.0" encoding="utf-8"?>
<net.simplyadvanced.widget.SquareRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/elementRootView"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- More widgets here. -->
</net.simplyadvanced.widget.SquareRelativeLayout>
Note: Depending on which orientation your grid is, then you may want to have the width based off of height (GridLayoutManager.HORIZONTAL) instead of the height being based off the width (GridLayoutManager.VERTICAL).
Constraint layout solves this problem. Use app:layout_constraintDimensionRatio="H,1:1"
recyclerview_grid_layout.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="#+id/imageview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,1:1"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
EDIT
Set ImageView width to 0dp. match_parent is now deprecated for ConstraintLayout.
In case someone would like to scale the view differently - this is how you do it:
private static final double WIDTH_RATIO = 3;
private static final double HEIGHT_RATIO = 4;
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = (int) (HEIGHT_RATIO / WIDTH_RATIO * widthSize);
int newHeightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, newHeightSpec);
}
Starting API 26 (Support Library 26.0), one can use ConstraintLayout that exposes aspect ratio property to force views to be squared:
https://developer.android.com/training/constraint-layout/index.htm
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
...
}
...
dependencies {
compile 'com.android.support:appcompat-v7:26.0.2'
compile 'com.android.support.constraint:constraint-layout:1.1.0-beta1' //use whatever version is current
}
Example of layout I'm using in GridLayoutManager:
<?xml version="1.0" encoding="utf-8"?>
<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"
android:layout_margin="#dimen/margin_small"
android:background="#drawable/border_gray"
android:gravity="center">
<android.support.constraint.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- place your content here -->
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
app:layout_constraintDimensionRatio="h,1:1" is the key attribute here
A small update for ConstraintLayout for androidx.
Include this line to your build.gradle:
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
I wanted to get a RecycleView with GridLayoutManager with square CardViews and I used such a layout for items:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
>
<androidx.cardview.widget.CardView
android:id="#+id/cardView"
android:layout_width="0dp"
android:layout_height="0dp"
card_view:cardElevation="4dp"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
>
On the ConstraintLayout
layout_width="match_parent" is important to let the item fill as much space as RecyclerView provides
layout_height="wrap_content" do not let the item to fill all the height given by RecyclerView, but use the constrained height, provided by ConstraintLayout. In my case, when I used FrameLayout or LinearLayout, the items were "tall".
On the child node, in my case CardView
limiting size to zero is important: layout_width="0dp" and layout_height="0dp" it means, that width and height are contrained
layout_constraintDimensionRatio="H,1:1" makes the desired effect, by setting H you define that height is to be constrained 1:1 is the ratio.
See some detailed explanations on the offsite.
Please, try this extension of the FrameLayout. It performs double measuring to improve consistency. It also supports custom XML properties to set-up required aspect ration from layouts
public class StableAspectFrameLayout extends FrameLayout {
private int aspectWidth = 1;
private int aspectHeight = 1;
public StableAspectFrameLayout(Context context) {
this(context, null, 0);
}
public StableAspectFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public StableAspectFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
extractCustomAttrs(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public StableAspectFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
extractCustomAttrs(context, attrs);
}
private void extractCustomAttrs(Context context, AttributeSet attrs) {
if (attrs == null) return;
TypedArray a = context.getResources().obtainAttributes(attrs, R.styleable.StableAspectFrameLayout);
try {
aspectWidth = a.getInteger(R.styleable.StableAspectFrameLayout_aspect_width, 1);
aspectHeight = a.getInteger(R.styleable.StableAspectFrameLayout_aspect_height, 1);
} finally {
a.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newSpecWidth = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
int newH = Math.round(((float) getMeasuredWidth()) * aspectHeight / aspectWidth);
int newSpecHeigh = MeasureSpec.makeMeasureSpec(newH, MeasureSpec.EXACTLY);
super.onMeasure(newSpecWidth, newSpecHeigh);
}
}
And the content fo the attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- StableAspectFrameLayout -->
<declare-styleable name="StableAspectFrameLayout">
<attr name="aspect_width" format="integer"/>
<attr name="aspect_height" format="integer"/>
</declare-styleable>
</resources>
Once again, I recommend the relatively recent 'percent' layouts. Using the dependency 'com.android.support:percent:25.2.0', you can do something like this:
<android.support.percent.PercentFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/image"
app:layout_widthPercent="100%"
app:layout_aspectRatio="100%"
android:padding="10dp"
android:scaleType="centerCrop"
android:cropToPadding="true"
tools:background="#efdbed"
/>
</android.support.percent.PercentFrameLayout>
It's probably much faster than ConstraintLayout, though someday we probably won't care anymore.
I don't like chosen answer so let me provide mine: Instead of wrapping entire item layout in SomeDammyLayoutWithFixedAspectRatio you can hack GridLayoutManager and rewrite code inside measureChild. I've replaced these lines:
if (mOrientation == VERTICAL) {
wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
horizontalInsets, lp.width, false);
hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(),
verticalInsets, lp.height, true);
} else {
hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
verticalInsets, lp.height, false);
wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(),
horizontalInsets, lp.width, true);
}
to:
if (mOrientation == VERTICAL) {
wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
horizontalInsets, lp.width, false);
hSpec = wSpec;
} else {
hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
verticalInsets, lp.height, false);
wSpec = hSpec;
}
It seems to work fine.
Don't get me wrong, this is quite messy too, but at least this solution doesn't hurt app performance by extending view hierarchy
I had similar problem and I had to inflate the view which would be square in Grid of recycler view. Below is my way of doing it.
Inside onCreateViewHolder method I used the ViewTreeObserver and GlobalLayoutListener to get the measured width of the layout. The layout has match_parent value in the width attribute. Any my recycler view has layout in center horizontal.
final View view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_deals, parent, false);
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int side = view.getMeasuredWidth();
ViewGroup.LayoutParams lp = view.getLayoutParams();
lp.width = side;
lp.height = side;
view.setLayoutParams(lp);
}
});
reference image

Android ScrollView set height for displayed content

I'm having a lot of trouble trying solve the issue of having a static height scrollable area within a layout. I have three long lists that need to be displayed on the same screen and it would be entirely impractical to display all of the entries sequentially because then if you want to skip a category you need to scroll past possibly hundreds of entries.
Assume I have a scroll view with a Linear Layout inside of it, I want this to take up say, 250dp height on the screen at max and be independently scrollable once populated with more entries than can fit in 250dp's space.
What I have now is:
<ScrollView
android:minWidth="25px"
android:minHeight="150px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/XXXXXXX"
android:scrollbars="vertical">
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/XXXXXX" />
</ScrollView>
When populated, the scrollview and linearlayout just stretch as long as they needs to to fit the content and displays all of it instead of having a "window" of 250dp/px (any measurment would be nice) with the content scrollable within it.
I'm new to the android platform, so perhaps the answer is obvious and I just didn't understand the language, but any help would be greatly appreciated!
Thank you
--- SOLVED:
Put a linearLayout outside of the ScrollView with the height.
With some help from this answer I managed to wrap up a very basic ScrollView component you can use for this case:
Create a custom class extending a ScrollView and do the following modifications:
public class MaxHeightScrollView extends ScrollView {
private int maxHeight;
public MaxHeightScrollView(Context context) {
super(context);
}
public MaxHeightScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxHeight, 200); //200 is a default value
styledAttrs.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
And then one minor thing left is to declare your styleable in the attrs.xml file of your values folder(If you don't have one, just create an xml file with this name in the values folder of your project's res folder). Add the following lines there:
<declare-styleable name="MaxHeightScrollView">
<attr name="maxHeight" format="dimension" />
</declare-styleable>
And use your new ScrollView as follows:
<com.yourpackage.MaxHeightScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:maxHeight="300dp">
</com.yourpackage.MaxHeightScrollView>
Credits go to whizzle for quickly wrapping this up!
My simple solution.
Set ScrollView heigh in xml:
android:layout_height="wrap_content"
Add this code to set Max Height to 200 if current measured Height is > 200
sv.measure(0, 0);
if (nsv.getMeasuredHeight() > 200) {
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 200);
sv.setLayoutParams(lp);
}
ScrollView in my example is into LinearLayout, so I used LinearLayout.LayoutParams.
What you need is a maxHeight. But since views do not support it, you have to use a workaround.
Check out this answer:
https://stackoverflow.com/a/13811461/770467
Add a layout around your ScrollView with fixed height

Android: why is there no maxHeight for a View?

View's have a minHeight but somehow are lacking a maxHeight:
What I'm trying to achieve is having some items (views) filling up a ScrollView. When there are 1..3 items I want to display them directly. Meaning the ScrollView has the height of either 1, 2 or 3 items.
When there are 4 or more items I want the ScrollView to stop expanding (thus a maxHeight) and start providing scrolling.
However, there is unfortunately no way to set a maxHeight. So I probably have to set my ScrollView height programmatically to either WRAP_CONTENT when there are 1..3 items and set the height to 3*sizeOf(View) when there are 4 or more items.
Can anyone explain why there is no maxHeight provided, when there is already a minHeight?
(BTW: some views, like ImageView have a maxHeight implemented.)
None of these solutions worked for what I needed which was a ScrollView set to wrap_content but having a maxHeight so it would stop expanding after a certain point and start scrolling. I just simply overrode the onMeasure method in ScrollView.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(300, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
This might not work in all situations, but it certainly gives me the results needed for my layout. And it also addresses the comment by madhu.
If some layout present below the scrollview then this trick wont work – madhu Mar 5 at 4:36
In order to create a ScrollView or ListView with a maxHeight you just need to create a Transparent LinearLayout around it with a height of what you want the maxHeight to be. You then set the ScrollView's Height to wrap_content. This creates a ScrollView that appears to grow until its height is equal to the parent LinearLayout.
This worked for me to make it customizable in xml:
MaxHeightScrollView.java:
public class MaxHeightScrollView extends ScrollView {
private int maxHeight;
private final int defaultHeight = 200;
public MaxHeightScrollView(Context context) {
super(context);
}
public MaxHeightScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
if (!isInEditMode()) {
init(context, attrs);
}
}
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (!isInEditMode()) {
init(context, attrs);
}
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
if (!isInEditMode()) {
init(context, attrs);
}
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
//200 is a defualt value
maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxHeight, defaultHeight);
styledAttrs.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
attr.xml
<declare-styleable name="MaxHeightScrollView">
<attr name="maxHeight" format="dimension" />
</declare-styleable>
example layout
<blah.blah.MaxHeightScrollView android:layout_weight="1"
app:maxHeight="90dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:id="#+id/commentField"
android:hint="Say Something"
android:background="#FFFFFF"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:gravity="center_vertical"
android:maxLines="500"
android:minHeight="36dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</blah.blah.MaxHeightScrollView>
(I know this does not directly answer the question but might be helpful to others looking for maxHeight functionality)
ConstraintLayout offers maximum height for its children via
app:layout_constraintHeight_max="300dp"
app:layout_constrainedHeight="true"
or
app:layout_constraintWidth_max="300dp"
app:layout_constrainedWidth="true"
Sample usage here.
I would have commented on whizzle's answer if I could, but thought it useful to note that in order for me to solve this problem in the context of multi-window mode in Android N, I needed to change the code slightly to this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(MeasureSpec.getSize(heightMeasureSpec) > maxHeight) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
This allows for the layout to resize to be smaller than the max height, but also prevent it from being larger than the max height. I used this is a layout class that Overrides RelativeLayout and this allowed me to create a custom dialog with a ScrollView as the child of MaxHeightRelativeLayout that does not expand the full height of the screen and also shrinks to fit within the smallest widow size in multi-window for Android N.
As mentioned above, ConstraintLayout offers maximum height for its children via:
app:layout_constraintHeight_max="300dp"
app:layout_constrainedHeight="true"
Besides, if maximum height for one ConstraintLayout's child is uncertain until App running, there still has a way to make this child automatically adapt a mutable height no matter where it was placed in the vertical chain.
For example, we need to show a bottom dialog with a mutable header TextView, a mutable ScrollView and a mutable footer TextView. The dialog's max height is 320dp,when total height not reach 320dp ScrollView act as wrap_content, when total height exceed ScrollView act as "maxHeight=320dp - header height - footer height".
We can achieve this just through xml layout file:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="320dp">
<TextView
android:id="#+id/tv_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/black_10"
android:gravity="center"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="#id/scroll_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1"
app:layout_constraintVertical_chainStyle="packed"
tools:text="header" />
<ScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/black_30"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toTopOf="#id/tv_footer"
app:layout_constraintHeight_max="300dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tv_header">
<LinearLayout
android:id="#+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/tv_sub1"
android:layout_width="match_parent"
android:layout_height="160dp"
android:gravity="center"
android:textColor="#color/orange_light"
tools:text="sub1" />
<TextView
android:id="#+id/tv_sub2"
android:layout_width="match_parent"
android:layout_height="160dp"
android:gravity="center"
android:textColor="#color/orange_light"
tools:text="sub2" />
</LinearLayout>
</ScrollView>
<TextView
android:id="#+id/tv_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/black_50"
android:gravity="center"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/scroll_view"
tools:text="footer" />
</android.support.constraint.ConstraintLayout>
Most import code is short:
app:layout_constraintVertical_bias="1"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constrainedHeight="true"
Horizontal maxWidth usage is quite the same.
There is no way to set maxHeight. But you can set the Height.
To do that you will need to discovery the height of each item of you scrollView. After that just set your scrollView height to numberOfItens * heightOfItem.
To discovery the height of an item do that:
View item = adapter.getView(0, null, scrollView);
item.measure(0, 0);
int heightOfItem = item.getMeasuredHeight();
To set the height do that:
// if the scrollView already has a layoutParams:
scrollView.getLayoutParams().height = heightOfItem * numberOfItens;
// or
// if the layoutParams is null, then create a new one.
scrollView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, heightOfItem * numberOfItens));
Wrap your ScrollView around your a plainLinearLayout with layout_height="max_height", this will do a perfect job. In fact, I have this code in production from last 5 years with zero issues.
<LinearLayout
android:id="#+id/subsParent"
android:layout_width="match_parent"
android:layout_height="150dp"
android:gravity="bottom|center_horizontal"
android:orientation="vertical">
<ScrollView
android:id="#+id/subsScroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginEnd="15dp"
android:layout_marginStart="15dp">
<TextView
android:id="#+id/subsTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/longText"
android:visibility="visible" />
</ScrollView>
</LinearLayout>
My MaxHeightScrollView custom view
public class MaxHeightScrollView extends ScrollView {
private int maxHeight;
public MaxHeightScrollView(Context context) {
this(context, null);
}
public MaxHeightScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray styledAttrs =
context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
try {
maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_mhs_maxHeight, 0);
} finally {
styledAttrs.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (maxHeight > 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
style.xml
<declare-styleable name="MaxHeightScrollView">
<attr name="mhs_maxHeight" format="dimension" />
</declare-styleable>
Using
<....MaxHeightScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:mhs_maxHeight="100dp"
>
...
</....MaxHeightScrollView>
I have an answer here:
https://stackoverflow.com/a/29178364/1148784
Just create a new class extending ScrollView and override it's onMeasure method.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (maxHeight > 0){
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
switch (hMode){
case MeasureSpec.AT_MOST:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.AT_MOST);
break;
case MeasureSpec.UNSPECIFIED:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
break;
case MeasureSpec.EXACTLY:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.EXACTLY);
break;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
In case anyone needs it:
app:layout_constraintHeight_max="300dp"
It forces the View (that is inside a ConstraintLayout) to be 300dp as a max height. For those who want to do this programmatically, it goes like this:
val totalScreenHeight = displayMetrics.heightPixels
val layoutParams: ConstraintLayout.LayoutParams = viewThatIsInsideAConstraintLayout.layoutParams as ConstraintLayout.LayoutParams
layoutParams.matchConstraintMaxHeight = totalScreenHeight/2
viewThatIsInsideAConstraintLayout.layoutParams = layoutParams
Have you tried using the layout_weight value? If you set one it to a value greater than 0, it will stretch that view into the remaining space available.
If you had multiple views that needed to be stretched, then the value will become a weight between them.
So if you had two views both set to a layout_weight value of 1, then they would both stretch to fill in the space but they would both stretch to an equal amount of space. If you set one of them to the value of 2, then it would stretch twice as much as the other view.
Some more info here listed under Linear Layout.
i think u can set the heiht at runtime for 1 item just scrollView.setHeight(200px), for 2 items scrollView.setheight(400px) for 3 or more scrollView.setHeight(600px)
As we know devices running android can have different screen sizes. As we further know views should adjust dynamically and become the space which is appropriate.
If you set a max height you maybe force the view not to get enough space or take to less space. I know that sometimes it seems to be practically to set a max height. But if the resolution will ever change dramatically, and it will!, then the view, which has a max height, will look not appropriate.
i think there is no proper way to exactly do the layout you want. i would recommend you to think over your layout using layout managers and relative mechanisms. i don't know what you're trying to achieve but it sounds a little strange for me that a list should only show three items and then the user has to scroll.
btw. minHeight is not guaranteed (and maybe shouldn't exist either). it can have some benefit to force items to be visible while other relative items get smaller.
If anyone is considering using exact value for LayoutParams e.g.
setLayoutParams(new LayoutParams(Y, X );
Do remember to take into account the density of the device display otherwise you might get very odd behaviour on different devices. E.g:
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics d = new DisplayMetrics();
display.getMetrics(d);
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, (int)(50*d.density) ));
First get the item height in pixels
View rowItem = adapter.getView(0, null, scrollView);
rowItem.measure(0, 0);
int heightOfItem = rowItem.getMeasuredHeight();
then simply
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);
scrollView.getLayoutParams().height = (int)((heightOfItem * 3)*displayMetrics .density);
if you guys want to make a non-overflow scrollview or listview, just but it on a RelativeLayout with a topview and bottomview on top and bottom for it:
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/topview"
android:layout_below="#+id/bottomview" >
I used a custom ScrollView made in Kotlin which uses maxHeight. Example of use:
<com.antena3.atresplayer.tv.ui.widget.ScrollViewWithMaxHeight
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="100dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.antena3.atresplayer.tv.ui.widget.ScrollViewWithMaxHeight>
Here is the code of ScrollViewWidthMaxHeight:
import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView
import timber.log.Timber
class ScrollViewWithMaxHeight #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {
companion object {
var WITHOUT_MAX_HEIGHT_VALUE = -1
}
private var maxHeight = WITHOUT_MAX_HEIGHT_VALUE
init {
val a = context.obtainStyledAttributes(
attrs, R.styleable.ScrollViewWithMaxHeight,
defStyleAttr, 0
)
try {
maxHeight = a.getDimension(
R.styleable.ScrollViewWithMaxHeight_android_maxHeight,
WITHOUT_MAX_HEIGHT_VALUE.toFloat()
).toInt()
} finally {
a.recycle()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var heightMeasure = heightMeasureSpec
try {
var heightSize = MeasureSpec.getSize(heightMeasureSpec)
if (maxHeight != WITHOUT_MAX_HEIGHT_VALUE) {
heightSize = maxHeight
heightMeasure = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST)
} else {
heightMeasure = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.UNSPECIFIED)
}
layoutParams.height = heightSize
} catch (e: Exception) {
Timber.e(e, "Error forcing height")
} finally {
super.onMeasure(widthMeasureSpec, heightMeasure)
}
}
fun setMaxHeight(maxHeight: Int) {
this.maxHeight = maxHeight
}
}
which needs also this declaration in values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollViewWithMaxHeight">
<attr name="android:maxHeight" />
</declare-styleable>
</resources>

Categories

Resources