I have a custom view that is sort of a vertical progress bar. It's just a custom view, drawing onto the canvas to fill from bottom to top with the percent complete. In my xml, I have text above, and text below the custom control (there are two of these, one on the left, one on the right, here's the right):
<RelativeLayout android:layout_width="150dp"
android:layout_height="fill_parent" android:layout_alignParentRight="true">
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="#+id/bar_right_text" android:text="Right"
android:layout_alignParentTop="true" android:gravity="center_horizontal" />
<com.myApp.VBarView android:id="#+id/right_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/bar_right_text"/>
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="#+id/bar_right_time_text" android:text="Time Remaining" android:layout_below="#id/right_bar" android:gravity="center_horizontal" />
</RelativeLayout>
In my custom view, I check the height in every way I can think of, but I always get a height that goes all the way to the bottom of the screen
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// h here is 489
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
int myHt = getHeight();
int myMeasHt = getMeasuredHeight();
Rect canvasRct = canvas.getClipBounds();
// all of these (myHt, myMeasHt, and canvasRct.getHeight()) are all 489
}
So far, I have tried reversing the views in the layout, so the top text is first (top aligned), the bottom text is second (bottom aligned), and the custom control is last (layout_above the bottom text), with both match_parent and wrap_content, to no avail. I tried changing to a linear layout, still with no better result.
In the case shown in code, the top text is displayed, but the bottom text is gone. When I changed the order so the custom view was layout_above the bottom text, it just moved the drawing area up so the bottom text was visible, but covered the top text. Note that when I say the bottom text is gone, I mean gone, not just covered up. My final test was to hardcode the layout_height to "350dp" in the layout.
In all cases, I always get a height of 489 on this particular device, even with the height hard-coded. I can not figure out what I need to do to get the middle part of the screen as something I can draw on and leave the rest alone. Are custom views assumed to be the last thing in a layout? The only way I can get this to work is to change the order so the custom view is last in the layout so that the bottom of the custom area is on top of the bottom text, the bottom text is still displayed, and then manually add about 150 to the top (i.e. don't write into zero on the canvas provided to the onDraw()) so it doesn't cover the top text. Fortunately, the graphic I'm drawing in is semi-transparent, so I can see whether the text is covered or just gone.
Does anyone have more info on how this is supposed to be working? Did I manage to make the explanation of what I did more confusing than the actual problem?
Thanks,
gb
Someone suggested offline that instead of just placing the custom field "above" or "below" one of the other fields, do both. Keep it as the last item in the XML, and set android:layout_above and android:layout_below. I did that, and now my heights come in correctly.
Related
I am trying to make a View as a divider on my application to separate two elements (two Buttons, two TextViews or whatever) and I would like to have a padding on the left and on the right of that View (to move the background some space on the left and on the right).
It allows you to set a padding but the View still continues occupying the full width screen. Here is the code I have:
<View
android:layout_width="wrap_content"
android:layout_height="0.5dp"
android:background="#FFFFFF"
android:paddingLeft="10dp"
android:paddingRight="10dp"/>
How can I set a space on the left and on the right of that View so the divider will be smaller than screen?
Thanks in advance!
use margins instead of padding
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
Finally I got it. As on Android documentation it says: Even though a view can define a padding, it does not provide any support for margins, I am not able to use marginLeft or marginRight properties.
If you try to set directly android:marginLeft="10dp" you will get the following error:
No resource identifier found for attribute 'marginLeft' in package 'android'
Nevertheless, you can use android:layout_marginLeft="10dp" and android:layout_marginRight="10dp" to get the desired result.
The final View xml will be like this:
<View
android:layout_width="wrap_content"
android:layout_height="0.5dp"
android:background="#FFFFFF"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"/>
Padding takes care of the inner space. Therefore you would use padding to increase the space between the outline of the view and the elements inside it, whether they are buttons, textviews etc.
Margin takes care of the outer space. You would use this to increase the space between the outline of the view and the element which contains it. Hence what you need. To implement this in a view use the following:
private void setMargins (View view, int left, int top, int right, int bottom) {
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
p.setMargins(left, top, right, bottom);
view.requestLayout();
}
}
Hope this helps :)
Say I have a image icon, I'd like to set the distance between the center of the icon and the left side of it's parent view to 20dp, the size of the icon may changes. How could I implement this?
It's something like the CenterX in iOS constraint, when you create a constraint, you can choose if from Leading, Trailing or CenterX. But I can't find anything similar on Android.
Updated:
See the image below, the icons inside the red rect, I want to make the distance from the centerX to the left side of the window to 20dp. marginLeft count from left side of the icon, the not what I want. I do have solutions like fix all the icons to same size, or wrap icon inside another ViewGroup and make the ViewGroup size fixed, just curious if there's some way like the centerX constraint on iOS.
This looks like it's part of a list, so I will assume you have a single icon in your layout.
FrameLayout that is 40dp wide with icon centered
<FrameLayout
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_marginRight="-8dp">
<ImageView
android:layout_gravity="center_horizontal"/>
</FrameLayout>
If the 40dp wrapper is too wide, put a negative right margin to reduce it (shown).
I have a custom view which is nested inside of a ScrollView. The idea is that the custom view can be longer than the height of the screen, so when you swipe through the ScrollView you can see all of its contents.
In order to do this I adjust the clip of the custom view's canvas like this:
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.clipRect(mCanvasArea, Region.Op.REPLACE);
}
Where mCanvasArea is a pre-generated RectF which represents the entire area the canvas can draw. I logged it's value # 10308.
However when the app runs, the custom view is unscrollable, it acts as if the visible content represents all of the content in the canvas, but I can see more peeking up from the bottom of the screen.
Here is my XML declaration for the view:
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/activity_toolbar"
android:fillViewport="true">
<com.myapp.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
(I had to add fillViewport in order to get the content to show up in the first place)
Is there anything else that needs to be done in order yo make the view scroll?
You will need to specify the height of the custom view, not the canvas. The canvas passed to onDraw reflects the width and height of the view.
Setting the view to wrap_content doesn't mean it will grow whenever you draw outside of its bounds. It will only "wrap" the child views, not the canvas.
To set your view's height programmatically:
getLayoutParams().height = 500;
invalidate();
I'm not sure what you are going for with this exactly so I can't get too much more specific about where you should set the height, but I'd recommend not changing your view's dimensions in onDraw because when you invalidate it will cause another draw and you'll need some funky logic to handle this recursion.
Instead you could determine the height you need when the view is constructed, and then override onMeasure with something like this:
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, calculatedHeight);
}
This would force your custom view into the calculated height.
I'm generating my Views programatically, so there's no XML. But as it is easier to show my problem, I will give the structure in XML-like notation
<RelativeLayout
android:id="#+id/mainLayout">
<LinearLayout
android:id="#+id/left"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:maxWidth="300dp"
android:maxLines="3"
android:text="Some long Text" />
</LinearLayout>
<ImageView
android:id="#+id/right"
android:layout_toRightOf="#id/left">
</RelativeLayout>
For this, the LinearLayout is positioned in the top left corner of the RelativeLayout. The ImageView is positioned right to it.
The TextView is very important. You see it's width is variable but to a maximum of 300dp. With this and maxLines this TextView has a maximum area by the size of 300dp x 3 lines. Most likely, the TextView won't be this large and by the wrap_content settings, TextView and LinearLayout will adapt to the text.
(I have to say, I overrid the TextView's onMeasure method, so the TextView is only as wide as the widest line.)
At the moment, everything is ok. The TextView and therefor LinearLayout enclose the text tightly, sitting at (0, 0). Next to it on the right sits the ImageView. Now comes the hard part I'm stuck at.
I want to align the LinearLayout within the before mentioned area, the TextView could maximally span. First thought was, I surround the LinearLayout with another RelativeLayout, by the maximum size and position the smaller LinearLayout within. But as the ImageView must have to be a direct child of mainLayout, I can't align it to the LinearLayout anymore.
My current approach is to override the LinearLayout's onLayout method:
#Override
protected void onLayout(boolean changed, int left, int top,
int right, int bottom){
super.onLayout(changed, this.getLeft(), this.getTop(),
this.getRight(), this.getBottom());
int translation;
int widthNeed = this.mLinLay.getMeasuredWidth();
switch(this.mLinLayAlignment)
{
case Left:
break;
case Center:
translation = (this.mLabWidth - widthNeed)/2;
this.setLeft(this.getLeft() + translation);
this.setRight(this.getRight() + translation);
// also tried: this.setTranslationX(translation);
break;
case Right:
translation = (this.mLabWidth - widthNeed);
this.setLeft(this.getLeft() + translation);
this.setRight(this.getRight() + translation);
// also tried: this.setTranslationX(translation);
break;
}
}
I only want to align the LinearLayout horizontally by now, so I only distinguish between 3 cases. The translation works pretty well, but the ImageView stays on its place, where it aligns to the initial position of the LinearLayout. I first tried LinearLayout.setTranslationX() but with the same effect. So I changed it, because the documentation on setTranslationX implicated, translation happens after layouting.
I think I just have to call the right method, so mainLayout is forced to layout the ImageView again, but I can't find it.
Also, are these the proper way and place to move the LinearLayout? As I need the final size of the TextView, it had to be measured, so this is the place I have to do it, I think.
EDIT
I have tried a little more. I got the LayoutParams from the LinearLayout, increased the left and decreased the right Margin by the calculated translation and set the LayoutParams again. Well, the ImageView still stays in place, so no progress here. But after switching out and back in the app, I realised, the Margin changed every time layouting. Well, should have seen this coming.
But why isn't the same thing happening, using setLeft and setRight?
Maybe if you draw a picture of what you want it would help.
I would stay out of the onLayout method alltogether.
If you want to move View or Layout around, then use layout params
RelativeLayout.LayoutParams params = linearLayout.getLayoutParams();
params.setMargins(left, top, right, bottom);
linearLayout.setLayoutParams(params);
Disclaimer: I rewrote most of this question, including its title, because I partially figured it out.
I'd like to maximize the size of the button on all screen sizes, because it looks silly when it is to small. It should look similar to this:
(Sadly, I cannot include a picture, because my reputation is too low.)
But if I turn the orientation of the device, for example, the button matches it's parents width, becoming ugly proportioned.
(Sadly, I cannot include a picture, because my reputation is too low.)
I now have figured out how to get the dimensions of its parent (the LinearLayout) and how to set the button's size. I used the following code:
window is the ID of the LinearLayout containing (only) the button.
this code is located in the onCreate()-method of the MainActivity.
// Adapt button's size to smaller dimension:
final View window = findViewById(R.id.window);
window.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int width = window.getMeasuredWidth();
int height = window.getMeasuredHeight();
int smallerSize;
if (width < height) {
smallerSize = width;
} else {
smallerSize = height;
}
View button = findViewById(R.id.fartButton);
button.setLayoutParams(new LinearLayout.LayoutParams(smallerSize, smallerSize));
window.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
The problem with this approach is, that it doesn't seem to account for padding. The button get's cut off a little bit on the smaller side (in portrait mode its width, in landscape mode its height).
Interestingly, the image inside the button fits the window perfectly. If for example the height gets cut off a bit, the image still is visible in its full height (only some "extra" parts of the button get cut off, like a little border and shadow).
Is there a way to get the maximal size of the button, which would be the size of the window, but without action bar and minus padding, to prevent any part of the button to get cut off?
Your example above "should look similar to this:" does not seem to have loaded, illustration would help...
But you can manage screen proportions pretty well using android:layout_weight
I'm not sure I'm envisioning your exact needs, but you might try something like this:
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="X"
android:text=" "
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="button"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="X"
android:text=" "
/>
</LinearLayout>
where different values for X would control the horizontal aspect ratio for your button in a view.
I just figured it out. Was much easier than I thought. Thanks to everyone who answered, though. It helped me a lot on the way!
The padding that is applied to the window can easily be accesed through the getPadding...() methods. I just needed to adjust the part where the width and height get saved:
int width = window.getMeasuredWidth() - window.getPaddingLeft() - window.getPaddingRight();
int height = window.getMeasuredHeight() - window.getPaddingTop() - window.getPaddingBottom();
I thought, that even by manually excluding the padding, the highlight when pressing the button would be cut off, because it is a bit bigger than the button itself. But this is not the case and it works perfectly. The button now gets displayed in its whole glory. ;)
You can overload your onMeasure method to always return a square.
Create a class that extends to Button and include this
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(size, size);
}
Not sure how this will work if you give exact dimensions but it should work if you set width, height to match parent