The best way to ask this question is to provide an example, so here it is:
<ScrollView>
<LinearLayout
android:id="#+id/main"
android:orientation="vertical"
>
<TextView
android:id="#+id/some_text"
/>
<LinearLayout
android:id="#+id/stretch_me"
/>
<Button
android:id="#+id/i_can_toggle"
/>
<TextView
android:id="#+id/toggle_me"
android:visibility="gone"
/>
<Button
android:id="#+id/finished"
/>
</LinearLayout>
</ScrollView>
This seems fairly simple, but hold on ...
1) I would like the stretch_me layout to take all the remaining space on the screen (and I need its size so I can dynamically populate it from code)
2) I can't change main to RelativeLayout because I would like to toggle toggle_me between gone and visible using the i_can_toggle but need to keep strech_me size the same as before
3) Before changing toggle_me to visible there must be no scroll and finished button must be positioned at the bottom of the screen
Now I have tried many things and the most promising approach was this one with some coding (I was thinking about setting the stretch_me size from code), but I was not able to get the size of the view from my onCreate() method (screen_height - view_height = remaining space).
Any ideas?
Thanks!
Set stretch_me to have these attributes to make it take up the space
android:layout_height="fill_parent"
android:layout_weight="1"
I would like the stretch_me layout to
take all the remaining space on the
screen
Set the height and weight parameters to the following
android:layout_height="fill_parent"
android:layout_weight="1"
I need its size
You can use onmeasure() to measure the view size. http://developer.android.com/reference/android/view/View.html#onMeasure(int, int)
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpec = MeasureSpec.getMode(widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
//Similarly for height
}
Related
I have to do some flattening on my layout, below piece of it after. Percent TextView and RadioButton are in same place on the left and are switching using animations. Second TextView shows some text fulfilling width. Problem is that RadioButton is shorter than percent TextView, but I'm using alignLeft and alignRight for same width, and even with gravity set to center radio drawable is aligned left. I've found this topic, but is a bit old, before Material, and currently this class is more complicated. Any advice how to center this radio in own width (or width of percent textview) without! adding another ViewGroup? (precent and radio might be in another RelativeLayout and set centerInParent, as I had before tries for flattening)
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/option_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:singleLine="true"
android:gravity="center"
android:ems="3"
android:visibility="invisible"/>
<RadioButton
android:id="#+id/option_radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignLeft="#id/option_percent"
android:layout_alignRight="#id/option_percent"
android:gravity="center"/>
<TextView
android:id="#+id/option_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_toRightOf="#id/option_percent"/>
</RelativeLayout>
OK, I have some workaround for my case. I'm not accepting this post, because its not a proper answer. Im still counting on another way, more elegant ;)
from above xml I've set fixed width for percent and removed ems, practically no difference. also removed aligning left and right to percent in radio attributes, so it have own size in both dimensions (wrap_content). next step is in code:
if(radioMarginLeft<0) {
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
percentTextView.getLayoutParams().width, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
radioButton.measure(widthMeasureSpec, heightMeasureSpec);
radioMarginLeft = (percentTextView.getLayoutParams().width -
radioButton.getMeasuredWidth())/2;
}
((RelativeLayout.LayoutParams) radioButton.getLayoutParams()).leftMargin=radioMarginLeft;
measuring radio, percent now has fixed width, so no need to measure. radioMarginLeft is calculated only once and kept outside method, in my case it may be even static.
additional: be careful with setting padding for radio, because of Add margin between a RadioButton and its label in Android? (check most upvoted answer)
I have a complex xml layout with part of it being:
...
<FrameLayout
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:layout_weight="1" >
<ImageView
android:id="#+id/flexible_imageview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#drawable/gradient"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingTop="8dp" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
...
The height of the FrameLayout #+id/parent must be determined at runtime because it is displayed above many other views, and they must be shown in the layout. This way the FrameLayout fills the remaining space perfectly (using height="0dp" and weight="1" properties).
The ImageView #+id/flexible_imageview receives an image from the network, and it always shows with the correct aspect ratio. This part is already ok as well. This View is the largest and should determine the size of the FrameLayout #+id/parent.
The problem is that when the image is drawn, the width of the FrameLayout #+id/parent is not adjusted to wrap and be the ImageView #+id/flexible_imageview as it should be.
Any help is appreciated.
-- update --
I've updated the layout to clarify some of the missing parts, and to add the reasoning behind all of it.
What I want is to have an Image (ImageView #+id/flexible_imageview) with unknown dimensions to have, on top of it, a gradient and some text on top of the gradient. I can't set the FrameLayout #+id/parent dimensions to both wrap_content because there is more Views after this Image that must be shown. If there's not enough space in the layout, the Image (ImageView #+id/flexible_imageview) is reduced until it all fits in the layout, but it should maintain its aspect ratio, and the gradient/texts on top of it.
The #drawable/gradient is just:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:angle="270"
android:endColor="#aa000000"
android:startColor="#00000000" />
</shape>
-- update 2 --
I added two images below to demonstrate what's happening, and what should happen:
Bug:
Correct:
If would help if you explained more about what you are trying to accomplish in your layout (not the layout itself but what should the user see on the screen and what are the other elements in the layout).
A FrameLayout with multiple children is usually a "code smell". Usually, FrameLayouts should have only one child element. So this makes me wonder whether there is something wrong with your design.
-- Edit --
If I understand correctly, you are trying the framelayout to wrap the content of the image but at the same time match the space left from the other layout views before/after the frame layout.
What is the parent view/layout of the frame layout?
I see a couple of problems with this design or your explanation:
You have framelayout width set to match parent, but you want to wrap the content of the image.
You want the imageView to be reduced but you are not taking into account the text views in the linear layout. You have them set to wrap content. So when the fame layout is small, you will not see all the textviews. (Unless you are resizing them as well somehow).
Sorry if this isn't helpful enough but it's difficult to understand what you are trying to accomplish with this layout. A sample use-case would help in providing you a better recommendation.
When a dimension (width / height) is MeasureSpec.EXACTLY adjustViewBounds will not effect it.
In your case, having android:width="match_parent" ensures that the image view is the size of the parent, regardless of adjustViewBounds.
It works to begin with because the height is wrap_content - the height is adjusted when the image is scaled to fill the width.
When you override the height to fit everything on the screen (this may not be a great idea to begin with), the width is still matching the parent and doesn't get adjusted. However, because the scale type is ScaleType.FIT_CENTER the image is scaled and positioned so that the entirety of it fits in the bounds of the ImageView (and centred.. hence the name).
If you turn on the debug option for drawing layout bounds, or look at your app using hierarchyviewer, you'll see that the image view is still matching the width of its parent.
There are a couple of ways you could do what you want.
Since you're already manually calculating the height, setting the width shouldn't be that hard.
Drawable drawable = imageView.getDrawable();
if (drawable != null && drawable.getIntrinsicWidth() > 0 && drawable.getIntrinsicHeight() > 0) {
int height = // however you calculate it
int width = height / (getDrawable().getIntrinsicHeight() / getDrawable().getIntrinsicWidth());
}
You might also get away with
<ImageView
android:id="#+id/flexible_imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:minWidth="9999dp" />
Even if this works, you probably wouldn't be able to use it in a horizontal LinearLayout using weights any more (for example, if you wanted a landscape variant of the layout), or a number of other scenarios.
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
I have a GridView based calendar. I have the following XML layout with the selector set to null thus android:listSelector="#null" in accordance with advise I have got from this site. Now I am getting a few pixels wide strip to the right of the GridView. Why? I have tried everything I can but to avail. Here is my XML layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<GridView
android:id="#+id/calendar"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:horizontalSpacing="-1px"
android:numColumns="7"
android:stretchMode="columnWidth"
android:gravity="center"
android:verticalSpacing="-1px"
android:listSelector="#null" >
</GridView>
</LinearLayout>
What I get is this picture:
This space is due to imperfect calculation for each row of your grid.
For example your device width is 320 px and you have 7 rows, try any calculation that meets 320 px. If the width of each cell is 45.71428571428571 px, only then it can be reduced.
Other option
apply
android:gravity="center"
property in you grid so that spaces will equally divided from left to right
in my case I had horizontal spacing as 1dp
android:horizontalSpacing="1dp"
so to solve this I just put the right padding as -1dp
android:paddingRight="-1dp"
even though I expected to get a space on the left side due to this , but it worked properly
Try using
android:layout_margin="0dp"
android:padding="0dp"
Also you might have that space allocated for the scroll bar.
And why do you have your vertical and horizontal spacing with negative values?
I had the same problem though in my case I didn't have control over the GridView instance so I couldn't read the column width. Nevertheless I could subclass its container.
This isn't very orthodox but you can override the onMeasure method and add a few pixels. Something like this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int RIGHT_MARGIN_OVERSIZE_DIP = 6;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = this.getMeasuredWidth()
int overSize = (int)((getResources().getDisplayMetrics().density*RIGHT_MARGIN_OVERSIZE_DIP) + 0.5f);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width + overSize, MeasureSpec.getMode(widthMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
grid.setColumnWidth(grid.getColumnWidth()+1);
This will get the current ColumnWidth and add 1 pixel to it.
Remember to use this after onCreateView,since the grid needs to be initialized before that.
Also,your grid view,should use match_parent (height and width).The image/layout in the column should be also match_parent.
tl;dr: How do I get the DIP height of the FrameLayout?
Full: I am trying to design a layout that is consistent across the general resolutions available. I've gone through the developer best practices on Google and that helped my understanding a bit. I've converted icons to 9-patch and am using DIP heights which improved things a lot.
However where I'm stuck is trying to play three rows of buttons that will take up the remainder of the screen, regardless of the resolution. I'll need to make something different for small screens and tablets, but I am only currently worried about the normal screens on most phones.
I have a layout that has a TabWidget as the #android:id/tabs role and a FrameLayout for the #android:id/tabcontent
One of the Tab Activities is simply 3 rows of buttons which I want to fill the entire FrameLayout which I suspect I must calculate the height of the button based on the height of the FrameLayout.
My question then is, how do I get the DIP height of the FrameLayout?
I've tried a GlobalLayoutListener and that just returns 0. I've tried pulling the LayoutParams and that just returned -1 for FILL_PARENT. I need the actual DIP height of the FrameLayout to properly set the height of the area available.
How can I do that, or am I looking at it incorrectly?
Any help is appreciated.
I figured out a way to get the result I wanted, just not the exact way I was trying and never managed to get the height of the tabcontent directly but indirectly.
I found two methods to do this and I'll post them below.
First I did Method 2 but then discovered I preferred Method 1 and decided to go with that since it's more extendable.
Method 1
This way I found from How to size an Android view based on its parent's dimensions and is the most customizable and readable method. In a nut shell, you need to extend FrameLayout and override the onMeasure method.
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="0dip"
android:layout_margin="0dip">
<LinearLayout
android:id="#+id/topLayout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="0dip"
android:layout_margin="0dip">
<TabWidget
android:id="#android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:padding="0dip"
android:layout_margin="0dip" />
<view
class="com.tmcaz.patch.TabContentFrameLayout"
android:id="#android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="0dip"
android:layout_margin="0dip" />
</LinearLayout>
</TabHost>
The major difference is using a custom class for this where you can handle the sizing from the event itself, similar to Method 2 but no need to do any calculations to get the content height.
I did this to give myself access to the event and handle all of the sizing in the content. Someone reading this may very well need to override something else and deal with the onMeasure event totally differently.
The code is below
public class TabContentFrameLayout extends FrameLayout {
// add constructors, etc
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
// Should turn these in to member variables and reference it throughout.
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth, parentHeight);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Method 2
I assigned an id to the LinearLayout that held the TabWidget and FrameLayout. Here is my main.xml
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="0dip"
android:layout_margin="0dip">
<LinearLayout
android:id="#+id/topLayout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="0dip"
android:layout_margin="0dip">
<TabWidget
android:id="#android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="50dip"
android:padding="0dip"
android:layout_margin="0dip" />
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="0dip"
android:layout_margin="0dip" />
</LinearLayout>
</TabHost>
I assigned a DIP height to the tabs and then grabbed the LayoutParams for the LinearLayout which I simply subtract the height of the tabs from the result. I've added code here for basic illustrative purposes only and can do it a bit more efficiently, it's not my production code :)
One thing to note is that you can't appear to directly pull the height of the layout during the onCreate event where it's most useful. You need to create a GlobalLayoutListener to capture the change in the layout and get the size.
public class MyTabActivity extends TabActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout ll = (LinearLayout)findViewById(R.id.topLayout);
ll.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
DisplayLayoutDimensions();
}
}
);
// Code here to add activities to the tabs, etc
}
.
public void DisplayLayoutDimensions()
{
// Put code to calculate the heights you need instead of printing
// out the values obviously.
Resources r = getResources();
LinearLayout topLayout = (LinearLayout)findViewById(R.id.topLayout);
LayoutParams tabWidgetParams = getTabHost().getTabWidget().getLayoutParams();
float scale = r.getDisplayMetrics().density;
float pxTabContent = topLayout.getHeight() - tabWidgetParams.height;
/*** The commented out DIP calculations didn't work for me in any AVD so
manually I calculated them with the scale which worked fine ***/
//float dipTopLayout = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, topLayout.getHeight(), r.getDisplayMetrics());
//float dipTabWidget = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabWidgetParams.height, r.getDisplayMetrics());
//float dipTabContent = dipTopLayout - dipTabWidget;
Log.d("MyTabActivity", "LinearLayout (topLayout) Height: " + topLayout.getHeight() + "px / " + (topLayout.getHeight() / scale) + "dp");
Log.d("MyTabActivity", "TabWidget Height: " + tabWidgetParams.height + "px / " + (tabWidgetParams.height / scale) + "dp");
Log.d("MyTabActivity", "Calculated (tabcontent) Height: " + pxTabContent + "px / " + (pxTabContent / scale) + "dp");
}
Hope this helps someone at some point. If someone has a better way to do this, please speak up.