Android set height and width of Custom view programmatically
If you know the exact size of the view, just use setLayoutParams()
:
graphView.setLayoutParams(new LayoutParams(width, height));
Or in Kotlin:
graphView.layoutParams = LayoutParams(width, height)
However, if you need a more flexible approach you can override onMeasure()
to measure the view more precisely depending on the space available and layout constraints (wrap_content
, match_parent
, or a fixed size). You can find more details about onMeasure()
in the android docs.
Custom view's onMeasure: how to get width based on height
Update: Modified code to fix some things.
First, let me say that you asked a great question and laid out the problem very well (twice!) Here is my go at a solution:
It seems that there is a lot going on with onMeasure
that, on the surface, doesn't make a lot of sense. Since that is the case, we will let onMeasure
run as it will and at the end pass judgment on the View
's bounds in onLayout
by setting mStickyWidth
to the new minimum width we will accept. In onPreDraw
, using a ViewTreeObserver.OnPreDrawListener
, we will force another layout (requestLayout
). From the documentation (emphasis added):
boolean onPreDraw ()
Callback method to be invoked when the view tree is about to be drawn. At this point, all views in the tree have been measured and
given a frame. Clients can use this to adjust their scroll bounds or
even to request a new layout before drawing occurs.
The new minimum width set in onLayout
will now be enforced by onMeasure
which is now smarter about what is possible.
I have tested this with your example code and it seems to work OK. It will need much more testing. There may be other ways to do this, but that is the gist of the approach.
CustomView.java
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
public class CustomView extends View
implements ViewTreeObserver.OnPreDrawListener {
private int mStickyWidth = STICKY_WIDTH_UNDEFINED;
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
logMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
int desiredHeight = 10000; // some value that is too high for the screen
int desiredWidth;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
// Height
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desiredHeight, heightSize);
} else {
height = desiredHeight;
}
// Width
if (mStickyWidth != STICKY_WIDTH_UNDEFINED) {
// This is the second time through layout and we are trying renogitiate a greater
// width (mStickyWidth) without breaking the contract with the View.
desiredWidth = mStickyWidth;
} else if (height > BREAK_HEIGHT) { // a number between onMeasure's two final height requirements
desiredWidth = ARBITRARY_WIDTH_LESSER; // arbitrary number
} else {
desiredWidth = ARBITRARY_WIDTH_GREATER; // arbitrary number
}
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(desiredWidth, widthSize);
} else {
width = desiredWidth;
}
Log.d(TAG, "setMeasuredDimension(" + width + ", " + height + ")");
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int w = right - left;
int h = bottom - top;
super.onLayout(changed, left, top, right, bottom);
// Here we need to determine if the width has been unnecessarily constrained.
// We will try for a re-fit only once. If the sticky width is defined, we have
// already tried to re-fit once, so we are not going to have another go at it since it
// will (probably) have the same result.
if (h <= BREAK_HEIGHT && (w < ARBITRARY_WIDTH_GREATER)
&& (mStickyWidth == STICKY_WIDTH_UNDEFINED)) {
mStickyWidth = ARBITRARY_WIDTH_GREATER;
getViewTreeObserver().addOnPreDrawListener(this);
} else {
mStickyWidth = STICKY_WIDTH_UNDEFINED;
}
Log.d(TAG, ">>>>onLayout: w=" + w + " h=" + h + " mStickyWidth=" + mStickyWidth);
}
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
if (mStickyWidth == STICKY_WIDTH_UNDEFINED) { // Happy with the selected width.
return true;
}
Log.d(TAG, ">>>>onPreDraw() requesting new layout");
requestLayout();
return false;
}
protected void logMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
String measureSpecHeight;
String measureSpecWidth;
if (heightMode == MeasureSpec.EXACTLY) {
measureSpecHeight = "EXACTLY";
} else if (heightMode == MeasureSpec.AT_MOST) {
measureSpecHeight = "AT_MOST";
} else {
measureSpecHeight = "UNSPECIFIED";
}
if (widthMode == MeasureSpec.EXACTLY) {
measureSpecWidth = "EXACTLY";
} else if (widthMode == MeasureSpec.AT_MOST) {
measureSpecWidth = "AT_MOST";
} else {
measureSpecWidth = "UNSPECIFIED";
}
Log.d(TAG, "Width: " + measureSpecWidth + ", " + widthSize + " Height: "
+ measureSpecHeight + ", " + heightSize);
}
private static final String TAG = "CustomView";
private static final int STICKY_WIDTH_UNDEFINED = -1;
private static final int BREAK_HEIGHT = 1950;
private static final int ARBITRARY_WIDTH_LESSER = 200;
private static final int ARBITRARY_WIDTH_GREATER = 800;
}
getWidth() and getHeight() for custom view give wrong values
I'm guessing you have already seen this?
Custom View Height and Width
Or perhaps this is a different problem.
Setting suggested width and height to a custom view in android
Measuring a custom view is actually easier than it looks. You should take a look at MeasureSpec.
You generally need to handle cases where your view can be given a specific size, wrap_content
or match_parent
.
When onMeasure
is called, it's given widthMeasureSpec
and heightMeasureSpec
parameters, which you can use in combination with MeasureSpec
to get
width
andheight
modewidth
andheight
size
Mode can be one of three pre-defined values in MeasureSpec
:
UNSPECIFIED
The parent has not imposed any constraint on the child. It can be whatever size it wants.
EXACTLY
The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
AT_MOST
The child can be as large as it wants up to the specified size.
What you need to do is to cover these cases, which ~mostly~ looks like this:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
width = //Calculation
} else if (widthMode == MeasureSpec.AT_MOST) {
width = //Calculation
} else {
// UNSPECIFIED
width = //Calculation
}
if (heightMode == MeasureSpec.EXACTLY) {
height = //Calculation
} else if (heightMode == MeasureSpec.AT_MOST) {
height = //Calculation
} else {
// UNSPECIFIED
height = //Calculation
}
setMeasuredDimension(width, height);
}
There is also case where you should measure child views if your custom view is a ViewGroup
and can contain 1 or many child views. This is a piece of code I wrote couple years ago for measuring a custom ViewGroup
which can have 1 child ( mChild
) at most and needs to calculate its height / width properties by also thinking it'll draw a stroke ( mStrokeWidth
) around the child.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
measureChild(mChild, widthMeasureSpec, heightMeasureSpec);
int childWidth = mChild.getMeasuredWidth();
int childHeight = mChild.getMeasuredHeight();
if (widthMode == MeasureSpec.EXACTLY) {
width = MeasureSpec.getSize(widthMeasureSpec);
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, (int) (childWidth + (mStrokeWidth * 2)));
}
if (heightMode == MeasureSpec.EXACTLY) {
height = MeasureSpec.getSize(heightMeasureSpec);
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, (int) (childHeight + (mStrokeWidth * 2)));
}
setMeasuredDimension(width, height);
}
How to set width and height for custom view in programmatically?
Override the onMeasure()
method, have a look here
Change Custom View Width And Height In Android
Following is the solution,
Modified onCreate from Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.crop_test_layout);
imageView = (ImageView)findViewById(R.id.android_image);
cropBox = new CropBox(this, imageView);
relativeLayout = (RelativeLayout)findViewById(R.id.crop_test_layout);
relativeLayout.addView(cropBox);
}
Modified CropBox class:
public class CropBox extends View {
private static final int CROP_BOX_START_X = 5;
private static final int CROP_BOX_START_Y = 5;
private static final int CROP_BOX_END_X = 305;
private static final int CROP_BOX_END_Y = 105;
private static final int DRAG_SQUARE = 75;
public ImageView mImageView;
boolean mIsFirstClick = false;
private Paint paint = new Paint();
private Rect mRect;
public CropBox(Context context, ImageView aBaseView) {
super(context);
mImageView = aBaseView;
mRect = new Rect(CROP_BOX_START_X, CROP_BOX_START_Y, CROP_BOX_END_X, CROP_BOX_END_Y);
setOnTouchListener(new Crop());
}
public CropBox(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
@Override
public void onDraw(Canvas canvas) {
paint.setStrokeWidth(2);
paint.setColor(Color.CYAN);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(mRect, paint);
canvas.drawLine(mRect.right-DRAG_SQUARE, mRect.bottom-DRAG_SQUARE,
mRect.right, mRect.bottom-DRAG_SQUARE, paint);
canvas.drawLine(mRect.right-DRAG_SQUARE, mRect.bottom-DRAG_SQUARE,
mRect.right-DRAG_SQUARE, mRect.bottom, paint);
}
class Crop implements OnTouchListener {
private static final int NONE = 0;
private static final int BOX_DRAG = 1;
private static final int BORDER_DRAG = 2;
private int mode = NONE;
private PointF start = new PointF();
public boolean onTouch(View view, MotionEvent event) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams();
switch(event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
start.set(event.getX(), event.getY());
if((event.getX() <= mRect.right && event.getX() >=(mRect.right - DRAG_SQUARE))
&& (event.getY() >= mRect.top && event.getY() >=(mRect.bottom - DRAG_SQUARE))){
mode = BORDER_DRAG;
mIsFirstClick = false;
}
else if(mRect.contains((int)event.getX(), (int)event.getY())) {
mode = BOX_DRAG;
if (mIsFirstClick){
mRect = new Rect(CROP_BOX_START_X, CROP_BOX_START_Y,
CROP_BOX_END_X, CROP_BOX_END_Y);
mIsFirstClick = false;
} else {
mIsFirstClick = true;
}
}
else{
mode = NONE;
mIsFirstClick = true;
}
break;
case MotionEvent.ACTION_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
mIsFirstClick = false;
if(mode == BOX_DRAG) {
layoutParams.leftMargin = (int)event.getX() - (int)start.x + view.getLeft();
layoutParams.topMargin = (int)event.getY() - (int)start.y + view.getTop();
}
else if(mode == BORDER_DRAG) {
if (event.getX() > view.getLeft() && event.getY() > view.getTop()){
mRect.right = (int) event.getX();
mRect.bottom = (int) event.getY();
}
}
while(layoutParams.topMargin + 5 < mImageView.getTop())
layoutParams.topMargin++;
while(layoutParams.leftMargin + mRect.right > mImageView.getRight())
layoutParams.leftMargin--;
while(layoutParams.topMargin + mRect.bottom > mImageView.getBottom())
layoutParams.topMargin--;
while(layoutParams.leftMargin + 5 < mImageView.getLeft())
layoutParams.leftMargin++;
break;
}
view.setLayoutParams(layoutParams);
invalidate();
return true;
}
}
}
Some points I would like to mention.
- Merged Attr and Crop in CropBox
- No need of creating a rectangle from lines. You can use Rect.
- Never initialize an array/object in Draw method
- Added a feature: if double touched on rectangle it returns to original position
- There might be some hitches about the restricting the rect in imageview. I am sure you can fix those... :)
Other than this there is another interesting way using scaling of canvas Image in Canvas with touch events
Use that class instead of Cropbox and try it.
Hope it helps..
Related Topics
Add a Search Filter on Recyclerview with Cards
List All Camera Images in Android
Disable Sound from Notificationchannel
Cannot Resolve Method Setlatesteventinfo
Android: How to Share Image with Text on Facebook via Intent
Edittext Seterror() with Icon But Without Popup Message
Hide Ics Back Home Task Switcher Buttons
Android Layout Folders: Layout, Layout-Port, Layout-Land
How to Get Data from Service to Activity
Does Android Support Jdk 6 or 7
How to Get the Google Username on Android
Android Vector Drawable App:Srccompat Not Showing Images
Firebase Cloud Messaging Notification Not Received by Device
Get Focused View from Viewpager
How to Get the Current Location in Google Maps Android API V2
Collapsingtoolbarlayout Subtitle
The Following Classes Could Not Be Found: Android.Support.V7.Internal.App.Windowdecoractionbar