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.
Related
What am I trying to do?
Activity starts with a ImageView taking the upper 9/10 of the screen, and a ListView the remaining bottom 1/10:
As more items added to the ListView, the ratio changes, ListView getting 1/10 more of the screen for each item, and ImageView shrinks respectively, up to 50:50 ratio (adding more items after that keeps the ratio fixed) :
What I already know:
Best practice of statically dividing the screen, is using LinearLayout attribute android:weightSum (and View's android:layout_weight). It should be possible to it dynamically as well using AbsListView.LayoutParams, combined with registering a callback somewhere in ArrayAdapter. However, it fills like an abuse of a static feature (weight). Moreover, I'd prefer to do the transition between to ratios continuously (using Property Animation).
Questions:
Should I prefer using getWindowManager() .getDefaultDisplay().getHieght(); once, and calculating ratio dynamically as desired?
As for ratio updating callback, are there any option other than ArrayAdapter.registerDataSetObserver() ?
If you want to keep ratio always 10% and 90%, use weight 1 and weight 9.
Otherwise better to write xml code to check
I do something like what you want by:
1) Set up layout:
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/ll_forImage"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="#+id/ll_forList"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
</LinearLayout>
2) write some Utility methods for layouting.
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density + 0.5f);
}
private static void setLayoutSize(View view, int width, int height) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = width;
params.height = height;
view.setLayoutParams(params);
}
3) code:
int screenWidth = dpToPx(getResources().getConfiguration().screenWidthDp);
int screenHeight = dpToPx(getResources().getConfiguration().screenHeightDp);
int ratio = ....;//
setLayoutSize(ll_forImage, screenWidth , screenHeight/ratio );
setLayoutSize(ll_forList, screenWidth , screenHeight - screenHeight/ratio );
I am using Html.fromHtml() to get content and display it on my activity. To do this I am using an ImageGetter. I was having a problem that if the phone could not connect to the internet the app crashed as the pictures could not load. Instead I wanted to have a placeholder image saved in my ldpi/mdpi/...etc folders that would be inserted whenever a picture could not be loaded.
My ImageGetter uses URLImageParser which has the following onPostExecute() method:
#Override
protected void onPostExecute(Drawable result) {
//check to see if an image was found (if not could
//be due to no internet)
if(result ==null){
//the drawable wasn't found so use the image not found
//png
Drawable imageNotFound = a.getResources().getDrawable(R.drawable.image_not_found);
result = imageNotFound;
}
intrinsicHeight = result.getIntrinsicHeight();
intrinsicWidth = result.getIntrinsicWidth();
DisplayMetrics dm = new DisplayMetrics();
((WindowManager) c.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels -50;
int height = width * intrinsicHeight / intrinsicWidth;
result.setBounds(0, 0, 0 + width, 0
+ height);
urlDrawable.setBounds(0, 0, 0+width, 0+height);
// change the reference of the current drawable to the result
// from the HTTP call
urlDrawable.drawable = result;
// redraw the image by invalidating the container
URLImageParser.this.container.invalidate();
// For ICS
URLImageParser.this.container.setHeight((URLImageParser.this.container.getHeight()
+ height));
// Pre ICS
URLImageParser.this.container.setEllipsize(null);
}
I have simply inserted the if(result==null) statement at the top of this method. But now if the pictures can be loaded the app works perfectly. If the images cannot be loaded and the placeholders are used instead I get some odd behavior.
The scrollview never scrolls to the bottom of the screen, and I have no idea why this is. In theory there should be no difference between my imageNotFound drawable (which is a png file) and the files downloaded off the internet. The scrollview will only move slightly.
I have no idea what is causing this. When searching online most people seem to be having problems with RelativeLayouts. I couldn't find anyone having trouble with drawables or TableLayouts.
My xml for the layout is as follows:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:paddingBottom = "0dip"
android:orientation = "vertical" >
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="0,1"
android:shrinkColumns="0,1"
android:id = "#+id/SharedTableLayout"
android:paddingBottom = "0dip" >
<TableRow
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:paddingBottom = "0dip">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id = "#+id/SharedTableContent"
android:layout_span="2"
android:gravity = "left"
android:paddingLeft = "10dip"
android:paddingRight = "10dip"
android:paddingTop = "20dip"
android:paddingBottom = "0dip"/>
</TableRow>
</TableLayout>
</ScrollView>
I would really appreciate any suggestions on this, I've been stuck on it for weeks.
Thanks for your time
The TextView with id SharedTableContent displays a string that was converted from html using Html.fromHtml() so the images may be surrounded by text which means I cannot hard code a solution into xml as there is no way of telling how many images there will be to download in advance, thats all done programmatically.
Try changing the layout width of the scrollview to match_parent.
android:layout_width="match_parent"
android:layout_height="match_parent"
Or try this
<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="#+id/ScrollView02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<HorizontalScrollView android:id="#+id/HorizontalScrollView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView android:id="#+id/ImageView01"
android:src="#drawable/pic"
android:isScrollContainer="true"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:adjustViewBounds="true">
</ImageView>
</HorizontalScrollView>
</ScrollView>
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.
I have a RelativeLayout which holds an ImageView and an ImageButton. The ImageView serves as an container for a background image. Now I'm trying to set the button at a fixed position so that it always appears on the same position on the background image.
Here is the layout file I'm using:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/relativeLayout1" android:layout_height="match_parent"
android:layout_width="match_parent" android:gravity="center_horizontal">
<ImageView android:src="#drawable/bg_1" android:id="#+id/imgView"
android:adjustViewBounds="true" android:layout_height="match_parent"
android:layout_width="match_parent" />
<ImageButton android:layout_width="120dp"
android:background="#drawable/button_1" android:layout_height="30dp"
android:id="#+id/imgButton" android:layout_marginLeft="100dp"
android:layout_marginTop="170dp" />
</RelativeLayout>
As you can see I've tried positioning the button with it's left-/top-margin using dp as unit, but this doesn't work. Since the background image is beeing scaled down/up, the position would have to be dynamic in some kind of way.
I understand that absolute positioning, with pixel-values for x-/y-position, is something that won't work on Android, like it is explained here. I still need to solve this and am not sure how.
Would I have to calculate the values for left-/top-margin (not sure how that would be) and then set them with something like this?
final float density = getResources().getDisplayMetrics().density;
int width = (int)((float)120 * density);
int height = (int)((float)120 * density);
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(width, height);
rlp.leftMargin = newMargin;
rlp.topMargin = newTopMargin;
ImageButton imgButton = (ImageButton) findViewById(R.id.imgButton);
imgButton.setLayoutParams(rlp);
Hope I didn't forget something ...
//EDIT:
I was thinking, the reason for the issue might be, that the scaled image has different "borders", depending on the screen size.
With an image at a 1:1.6 ratio on a HVGA screen I have black bars on the left and right, whereas on a WVGA screen the bars are on the left. Considering I'm using the default scaling. I will look into it and post again, if necessaray...
Why scaling happens? Because of different dpi on different devices? Do you have different drawabled for different dpi settings? If it isn't just dpi issue and you want to scale that background image freely then you can't do the job using standard layouts. You should implement a custom one.
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
}