Android onMeasure change View's parameters twice - android

I've created a new view (extends ImageView) and override onMeasure method.
Xml code:
<by.bsuir.klondike.mainCardsView
android:id="#+id/imageview1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitEnd"
android:baselineAlignBottom="true"
android:src="#drawable/cardsmain"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
/>
At first I overrode a method like this
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
width = parentWidth/2;
height = 210*width/498;
this.setMeasuredDimension(width, height);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(width,height));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
According to Android developer onMeasure calls twice. On it's first call I receive parentWidth == 480 and parentHeight == 320. But on second call I receive parentWidth == 240 and parentHeight == 101.
It turns out, that the size is changing twice.
I've solve this problem using bool flag and change dimension only on first call.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (flag){
flag=false;
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
width = parentWidth/2;
height = 210*width/498;
}
this.setMeasuredDimension(width, height);
}
But I think that it's a wrong way.
How can I do it right?

Related

Dynamic height viewpager for images (onMeasure() not working)

I have a custom ViewPager inside of an RecyclerView. I made wrap_content height of ViewPager and put onMeasure() function inside of CustomViewPager. But when I scroll down and up the RecyclerView, sometimes ViewPager is disappear. I look many SO answers but onMeasure() function not working every time for me. Method:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); //Deneme
int height = 0;
for(int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if(h > height) height = h;
}
if (height != 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
ViewPager XML:
<com.monster.vpagerapp.Utils.ViewPagerFixedPost
android:id="#+id/postsViewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Also I use this in constructor of RecylerView:
setHasStableIds(true);
And use this in MainFragment
recyclerView.getRecycledViewPool().setMaxRecycledViews(1, 0); //for unnecessary refresh

MatchParent matching with activity, not with the WrapContent Parent

I have a adapter layout and inside it I add 4 elements that uses WrapContent(3x TextView and one a custom View). These 3 Views works fine, but the custom view isn't match to the container(LinearLayout) and let the TextView to do the work to choose the height of this layout.
When I use wrap_content in this custom view, this happens:
I tried to use this code to calculate the size of this view, using this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = 100;
int desiredHeight = 100;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
But I REALLY want to this view match with the parent, without any other parameter.
I tried to to this too inside the method onMeasure:
ViewGroup parent = (ViewGroup) getParent();
parent.getHeight; <- Return 0 >:(
Anyone can help me?

error overriding OnMeasure method

hi I'm working in my android app which plays videos and now I want to control my video to play in full screen I have a code but it contains errors it says that void is not valid type for variable onmeasure I dont know how to correct that
public class video extends Activity {
String SrcPath = "rtsp://v8.cache3.c.youtube.com/CjYLENy73wIaLQltaP8vg4qMsBMYDSANFEIJbXYtZ29vZ2xlSARSBXdhdGNoYOL6qv2DoMPrUAw=/0/0/0/video.3gp";
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video1);
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
VideoView myVideoView = (VideoView)findViewById(R.id.vview);
myVideoView.setVideoURI(Uri.parse(SrcPath));
myVideoView.setMediaController(new MediaController(this));
myVideoView.requestFocus();
}
}
thanks
The compilation problem is that there are bracket issues. Move
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
So it is not in onCreate(). Right now, methods can't directly enclose other methods.
Also, onMeasure() is not a method in Activity, but instead in View. You should review the documentation so you figure out exactly what you want to do.
It seems like you might want to have these two functions as methods in your class:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video1);
// Set up the video when you create the activity
VideoView myVideoView = (VideoView)findViewById(R.id.vview);
myVideoView.setVideoURI(Uri.parse(SrcPath));
myVideoView.setMediaController(new MediaController(this));
myVideoView.requestFocus();
}
// On measure should be a different method also in the class video
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Simple way to do dynamic but square layout

I'm using a GridView to display a bunch of views which are essentially LinearLayouts. I want the LinearLayouts to all be square, but I also want them to be dynamically sized--that is, there are two columns and I want the LinearLayouts to stretch depending on the size of the screen but remain square. Is there a way to do this through the xml layout or do I have to set the heights and widths programmatically?
A neat solution for square GridView items is to extend RelativeLayout or LinearLayout and override onMeasure like so:
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
With the new ConstraintLayout introduced in Android Studio 2.3, it is now quite easy to build responsive layouts.
In a parent ConstraintLayout, to make any of its children view/layout dynamically square, add this attribute
app:layout_constraintDimensionRatio="w,1:1"
w is to specify width-wise constraints and 1:1 ratio ensures square layout.
I've done this way:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int size;
if(widthMode == MeasureSpec.EXACTLY && widthSize > 0){
size = widthSize;
}
else if(heightMode == MeasureSpec.EXACTLY && heightSize > 0){
size = heightSize;
}
else{
size = widthSize < heightSize ? widthSize : heightSize;
}
int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}
With this implementation, your layout will be square, assuming the lower size between width and height. And it can even be set with dynamic values, like using weight inside a LinearLayout.
There's nothing in the xml that will let you link the width and height properties. Probably the easiest thing to do is to subclass LinearLayout and override onMeasure
#Override public 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);
}
I've used this to create views that are always square before. It should still work for a LinearLayout.
More info that will help doing this:
http://developer.android.com/guide/topics/ui/custom-components.html
http://developer.android.com/reference/android/view/View.MeasureSpec.html
We can do it with a very simple way - just call super.onMeasure() twice.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int squareLen = Math.min(width, height);
super.onMeasure(
MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY));
}
By calling super.onMeasure() twice, this is less efficient in terms of the drawing process, but it is a simple way to fix layout issues that the other answers can cause.
It is as simple as:
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);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if (widthMeasureSpec < heightMeasureSpec)
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
else
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
}
}
Add the following line in XML:
app:layout_constraintDimensionRatio="1:1"
Here's a solution that works for all layout parameters that can be set to view or viewgroup:
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthDesc = MeasureSpec.getMode(widthMeasureSpec);
int heightDesc = MeasureSpec.getMode(heightMeasureSpec);
int size = 0;
if (widthDesc == MeasureSpec.UNSPECIFIED
&& heightDesc == MeasureSpec.UNSPECIFIED) {
size = DP(defaultSize); // Use your own default size, in our case
// it's 125dp
} else if ((widthDesc == MeasureSpec.UNSPECIFIED || heightDesc == MeasureSpec.UNSPECIFIED)
&& !(widthDesc == MeasureSpec.UNSPECIFIED && heightDesc == MeasureSpec.UNSPECIFIED)) {
//Only one of the dimensions has been specified so we choose the dimension that has a value (in the case of unspecified, the value assigned is 0)
size = width > height ? width : height;
} else {
//In all other cases both dimensions have been specified so we choose the smaller of the two
size = width > height ? height : width;
}
setMeasuredDimension(size, size);
Cheers
My suggestion is to create a custom layout class that inherits from FrameLayout. Override the OnMeasure() method and put whatever control you want to be square inside that SquareFrameLayout.
This is how it's done in Xamarin.Android:
public class SquareFrameLayout : FrameLayout
{
private const string _tag = "SquareFrameLayout";
public SquareFrameLayout(Android.Content.Context context):base(context) {}
public SquareFrameLayout(IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer):base(javaReference, transfer) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs):base(context, attrs) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr):base(context, attrs, defStyleAttr) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes):base(context, attrs, defStyleAttr, defStyleRes) {}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
var widthMode = MeasureSpec.GetMode(widthMeasureSpec);
int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
var heightMode = MeasureSpec.GetMode(heightMeasureSpec);
int heightSize = MeasureSpec.GetSize(heightMeasureSpec);
int width, height;
switch (widthMode)
{
case MeasureSpecMode.Exactly:
width = widthSize;
break;
case MeasureSpecMode.AtMost:
width = Math.Min(widthSize, heightSize);
break;
default:
width = 100;
break;
}
switch (heightMode)
{
case MeasureSpecMode.Exactly:
height = heightSize;
break;
case MeasureSpecMode.AtMost:
height = Math.Min(widthSize, heightSize);
break;
default:
height = 100;
break;
}
Log.Debug(_tag, $"OnMeasure({widthMeasureSpec}, {heightMeasureSpec}) => Width mode: {widthMode}, Width: {widthSize}/{width}, Height mode: {heightMode}, Height: {heightSize}/{height}");
var size = Math.Min(width, height);
var newMeasureSpec = MeasureSpec.MakeMeasureSpec(size, MeasureSpecMode.Exactly);
base.OnMeasure(newMeasureSpec, newMeasureSpec);
}
}
If you want a View (or any other control) to be square (and centered) just add it to your layout the following way:
<your.namespace.SquareFrameLayout
android:id="#+id/squareContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center">
<View
android:id="#+id/squareContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</your.namespace.SquareFrameLayout>
Check out SquareLayout, an Android Library which provides a wrapper class for different Layouts, rendering them Squared dimensioned without losing any core functionalities.
The dimensions are calculated just before the Layout is rendered, hence there is no re-rendering or anything as such to adjust once the View is obtained.
To use the Library, add this to your build.gradle:
repositories {
maven {
url "https://maven.google.com"
}
}
dependencies {
compile 'com.github.kaushikthedeveloper:squarelayout:0.0.3'
}
The one you require is SquareLinearLayout.
For anyone wants solution With Kotlin, here's what I did with FrameLayout.
package your.package.name
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
class SquareLayout: FrameLayout {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (widthMeasureSpec < heightMeasureSpec)
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
else
super.onMeasure(heightMeasureSpec, heightMeasureSpec)
}
}
Try this code:
public class SquareRelativeLayout extends RelativeLayout {
public SquareRelativeLayout(Context context) {
super(context);
}
public SquareRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int size;
if (widthMode == MeasureSpec.EXACTLY && widthSize > 0) {
size = widthSize;
} else if (heightMode == MeasureSpec.EXACTLY && heightSize > 0) {
size = heightSize;
} else {
size = widthSize < heightSize ? widthSize : heightSize;
}
int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}
}
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>

Android ImageView adjusting parent's height and fitting width

Update : I solved this issue by using the method described in this answer
I'm a bit stuck with this issue, which I think should be pretty simple.
So my app downloads an image, and renders the bitmap in an ImageView, a child element of a RelativeLayout.
I would like the ImageView to fit the parent width, and to adapt it's size to keep the aspect ratio.
Here is my XML :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RelativeLayout android:id="#+id/banner" android:layout_width="fill_parent" android:layout_height="wrap_content"></RelativeLayout>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello"
/>
</LinearLayout>
And the code :
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
RelativeLayout banner = (RelativeLayout) findViewById(R.id.banner);
ImageView imgV = new ImageView(this);
imgV.setScaleType(ImageView.ScaleType.CENTER_CROP);
// I tried all the scale types : CENTER_INSIDE : same effect, FIT_CENTER : same effect...
imgV.setBackgroundColor(0x00FFFF00);
imgV.setAdjustViewBounds(Color.BLUE);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
banner.addView(imgV,params);
// Some code downloading the image stream
bitmap = BitmapFactory.decodeStream(stream);
imgV.setImageBitmap(bitmap);
}
Desired :
Result :
Thanks to #Julien and #js.
Here is complete solution of ImageView that will stretch bitmap height preserving aspect ratio even if bitmap is smaller than ImageView.
public class ResizableImageView extends ImageView {
public ResizableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
Drawable d = getDrawable();
if(d!=null){
// ceil not round - avoid thin vertical gaps along the left/right edges
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth());
setMeasuredDimension(width, height);
}else{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
You can use this class in your xml layouts instead ImageView.
<com.example.ResizableImageView
android:id="#+id/banner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/banner" />
You're probably looking for android:adjustViewBounds="true" in xml or imageView.setAdjustViewBounds(true) in Java.
As I mentioned in a comment, I subclassed the ImageView. I found my code, here you go :
protected class ResizableImageView extends ImageView
{
private Bitmap mBitmap;
// Constructor
public ResizableImageView(Context context)
{
super(context);
}
// Overriden methods
#Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
if(mBitmap != null)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * mBitmap.getHeight() / mBitmap.getWidth();
setMeasuredDimension(width, height);
}
else
{
super.onMeasure(widthMeasureSpec,
heightMeasureSpec);
}
}
#Override
public void setImageBitmap(Bitmap bitmap)
{
mBitmap = bitmap;
super.setImageBitmap(bitmap);
}
}
I think you should change your imgV width to "match_parent"
You should change the scale type to imgV.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
I made a slight change to #Seraphim S's solution to account for cases where the image may be wide, and the view's bounds also wide (for example, rotating the device to landscape mode).
public class ResizableImageView extends ImageView {
public ResizableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable d = getDrawable();
if (d != null) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (width >= height) {
width = (int) Math.ceil((float) height * (float) d.getIntrinsicWidth() / (float) d.getIntrinsicHeight());
} else {
height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth());
}
setMeasuredDimension(width, height);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

Categories

Resources