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.
Related
I'm trying to figure out how to draw a Square within my onDraw method in Android.
The square must be positioned in the exact center of the canvas
(Not the screen)
The padding/spacing on the left and right hand side of the square should be
equal
The padding/spacing on the top and bottom of the square should be equal
The size of the square should be relatively large, about 90% of the
canvas's width
Here's what I have so far.
//this.rect is an instance of Rect() which later gets called in the canvas.drawRect() method
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = this.getMeasuredWidth();
int height = this.getMeasuredHeight();
int padding = (width / 10);
this.size = width - padding;
this.rect.set(padding,padding,size,size);
}
The code above draws the square but I'm not sure how to get it to center in the canvas. I am also open to using another technique that does not involve using a Rect.
What properties do I need to set to this Rect() in order for the canvas.drawRect(rect,paint) to draw the rectangle directly in the center of the canvas?
Edit:
Terribly drawn example of what I want to achieve
Assuming width is the width of the canvas, I guess you're missing substracting the padding twice.-
this.size = width - padding * 2;
EDIT
Since we're talking about a rectangle here, you'll need to do some more changes to your code, and calculate different top and left padding .-
int width = this.getMeasuredWidth();
int height = this.getMeasuredHeight();
int paddingLeft = (width / 10);
size = width - paddingLeft * 2;
int paddingTop = (height - size) / 2;
this.rect.set(paddingLeft,paddingTop,size,size);
EDIT 2
Maybe a clearer approach would start calculating the size of your square.-
size = width * 0.9f;
int paddingLeft = (width - size) / 2;
int paddingTop = (height - size) / 2;
this.rect.set(paddingLeft,paddingTop,size,size);
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 have a small program that show a circle, and when you click on that circle it re-appears somewhere else on the screen.
This works good in 90% of the cases, but sometimes the circle is buggy. It can be that it appears outside the view, appears as an oval instead of circle, or is placed halfway outside the view.
Can anyone point me in the right direction, what am I doing wrong?
Screens:
Code example:
public class Activity1 : Activity
{
int margin = 20;
Button ball;
TextView debug;
RelativeLayout mRel;
RelativeLayout.LayoutParams ballParams;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Create a debug label
debug = new TextView(this);
// Create a new ball
ball = new Button(this);
ball.SetBackgroundDrawable(Resources.GetDrawable(Resource.Drawable.round_button));
ball.Click += (o, e) => {
RandomizePosition();
};
// Set ball parameters
ballParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WrapContent,
RelativeLayout.LayoutParams.WrapContent);
// Create relative layout
mRel = new RelativeLayout(this);
mRel.SetBackgroundColor(Color.AntiqueWhite);
mRel.AddView(ball);
mRel.AddView(debug);
SetContentView(mRel);
// Randmize the ball position
RandomizePosition ();
}
void RandomizePosition ()
{
// Get height and width
Display display = WindowManager.DefaultDisplay;
int width = display.Width;
int height = display.Height;
int relativeBallSize = ((((width * 2) + (height * 2)) / 100) * 3);
// Set random parameters
Random r = new Random();
int maxWidth = (width - relativeBallSize);
int maxHeight = (height - relativeBallSize);
int x = r.Next(margin, (maxWidth < margin) ? margin : maxWidth);
int y = r.Next(margin, (maxHeight < margin) ? margin : maxHeight);
// Place the ball randomly
ballParams.SetMargins(x, y, x, y);
ball.LayoutParameters = ballParams;
ball.SetHeight(relativeBallSize);
ball.SetWidth(relativeBallSize);
debug.SetText(string.Format("X = {0}, Y = {1}, Width = {2}, Height = {3}, Ball Width = {4}, Ball Height = {5}, Ball size = {6}", x, y, width, height, ball.Width, ball.Height, relativeBallSize), TextView.BufferType.Normal);
}
}
Assuming that your r.Next method is working correctly I think the problem is here:
ballParams.SetMargins(x, y, x, y);
You're setting the margins for the left,top,right,bottom respectively and I don't think you mean to be setting the right and bottom margins. You might want to try using the setX and setY methods instead.
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 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