LinearLayout layout_weight and weight_sum for sizing? - android

I have 5 LinearLayouts. For ease of writing and understanding, I'll refer to them as Views A - E.
View A is the parent of Views B, C, D, and E.
I have set the layout_weight to 8.0f for View A and the weights to 1.0f, 3.0f, 1.0f, 3.0f for Views B - E respectively.
I understand that this is for managing the empty space in the parent view, but I was hoping to size my View and subviews to own a percentage of the screen, rather than compete just for the free space.
However, this is all done programmatically, so should I set the height of the LinearLayouts to a coefficient of the getHeight() method/accessor of it's parent (View A)? If so, then how can I get the height of the parent if the view hasn't yet been added to it's parent, which would set its height with MATCH_PARENT?
Since the getHeight() method will return 0 for onCreate, onStart, and the first onResume, I had to keep looking for an answer. I found ViewTreeObserver and OnGlobalLayoutListener, which will let me know when the Layouts have been set. However, I would really appreciate having the height before any drawing occurs. Is that not possible?

If you don't need to do this programatically, you can do it in xml. In order to have the child LinearLayouts take up a percentage of the Parent LinearLayout (LinearLayout A) then you need to set the parent's weightSum=(Total layout_weight of child LinearLayouts) and then set the child LinearLayouts width/height property to "0dip" and set their layout_weight to the desired percentage.
Sample code for a vertical orientation would be:
<LinearLayout
android:id="#+id/A"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:weightSum="8.0"
android:orientation="vertical">
<LinearLayout
android:id="#+id/B"
android:layout_height="0dip"
android:layout_width="fill_parent"
android:layout_weight="1.0"/>
<LinearLayout
android:id="#+id/C"
android:layout_height="0dip"
android:layout_width="fill_parent"
android:layout_weight="3.0"
</LinearLayout>

Unfortunately, it's not possible to get the size of a view before it's drawn (not unless there is a hack out there that I'm unaware of). The view doesn't hold any information about its size until it until it is ready to be laid out. It would be nice if, in a future release, they designed the system hold the view dimensions prior to the view being drawn.
You could create a custom class, extend the View class, and then override a method that is called by the system and return the dimensions to an object reference in your activity. However, this is likely to be more of a headache then a help and doesn't really give you any real advantage.
I'd recommend using ViewTreeObserver and the OnGlobalLayoutListener.

Here's how to do this with Fragments as mentioned above.
First save a reference to context in the onAttach method (you will probably want to setup a callback to the activity anyway:
Context context;
#Override
public void onAttach(Activity activity){
super.onAttach(activity);
context = activity;
}
Then in onViewCreate, you take the measurements and use them:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Display display = ((WindowManager)
context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
float pixHeight = metrics.heightPixels;
float pixWidth = metrics.widthPixels;
float density = context.getResources().getDisplayMetrics().density;
float dpHeight = pixHeight / density;
float dpWidth = pixWidth / density;
// make various layouts using your measurements, send the
// measure of each parent to each custom drawn child, or
// send the bounds of each child as determined by the size of each parent
return someView;
}

Related

How can I get wrap_content or match_parent layout width and height in android?

I want to get my layout or view (not screen size) width an height in some android devices, so how can i do this? For example:
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingRight="8dp"
android:paddingLeft="8dp"
android:id="#+id/layout_scrol_left"/>
I have this layout and i want to get layout width and height size (in dip) in runtime, how can i do that?
First of all you need to take your layout.
LinearLayout ll_left = (LinearLayout)findViewById(R.id.layout_scrol_left);
Now if ll_left is not yet drawn both ll_left.getHeight() and ll_left.getWidth() will return 0.
So you have to get them after your view is drawn:
ll_left.post(new Runnable(){
public void run(){
int height = ll_left.getHeight();
int weight = ll_left.getWidth();
}
});
i think you can use something like
LinearLayout myView =(LinearLayout)findViewById(R.id.layout_scrol_left);
and you can use the methodes below after the UI thread has been sized and laid
myview.getHeight();
myview.getWidth();
hope this helps .
if you problems using the method above take a look at getWidth() and getHeight() of View returns 0

Change android window size with setScaleX() setScaleY()

I am adding a view using the WindowManager.
In the WindowManager.LayoutParameters I am setting both width and height to WRAP_CONTENT as the content should dictate how large the view should be.
However, I am allowing the user to adjust the overall size of the layout.
Then, when I create the layout, I simply apply the saved scale value and viola it shows the newly resized view.
The problem is that despite scaling the actual view I add as shown below:
myView.setScaleX(scaleFactor);
myView.setScaleY(scaleFactor);
mWindowManager.addView(myView, params);
There still seems to be some sort of "container" around my shrunk view.
So I figured instead of using setScaleX() and setScaleY(), to instead add a runnable to my view to run after its done drawing and calling getWidth() and getHeight() then using my scaleFactor value to compute the new height and width. This works, but now things are getting cut off because setScaleX() and setScaleY() actually shrinks everything inside the view. The entire Paint object is shrunk including text, space between text and everything.
Does anyone know how I can make the absolute dimensions the same size as the shrunk view?
EDIT: So after messing around with this. What I believe I need to do is figure out how to resize the parent layout and allowing its children to be clipped instead of resized.
For instance:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/parent_layout"
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">
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</RelativeLayout>
In the layout above, I scale the TextView object using setScaleX() and setScaleY() this causes the textView to be scaled but doesn't change its actual dimensions.
What I need to do then, is get the dimensions of the RelativeLayout and multiply it by the float scale value. This will get the new dimensions. Then I need to update those dimensions WITHOUT changing the dimensions of the TextView oject.
So I figured this out and posting answer and code.
When using setScaleX() and setScaleY() it would scale it according to the view's center. This would cause the view's top and left positions to shrink or grow without changing the actual dimensions of the view.
So to compensate, we must take the child view (textView1) and move it to the left and up how ever much we shrunk it by.
Here is the code to do this:
RelativeLayout parent; //THis is the parent view that you added with a WindowManager object.
TextView textView1; //This is the child view of our parent. For this example, I am using a TextView.
float factor; //This is the amount we scaled our view by.
parent.post(new Runnable() { //When you post a runnable to a view, it runs after the view is drawn and measured.
#Override
public void run() {
int newWidth = Math.round(parent.getWidth() * factor), newHeight = Math.round(parent.getHeight() * factor); //Calculate what the new height and width will be for the parent view to get resized to.
WindowManager.LayoutParams pp = (WindowManager.LayoutParams)parent.getLayoutParams(); //Get an instance of the parent LayoutParams so we can set the new height and width measurements.
RelativeLayout.LayoutParams ch = (RelativeLayout.LayoutParams)chatHead.getLayoutParams(); //Get the child's layout parameters so we can adjust the margins as needed.
int diffX = parent.getWidth() - newWidth, diffY = parent.getHeight() - newHeight; //Calculate the difference in sizes from the newly scaled size to the old size.
diffX = -Math.round(diffX / 2); //Calculate the amount of space needed to move the child view inside its parent. The negative sign is needed here depending how you calculate the differences.
diffY = -Math.round(diffY / 2); //Same as above, but for the height.
ch.setMargins(diffX, diffY, Integer.MIN_VALUE, Integer.MIN_VALUE); //Set the new margins for the child view. This will move it around so it is anchored at the top left of the parents view.
rootChatHead.updateViewLayout(chatHead, ch); //Apply the new parameters.
pp.width = newWidth; //Set the parent's new width.
pp.height = newHeight; //Set the parent's new height.
windowManager.updateViewLayout(rootChatHead, pp); //Update the parent view.
}
Now our view scaled down and the container's size has been adjusted to take up any extra space caused by the scaling of our child view.
This method ALSO works if you scale the child view higher. It will readjust the parent's size to accommodate the new size of the child.

`RelativeLayout` set in a `FrameLayout` is catching touch events from the entire `FrameLayout`

EDIT: I just realized that if I want to add buttons or anything below, I want to be able to drag on top of them. So maybe my solution would be changing the Z-Index of the cards so they can appear outside the FrameLayout?
I have a FrameLayout as such:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/stack"
/>
I put a RelativeLayout in it that contains some items, but has the formatting of so:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:font="http://schemas.android.com/apk/res-auto"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#drawable/card_background"
android:layout_centerInParent="true"
>
The only reason I have my FrameLayout set to match_parent is because the user can drag the relative layout anywhere on the screen, and if the width and height of the FrameLayout are wrap_content, the RelativeLayout gets cropped when dragged. However, when leaving it as match_parent touching anywhere in the FrameLayout trigger's the LinearLayout's touch event.
For context, I'm trying to display a stack of cards, each the same size, and let the user drag the top one off the screen one by one.
I ended up adding directly to the RelativeLayout. Unfortunately, when creating a class by hand some of the gravity settings were lost, so I did something like this:
int size = ScreenMetricUtil.convertPixelToDp(getActivity().getBaseContext(), 300);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(size, size);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
card.setLayoutParams(params);
container.addView(card); // add to LinearLayout
And my helper method (source):
public static int convertPixelToDp(Context context, int pixels) {
float density = context.getResources().getDisplayMetrics().density;
return (int) (pixels * density + 0.5f);
}

Can I prevent a HorizontalScrollView from expanding?

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.

How is the height of a GridView determined, and can it be changed?

I have a pair of vertically-arranged GridViews that are populated with buttons by an Adapter. I can get the GridViews to fill the width of the parent view, but I can't get the second GridView to fill the height of the parent view.
It appears that the height of the GridViews is fixed and can't be changed. If there's more than enough space for the GridViews, they don't fill the parent. If there's not enough space, they go into scroll mode -- and scroll mode can't be turned off.
I'm not sure, but GridView seems to be using a fixed height for the child button views, I can't figure out how the button (row) height is determined, or how I can change it or force GridView to fill the parent height.
Here's the XML for the GridViews:
<LinearLayout
android:id="#+id/keypads"
android:background="#color/keypad_background_color"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:isScrollContainer="false"
android:orientation="vertical" >
<GridView
android:id="#+id/grdMeasButtons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="4"
android:scrollbars="none"
android:isScrollContainer="false"
android:stretchMode="columnWidth" />
<GridView
android:id="#+id/grdNumButtons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="3"
android:isScrollContainer="false"
android:scrollbars="none"
android:stretchMode="columnWidth" />
</LinearLayout>
Note that my original version sets a height of 0px for each GridView and weight ratio of 1:2 to set the height relationship of the GridViews. That doesn't fill the parent either, and if the weight of one of the views is too small, it goes into scroll mode. The "isScrollContainer" attribute doesn't turn that off.
Here's the getView() method from the Adapter:
// create a new ButtonView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
Button btn;
if ((convertView == null) || !initializing) { // if it's not recycled, initialize some
// attributes
btn = new Button(mContext);
KeypadButton keypadButton = mButtons[position];
switch(keypadButton.mCategory)
{
case NUMBER:
btn.setBackgroundResource(R.drawable.keypadnumber);
break;
case DECIMAL:
btn.setBackgroundResource(R.drawable.keypadnumber);
break;
case CLEAR:
btn.setBackgroundResource(R.drawable.keypadnumber);
break;
case WEIGHT:
case VOLUME:
switch (unitState[position]) {
case SELECTED:
btn.setEnabled(true);
btn.setBackgroundResource(R.drawable.keypad_measure_selected);
btn.setTextColor(mContext.getResources().getColor(R.color.units_text_enabled));
break;
case ENABLED:
btn.setEnabled(true);
btn.setBackgroundResource(R.drawable.keypad_measure_not_selected);
btn.setTextColor(mContext.getResources().getColor(R.color.units_text_enabled));
break;
case DISABLED:
btn.setEnabled(false);
btn.setBackgroundResource(R.drawable.keypad_measure_not_selected);
btn.setTextColor(mContext.getResources().getColor(R.color.units_text_disabled));
break;
default:
break;
}
break;
case DUMMY:
break;
default:
btn.setBackgroundResource(R.drawable.keypadnumber);
break;
}
// Set OnClickListener of the button to mOnButtonClick
if(keypadButton != KeypadButton.DUMMY)
btn.setOnClickListener(mOnButtonClick);
else
btn.setClickable(false);
// Set CalculatorButton enumeration as tag of the button so that we
// will use this information from our main view to identify what to do
btn.setTag(keypadButton);
} else {
btn = (Button) convertView;
}
btn.setText(mButtons[position].getText());
return btn;
}
(Don't worry about the the unitState array. It solves yet another problem.)
Even when the view is being recycled, the height (and width) of the button is zero, and setting it has no effect.
Can the height or fill attributes of the GridViews or buttons be set in Layout parameters before the GridViews are populated?
// Just replace your second GridView With This One
<GridView
android:id="#+id/grdNumButtons"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:isScrollContainer="false"
android:numColumns="3"
android:scrollbars="none"
android:stretchMode="columnWidth" />
If GridView not filled to parent, then you have to calculate, device Height programatically in getView() method. After calculating height of the device, set GridView's layoutParams to match parent or fill parent, it will solve your problem.
The solution proposed by pratik was on the right track, but setting the size of the GridViews in the adapter is no different from using "fill_parent" or weighted heights on the GridViews in XML. Sure, the GridViews are set to the correct height, but the buttons are always the same size. So if a GridView isn't big enough for its buttons, the GridView scrolls. Useless for a keypad.
As I suspected, the problem is the button size. I still don't know how Android determines the button height, but it appears to fixed -- i.e., it's not dependent on the button width, which is properly adjusted to fit the GridView width. I'm guessing this is a bug in GridView. It also seems to be a bug that you can't turn off scrolling in GridView.
Anyhow, the solution is for getView() in each GridView's adapter to set the button heights to the value that makes them all fit exactly in the height allocated to the GridView. I actually tried that before posting here, but used the construct:
btn.setHeight(number);
instead of setting the height in the button's layout parameters. That was due to my not realizing that the button height doesn't get set until after the layout has completed. Also, I hadn't considered getting the usable screen height so I could compute a device-independent height. Sorry for being new to this stuff.
I spent a lot of time trying to use the height of the LinearLayout that contains the GridViews, which would reduce the number of constants I need to adjust for different device sizes. But that got very messy due to timing of layout completion and the oddities of how tabbed fragments are implemented (and yes, I tried setting up an OnGlobalLayoutListener). Bottom line, there are some timing issues with that approach. The upshot was that the keypad area would disappear for a second or two and get redrawn when the fragment was tabbed into view after being destroyed and re-created. It can probably be done, but I didn't have time to figure it out.
Anyhow, here are the relevant additions to the code. First I added the following method that I found here to the MainActivity:
// Method to get the usable screen height and width, which will then be available through their getters and setters
private void setUsableScreenDimensions() {
DisplayMetrics dm = getResources().getDisplayMetrics();
float screen_w = dm.widthPixels;
float screen_h = dm.heightPixels;
int resId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
screen_h -= getResources().getDimensionPixelSize(resId);
}
TypedValue typedValue = new TypedValue();
if(getTheme().resolveAttribute(android.R.attr.actionBarSize, typedValue, true)){
screen_h -= getResources().getDimensionPixelSize(typedValue.resourceId);
}
screenWidth = (double) screen_w;
screenHeight = (double) screen_h;
}
Then I added this code to the adapter constructor:
double keypadParentHeight = (MainActivity.getScreenHeight() * Constants.KEYPAD_PARENT_HEIGHT_PERCENTAGE);
double rowHeight = keypadParentHeight / Constants.NUMBER_OF_KEYPAD_ROWS;
buttonHeight = (int) (rowHeight * Constants.BUTTON_HEIGHT_FACTOR);
Finally, the button height in getView is set thusly:
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, buttonHeight);
LayoutParams params = params;
The actual code is slightly more complicated because there are two sets of keys (two GridViews) with different weights (1:2), so the button sizes are different. In addition, the same keypads are drawn by the same adapter on three different fragments with different size parent LinearLayouts. But that all boiled down to a few extra constants and some conditional statements in the adapter.

Categories

Resources