I have a non-fullscreen activity (the system notification bar is visible). In order to create my view hierarchy I need to know the size of the the piece of screen that my activity occupates (that is the size of the display detracted by the size of the system notification bar). How can I determine that within the onCreate method?
This is not known in onCreate(). What you should do is participate correctly in the view hierarchy layout process. You do NOT do your layout in onCreate(), you do it in the view hierarchy with the layout managers. If you have some special layout that you can't implement with the standard layout managers, it is pretty easy to write your own -- just implement a ViewGroup subclasses that does the appropriate things in onMeasure() and onLayout().
This is the only correct way to do this because if the available display size changes, your onCreate() will not run again but the view hierarchy will go through its layout process to determine the correct new place to position its views. There are an arbitrary number of reasons why the screen size could change on you like this -- for example on the Xoom tablet when it is plugged in to an HDMI output it makes the system bar larger so that when it mirrors its display to a 720p screen the bottom of applications do not get chopped off.
For example, here's a layout manager that implements a simple version of FrameLayout:
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
int childRight = getPaddingLeft()
+ child.getMeasuredWidth() - getPaddingRight();
int childBottom = getPaddingTop()
+ child.getMeasuredHeight() - getPaddingBottom();
child.layout(getPaddingLeft(), getPaddingTop(), childRight, childBottom);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
int measuredChildState = 0;
// Find rightmost and bottom-most child
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
measuredChildState = combineMeasuredStates(measuredChildState,
child.getMeasuredState());
}
}
// Account for padding too
maxWidth += getPaddingLeft() + getPaddingRight();
maxHeight += getPaddingTop + mPaddingBottom();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth,
widthMeasureSpec, measuredChildState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
measuredChildState<<MEASURED_HEIGHT_STATE_SHIFT));
}
Note the last line there is the best way to implement measurement starting with API 11, since it allows you to propagate states like "layout does not fit" up which can be used to do things like determine the size that dialogs need to be. You likely don't need to worry about such things, in which case you can simplify it to a form that works on all versions of the platform:
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
resolveSize(maxHeight, heightMeasureSpec));
There is also an API demo for a slightly more complicated layout:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/animation/FixedGridLayout.html
I can't test it now, but I believe
int h = getWindow().getAttributes().height;
int w = getWindow().getAttributes().width;
API docs:
http://developer.android.com/reference/android/app/Activity.html#getWindow%28%29
http://developer.android.com/reference/android/view/Window.html#getAttributes%28%29
http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html
http://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html#height
Related
I am creating a custom view which extends LinearLayout. I'm adding some shapes to the linear layout, currently with a fixed value of space between them. I'm doing so by defining a LayoutParams and setting the margins to create the space between the shapes.
What I want to do is span them at an equal space across the entire screen so they would fill it, but only if the width of the view is set to either match_parent or fill_parent. If it's set to wrap_content then the original fixed value of space should be set.
I've tried doing:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
spaceBetweenShapesPixels = (parentWidth - shapeWidth * numberOfShapes) / numberOfShapess;
}
However, the method seems to be called twice - Once for the width and height of the parent and after that for the View's itself and when it's for the view itself, the space gets a 0 value.
So how can I just make this logic:
if(width is wrap_content)
{
space = 10;
}
else
{
space = (parentWidth - shapeWidth * numberOfShapes) / numberOfShapess;
}
You should use WidthMode and HeightMode
In the onMeasure() method. Below is sample a from one of my projects
#Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int measuredWidth = 0, measuredHeight = 0;
if (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST) {
measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
}
if (heightMode == MeasureSpec.EXACTLY) {
measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
} else if (heightMode == MeasureSpec.AT_MOST) {
double height = MeasureSpec.getSize(heightMeasureSpec) * 0.8;
measuredHeight = (int) height;// + paddingTop + paddingBottom;
}
}
MeasureSpec.EXACTLY - A view should be exactly this many pixels regardless of how big it actually wants to be.
MeasureSpec.AT_MOST - A view can be this size or smaller if it measures out to be smaller.
MeasureSpec.UNSPECIFIED - A view can be whatever size it needs to be in order to show the content it needs to show.
Refer this for more details https://stackoverflow.com/a/16022982/2809326
Can we get the visible width of a child inside a horizontalscrollview ?
i tried
int viewLeft = v.getLeft();
int viewRight = v.getRight();
but it's always the same result
I need to know if the visibility is higher than half the width
"Visible width" sounds like you want a child view's width actually visible to the user (whole width minus the currently invisible part(s)).
In that case it's correct to use getLeft() and getRight(), but like this instead (hsv being your HorizontalScrollView instance):
int viewLeft = v.getLeft();
int viewRight = v.getRight();
int scrollX = hsv.getScrollX();
if (scrollX > viewLeft) {
viewLeft = scrollX;
}
if (scrollX < viewRight) {
viewRight = scrollX + hsv.getWidth();
}
int visibleWidth = viewRight - viewLeft;
I want to create a custom View, such that when it is inflated with wrap_content as one of the dimension parameters and match_parent as the other, it will have a constant aspect ratio, filling whichever dimension is set to match_parent, but providing the layout inflater with the other dimension to be "wrapped". I presume this is possible because, for example, a full screen width TextView would obviously be able to demand that it have space for two, three or any arbitrary number of lines of text (depending on width), but would not necessarily know this until inflation-time.
Ideally what I want to do is override layout methods in the View subclass such that when the view is inflated, I get the layout information, and supply my own dimensions for the "content" to be wrapped (ie my fixed-ratio rectangle).
I will need to create a lot of these custom views and put them in various different types of layout—sometimes using an Adapter—so really I want to have the maximum control over their inflation I can. What's the best technique for doing this?
You can always check for compliance to aspect ratio in onMeasure.
not a full answer I know, but it should lead you there ;)
I've now solved this with the following code. It's worth mentioning in passing that the class I'm overriding is a custom ViewGroup with custom children, all using the inherited onMeasure. The children are created and added at construction-time, and I would assume as a matter of course that this is necessary.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
float width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
float height = MeasureSpec.getSize(heightMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
float nominalHeight = getResources().getInteger(R.integer.nominalheight);
float nominalWidth = getResources().getInteger(R.integer.nominalwidth);
float aspectRatio = nominalWidth / nominalHeight;
if( widthMode == MeasureSpec.UNSPECIFIED ) { //conform width to height
width = height * aspectRatio;
}
else if (heightMode == MeasureSpec.UNSPECIFIED ) { //conform height to width
height = width / aspectRatio;
}
else if( width / height > aspectRatio //too wide
&& ( widthMode == MeasureSpec.AT_MOST )
) {
width -= (width - height * aspectRatio);
}
else if( width / height < aspectRatio //too tall
&& ( heightMode == MeasureSpec.AT_MOST )
) {
height -= (height - width / aspectRatio);
}
int newWidthMeasure = MeasureSpec.makeMeasureSpec((int)width, MeasureSpec.AT_MOST);
int newHeightMeasure = MeasureSpec.makeMeasureSpec((int)height, MeasureSpec.AT_MOST);
measureChildren(newWidthMeasure, newHeightMeasure);
setMeasuredDimension((int)width, (int)height);
}
I'm defining the aspect ratio in terms of a nominal rectangle in resources, but obviously there are plenty of other ways to do this.
With thanks to Josephus Villarey who pointed me at onMeasure(...) in the first place.
I've got a tiled pan-n-zoom component that uses several FrameLayouts to position image tiles (that scale), markers (that move, but don't scale) and controls (which neither move nor scale). It works fine.
Markers are positioned in a FrameLayout with topMargin and leftMargin. So far so good.
When a marker is touched, I need to open a little popup, at the same position as the marker that was touched but offset by the dimensions of the popup. That's to say that if the popup is 100 pixels wide and 50 pixels tall, and the marker touched was at 800 x and 1100 y, the popup should be at 750 x (postion of marker minus half the width of the popup) and 1050 y (position of the marker minus the full height of the popup). The effect is like a tooltip - a little nub points down at the marker, etc.
The popup dimensions are flexible, based on the text to be displayed, and need to be calculated.
Obviously the dimensions aren't available until after layout happens. What's the best way to get these dimensions and react accordingly?
(should mention that a RelativeLayout is not an option)
TYIA.
/EDIT
looks like I can get the dimensions of the children in onMeasure and onLayout, but re-applying a new layout in one of these handlers would create an infinite loop - setting a flag to catch just the first pass did not work. i guess the updated question would be "now that I know where I can get the information, how should I react and position it?"
the answer is to manage positioning by override onLayout of the containing ViewGroup, rather than the View itself.
E.g.,
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
MyCustomLayout.LayoutParams lp = (MyCustomLayout.LayoutParams) child.getLayoutParams();
int w = child.getMeasuredWidth();
int h = child.getMeasuredHeight();
int x = lp.left - (int) (w / 2f);
int y = lp.top - h;
child.layout(x, y, x + w, y + h);
}
}
}
(note that the above example specifically is untested, but i've tested the premise and it works)
I have recently delved into creating custom ViewGroups and have encountered a problem I can't figure out.
I have 2 ViewGroups - ViewManager and Article
ViewManager simply lays out an Article below the previous Article (ie like a vertical LinearLayout)
Article arranges several TextViews and an ImageView as you can see in the image. Creating a single Article and adding it to ViewManager works fine and everything shows up but when I add a second Article none of the content of the Article is visible.
So why are none of the TextViews or the ImageView showing up (note the blue bar is drawn with canvas.drawRect() in onDraw()). I have also printed out (via logcat) the the bound values of the child views(ie getLeft()/Top()/Right()/Bottom() in drawChild() and they all seem to look fine
FIRST TextView
FIRST left 5 right 225 top 26 bottom 147
FIRST TextView
FIRST left 5 right 320 top 147 bottom 198
FIRST TextView
FIRST left 5 right 180 top 208 bottom 222
FIRST ImageView
FIRST left 225 right 315 top 34 bottom 129
SECOND TextView
SECOND left 10 right 53 top 238 bottom 257
SECOND TextView
SECOND left 5 right 325 top 257 bottom 349
SECOND TextView
SECOND left 5 right 320 top 349 bottom 400
SECOND TextView
SECOND left 5 right 180 top 410 bottom 424
So does anyone have an idea of what I've done wrong?
the Article onMeasure method
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
specWidth = widthSpecSize;
int totalHeight=0;
int width = 0;
if(mImage!=null&&mImage.getParent()!=null){
measureChild(mImage,MeasureSpec.makeMeasureSpec(100, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(100, MeasureSpec.AT_MOST));
width=widthSpecSize-mImage.getWidth();
imageWidth = mImage.getMeasuredWidth();
}
for(int i = 0;i<this.getChildCount();i++){
final View child = this.getChildAt(i);
//get the width of the available view minus the image width
if(imageWidth > 0)
width =widthSpecSize- imageWidth;
else
width = widthSpecSize;
//measure only the textviews
if(!(child instanceof ImageView)){
measureChild(child, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), heightMeasureSpec);
//calculate total height of the views, used to set the dimension
totalHeight+=child.getMeasuredHeight();
}
}
//if the title height is greater than the image then the snippet
//can stretch to full width
if(mTitle.getMeasuredHeight()>mImage.getMeasuredHeight())
measureChild(mSnippet, MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.AT_MOST), heightMeasureSpec);
//measure source to make it full width
measureChild(mSource,MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.AT_MOST), heightMeasureSpec);
setMeasuredDimension(widthSpecSize, totalHeight);
}
and the onLayout method
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int newLeft = left+outerMargin;
int newTop = top+marginTop;
int prevHeight = 0;
if(mEngine!=null){
int height = mEngine.getMeasuredHeight();
int childRight = newLeft+mEngine.getMeasuredWidth();
mEngine.layout(newLeft+marginLeft, newTop+prevHeight+2, childRight+marginLeft, newTop+height+prevHeight+2);
maxRight = Math.max(maxRight, childRight);
prevHeight += height+2;
topBarHeight = mEngine.getMeasuredHeight()+(marginLeft*2);
maxBottom = Math.max(maxBottom, mEngine.getBottom());
}
if(mTitle!=null){
int height = mTitle.getMeasuredHeight();
int childRight = newLeft+mTitle.getMeasuredWidth();
mTitle.layout(newLeft, newTop+prevHeight, childRight, newTop+height+prevHeight);
maxRight = Math.max(maxRight, childRight);
prevHeight += height;
maxBottom = Math.max(maxBottom, mTitle.getBottom());
}
if(mSnippet!=null){
int height = mSnippet.getMeasuredHeight();
int childRight = newLeft+mSnippet.getMeasuredWidth();
mSnippet.layout(newLeft, newTop+prevHeight, right, newTop+height+prevHeight);
maxRight = Math.max(maxRight, childRight);
prevHeight += height;
maxBottom = Math.max(maxBottom, mSnippet.getBottom());
}
if(mSource !=null){
int height = mSource.getMeasuredHeight();
int childRight = newLeft+mSource.getMeasuredWidth();
mSource.layout(newLeft, newTop+prevHeight+(marginTop*2), childRight, newTop+height+prevHeight+(marginTop*2));
maxRight = Math.max(maxRight, childRight);
prevHeight += height;
maxBottom = Math.max(maxBottom, mSource.getBottom());
}
if(mImage!=null){
int height = mImage.getMeasuredHeight();
log("mxW "+maxRight);
int childRight = maxRight+mImage.getMeasuredWidth();
mImage.layout(right-mImage.getMeasuredWidth()+5, newTop+topBarHeight, right-5, height+topBarHeight);
totalWidth = childRight;
maxBottom = Math.max(maxBottom, mImage.getBottom());
}else
totalWidth = maxRight;
}
On a side note is it okay to use LayoutParams.WRAP_CONTENT as a parameter for makeMeasureSpec() like this?
MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.AT_MOST)
In onLayout, the children should be positioned with respect to their parent. You are adding top (and left) to the coordinates of the children. That's not a problem for the first Article because top and left are zero. For the second Article, left is still zero but top is the top of the second article in its ViewManager. Just get rid of newTop and newLeft and use, respectively, marginTop and outerMargin in their place.
Regarding your aside: the first argument to makeMeasureSpec is supposed to be a dimension. By using WRAP_CONTENT, you are setting the maximum dimension to -2, which is probably not what you want to do.