I've encountered some interesting behavior setting the weight of two views in a linear layout.
I have a horizontal Linear layout with weightSum = 1. It has two children views, leftView & rightView. I am making a horizontal bar graph by setting the weight of the views.
If I set the left view with a weight of 0.05 and the right view with a weight of 0.95 programmatically. The behavior presented in the UI will show the left view with taking up 95% of the width and the right view 5% of the width.
If I reverse it setting the leftView with 0.95 & the rightView with 0.05 the UI will present the leftView taking up 5% of the space (the way I wish it to be)
Does anyone have any insight to why this occurs?
float percentage= (float)(x * 100) / y; // (2000 * 100) / 40000 = 5
LinearLayout.LayoutParams leftViewParams =
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
percentage/100f); // (5 / 100) = .05
leftView.setLayoutParams(leftViewParams);
LinearLayout.LayoutParams rightViewParams=
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
(100 - percentage)/100f); //(100 - 5) / 100 = .95
rightView.setLayoutParams(rightViewParams);
However if I set the leftview to have a weight of .95 and the right view to have a weight of .05 the left view takes up 95% of the space while the right view only takes up 5%.
Can anyone explain what is happening here?
BONUS XML - if I set leftView layout_weight=0.05 and rightView layout_weight=0.95 it displays correctly.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="#dimen/core_dimen_16dp"
android:orientation="horizontal">
<View
android:id="#+id/leftView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#000" />
<View
android:id="#+id/rightView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#AAA" />
</LinearLayout>
I tested your code and everything is correct. There is no problem. I think that probably you make a little mistake.
I paid attention to your code and I noticed that you should change remainingWeight to rightViewParams in the line: rightView.setLayoutParams(remainingWeight);
I hope this will help.
Related
I'm designing an android app with a layout that has one main vertical LinearLayout containing two child LinearLayouts, but I'm having difficulty getting the child LinearLayouts to size how I need them. These views each contain multiple other views, including an ExpandableListView (i.e. a total of two ELVs, one in each of the two child LinearLayouts). The size of the ELVs are dynamic. I need the two child LinearLayouts to fill the available space if needed, and the second child LinearLayout needs to be directly below the first one. If one of them needs less than half of the space, then the other can grow as much as available space allows (i.e. if one needs 25%, the other can expand up to 75%, and the ELV will scroll if it needs more). If both needs more than 50%, they each should get half and their ELVs should scroll. How do I do this? If views other than LinearLayouts are needed (such as ConstraintLayout or CoordinatorLayout), I am willing to use them.
I have tried using android:layout_weight, but these seem to carve out permanent space which creates empty space between the views or unnecessarily limits the size of a view. I've tried RelativeLayouts with no success. Flexbox seems geared toward horizontal spacing. I've reviewed other questions/answers, such as Height half fill_parent xml, Divide screen in two half and place imageview on half-half both, 2 LinearLayouts, each half the parent size, How to spread the Views in android, etc.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="#+id/content_main"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:showIn="#layout/activity_main"
android:orientation="vertical">
<LinearLayout
android:id="#+id/ll_Main_MyPursuitsOuter_Wrapper"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:minHeight="#dimen/preD_scenarios_min_height">
<LinearLayout
android:id="#+id/ll_Main_MyPursuitsOuter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/customborder_outer"
android:orientation="vertical">
…
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="#+id/ll_Main_ScenariosOuter_Wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="#dimen/preD_scenarios_min_height"
>
<LinearLayout
android:id="#+id/ll_Main_ScenariosOuter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="#drawable/customborder_outer"
android:orientation="vertical">
…
</LinearLayout>
</LinearLayout>
</LinearLayout>
Using this code I get an output like figure 1. There is a lot of blank space between the views when View 1 isn't large enough to fill the space, and I'm concerned that if View 2 grows large enough it will cause View 1 to be too small.
Figure 1:
If both Views can't fill the entire screen, it should look like Figure 2.
Figure 2:
If one View needs less than half of the screen, and the other needs more than the remaining space, it should look like Figure 3, with the applicable View taking the remaining space and its ELV scrolling as needed.
Figure 3:
Finally, if both views need more than half of the screen, then it should look like Figure 4, with each View geting half and both ELVs scrolling as needed.
Figure 4:
UPDATE:
Based upon #Sultan Mahmud recommendation, I developed the following method. I tried using ViewTreeObserver but it ended up invoking multiple resizings since I have two different Views being dynamically updated via AsyncTasks. So instead I used post on one of my Views (likely the one that will take the longest to populate; may need to do a post for both if ). I call this method from onResponse for each of the AsyncTasks, from onResume (to account for real estate changes from screen rotations), and whenever the user manually adds or deletes an item from one of the ExpandableListViews.
New method:
private void measureViews(){
// final String METHOD = "In measureViews,";
//
// Log.d(LOG_CLASS, String.format(Locale.US, "%s Started.", METHOD));
mLL_MyPursuits_Outer_Wrapper.post(new Runnable() {
#Override
public void run() {
//Get parent LinearLayout dimensions
parentPadding = (int) (getResources().getDimension(R.dimen.activity_vertical_margin) * 2.0);
parentHeight = mLL_Main_Wrapper.getHeight() - (int) (getResources().getDimension(R.dimen.activity_vertical_margin) * 2.0);
parentWidth = mLL_Main_Wrapper.getWidth() - (int) (getResources().getDimension(R.dimen.activity_horizontal_margin) * 2.0);
//Get MyPursuits LinearLayout desired height
mLL_MyPursuits_Outer_Wrapper.measure(
View.MeasureSpec.makeMeasureSpec(parentWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(parentHeight, View.MeasureSpec.AT_MOST));
myPursuitsDesiredHeight = mLL_MyPursuits_Outer_Wrapper.getMeasuredHeight();
//Get PreD_Scenarios LinearLayout desired height
mLL_Scenarios_Outer_Wrapper.measure(
View.MeasureSpec.makeMeasureSpec(parentWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(parentHeight, View.MeasureSpec.AT_MOST));
scenariosDesiredHeight = mLL_Scenarios_Outer_Wrapper.getMeasuredHeight();
//Determine if either or both are bigger than half
boolean isMyPursuitsBiggerThanHalf = (myPursuitsDesiredHeight > (parentHeight / 2));
boolean isPreD_ScenariosBiggerThanHalf = (scenariosDesiredHeight > (parentHeight / 2));
//Set LayoutParams
LinearLayout.LayoutParams paramsMyPursuits;
LinearLayout.LayoutParams paramsScenarios;
LinearLayout.LayoutParams paramsZeroWeight = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
0
);
LinearLayout.LayoutParams paramsOneWeight = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1
);
if ((myPursuitsDesiredHeight + scenariosDesiredHeight) <= parentHeight){
paramsMyPursuits = paramsZeroWeight;
paramsScenarios = paramsZeroWeight;
} else if (!isMyPursuitsBiggerThanHalf && isPreD_ScenariosBiggerThanHalf){
paramsMyPursuits = paramsZeroWeight;
paramsScenarios = paramsOneWeight;
} else if (isMyPursuitsBiggerThanHalf && !isPreD_ScenariosBiggerThanHalf){
paramsMyPursuits = paramsOneWeight;
paramsScenarios = paramsZeroWeight;
} else {
paramsMyPursuits = paramsOneWeight;
paramsScenarios = paramsOneWeight;
}
mLL_MyPursuits_Outer_Wrapper.setLayoutParams(paramsMyPursuits);
mLL_Scenarios_Outer_Wrapper.setLayoutParams(paramsScenarios);
}
});
}
Set layout weight programmatically after taking the decision based on your condition. Like if the first condition match for half screen occupied then divide layout weight on both views.
LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(
0,
LayoutParams.MATCH_PARENT,
1.0f
);
first_layout.setLayoutParams(param);
second_layout..setLayoutParams(param);
I have TableLayout. As each cell in Table Layout I have LinearLayout with few elements inside a nice border.
LinearLayout definition:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:layout_marginBottom="2dp"
android:orientation="vertical"
android:background="?border_empty"
android:layout_weight="1"
>
Unfortunately layout_marginLeft and layout_marginBottom seems to be ignored. :-( I've even tried to set very high values ex. 15 dp. How to set more space between cells (I would prefer to set it on left and on bottom if possible)?
I saw your question yesterday but I wanted to wait until today to answer, because now I'm facing exactly the same issue.
I've tried using this (being fila my TableRow):
final TableLayout.LayoutParams trParams = new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.WRAP_CONTENT);
trParams.setMargins(10, 5, 10, 5);
fila.setLayoutParams(trParams);
But as you comment, both left and right margins seem to be ignored. I finally managed it through the TextView itself, this way (being tv my TextView):
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tv.getLayoutParams();
lp.rightMargin = 10;
lp.leftMargin = 10;
This seems to work perfectly.
One of my DialogFragment's layouts uses a HorizontalScrollView to horizontally scroll its child LinearLayout. Is there a way to lock its width into place so that it doesn't expand the width of the DialogFragment once the LinearLayout has been populated with items?
Here's what the DialogFragment looks like when it first loads:
And here's what happens when the LinearLayout under the Kanji header is populated with items:
Everything scrolls fine once the items are populated, but I want to prevent the DialogFragment from expanding. Setting a fixed width on the root of the HorizontalScrollView prevented it from expanding, but I can't think of a good way to do so while taking into account the highly variable size of Android screens.
Here's some code for the area under Kanji:
<HorizontalScrollView
android:layout_height="48dp"
android:layout_width="match_parent">
<LinearLayout
android:id="#+id/grid_kanji"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
</HorizontalScrollView>
I got the desired effect by setting the width of the HorizontalScrollView to whatever it was when the dialog first opened:
// Fix the size of the HorizontalScrollView so it doesn't expand
HorizontalScrollView hsv = (HorizontalScrollView) getDialog().findViewById(R.id.grid_kanji_root);
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, (float) hsv.getHeight(), getResources().getDisplayMetrics());
int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, (float) hsv.getWidth(), getResources().getDisplayMetrics());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(width, height);
hsv.setLayoutParams(lp);
getWidth() and getHeight() return measurements in pixels, which is why my TypedValue's use COMPLEX_UNIT_PX instead of COMPLEX_UNIT_DP.
Note that you can't do this in onCreateDialog() or onResume() since the width returned will be 0 (zero). I ended up running this code in the function that loads cursor results into the child LinearLayout with a global boolean that keeps track of whether or not I've already set the width - if it's FALSE (as when the dialog first loads), then I set the width and set the boolean to TRUE.
I also discovered that the LayoutParams you apply to a control must match the type of the parent element. Since my HorizontalScrollView is located inside of a LinearLayout, I had to apply LinearLayout.LayoutParams to it instead of HorizontalScrollView.LayoutParams. You'll get a ClassCast Exception otherwise.
I'm using a SlidingPaneLayout in my activity:
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/myslidingpanelayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- menu left -->
<LinearLayout
android:id="#+id/menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#8d305f"
android:orientation="vertical" >
...
</LineareLayout>
<!-- main page right-->
<LinearLayout
android:id="#+id/right_main"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#fff"
android:orientation="vertical" >
...
</LineareLayout>
</android.support.v4.widget.SlidingPaneLayout>
I want the menu to cover 3/4 of the page I want it to work on all the phones so I can't put for example
android:layout_width="300dp"
I want to calculate the screen width and set it to the left pane
Thank for your help
Thanks for you all I found this answer and it works with me:
int width;
int height;
if (android.os.Build.VERSION.SDK_INT >= 13){
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
width = size.x;
height = size.y;
}else {
Display display = getWindowManager().getDefaultDisplay();
width = display.getWidth(); // deprecated
height = display.getHeight(); // deprecated
}
if(width>0&&height>0){
LinearLayout layout = (LinearLayout)findViewById(R.id.menu);
// Gets the layout params that will allow you to resize the layout
LayoutParams params = layout.getLayoutParams();
// Changes the height and width to the specified *pixels*
params.height = height;
params.width = width*3/4;
}
Just looking up the doc for sliding pane, looks like it functions like a linear layout, and can use the
layout_weight
parameter to set a percentage based width since the parent viewgroup is match_parent
In the case of 3/4 = 75% you can
android:layout_weight="0.75"
From the android docs http://developer.android.com/reference/android/support/v4/widget/SlidingPaneLayout.html:
Like LinearLayout, SlidingPaneLayout supports the use of the layout parameter layout_weight on child views to determine how to divide leftover space after measurement is complete. It is only relevant for width. When views do not overlap weight behaves as it does in a LinearLayout.
When views do overlap, weight on a slideable pane indicates that the pane should be sized to fill all available space in the closed state. Weight on a pane that becomes covered indicates that the pane should be sized to fill all available space except a small minimum strip that the user may use to grab the slideable view and pull it back over into a closed state.
And from the LinearLayout docs http://developer.android.com/guide/topics/ui/layout/linear.html#Weight
Note: You will end up setting the layout_width parameter to 0dp since the view group will actually use the weight to lay the children out
Apart from Selecsosi's answer, which is correct, there is also this view I wrote to always display the second item as a pane (ignoring the default show-side-by-side-if-the-fit behaviour). It can, as the name shows, wrap around the sliding view.
You can implement the behaviour you're after by either using a lot of #dimen resources and switching them based on swXXXdp-(port|land) or just setting the sliding view's width at runtime (something I'm reasonably certain you can do with the default layout as well).
With the below layout (a portion of a larger layout), the container layout's visibility is set to "gone" until a button is clicked. On the button click, if the container_ll is not visible, it's set to visible, and a custom view is added to the reminderViews_ll container.
The views are being added, and the container_ll view is visible on button click. What follows is the width and height of various views after the button is clicked.
container_ll width 420, height 96.
lineDivider_view width 420, height 2 (as expected)
reminder_img width 36, height 36 (as expected, hdpi phone)
reminderViews_ll width 0, height 96 (argh)
<LinearLayout
android:id="#+id/container_ll"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
>
<View style="#style/lineDivider_view" />
<ImageView
android:id="#+id/reminder_img"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
/>
<!-- Stick the actual Reminder TVs + Del buttons in here dynamically -->
<LinearLayout
android:id="#+id/reminderViews_ll"
android:orientation="vertical"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
/>
</LinearLayout>
I'm at a bit of a loss as to where to go from here. I was thinking invalidate layout, to force it to draw again after making the view visible, but that's never worked for me (seemingly), and if the reminderViews_ll can get a height of 96, then it can't be an issue with when it's calculating the dimensions of the LinearLayout.
I hope you have enjoyed reading this question as much as I have writing it. Any pointers, as always, appreciated.
The numbers you show look correct to me.
The container is width 420. The other views that are set to fill_parent or wrap_content take up all of the space (actually more). The Linear layout then goes to carve up the remaining space to any weighted views. Since there is no space to allocate, you get zero.
container_ll width 420
lineDivider_view - 420
reminder_img - 36
= -36
so this makes sense
reminderViews_ll width 0
There simply is no room to give it.
Why are you using a horizontal line divider in your horizontal view?
Ah, very confused: layout_width="fill_parent" and layout_weight="1.0" doesn't work?
I mean, layout_width="0dp" is guaranteed to be width 0, regardless of what you put into it, so that one will never do what you want. If you are using fill_parent and its still not working I'd question if you are adding your custom View into the right LinearLayout, because that really should work correctly.
I see that you've set android:layout_width for reminderViews_ll to 0 dp, you have to change this parameter, possibly dynamically if you want to. I don't really understand why you set it to 0 dp and then ask why it has zero width.