I'm trying to figure out designing for multiple screen sizes/densities/etc. I thought "dp" was my answer but it's not working the way I thought it was going to.
Here's what I'm trying to do:
I have a header and footer section for menu items that remain static. Then I have a ScrollView between the 2. Within the ScrollView is several "blocks" that display various information. I want those blocks to always expand to the full width of the screen. I want the height of those blocks to simply scale appropriately to keep a proper 1:1 -> height:width scale. I also want the TextViews/Images/Buttons/etc to scale and keep their position within the block. If the Blocks height get too big for the screen, that's where the ScrollView comes in. If they fit just fine, that's OK too. I mainly want to scale to width and let the user Scroll if necessary.
Currently if the screen gets wider, the Blocks get really wide but don't scale the height to match them in.
Here's a basic (simplified for readabilities sake) rundown of what my XML layout code looks like. Also note that each block may be a different size. I'm not looking for a "take up 25%" solution. The blocks have fairly complex and completely different layouts within them.
<LinearLayout>
<RelativeLayout>Header stuff</RelativeLayout>
<ScrollView> //height and width = match_parent width lower priority than header/footer
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="???" //Had been using a specified "dp" value but that didn't work like I thought. What goes here?
>
Block#1 with lots of TextViews, ImageViews and Buttons...etc
</RelativeLAyout>
<RelativeLayout>
Block#2
</RelativeLAyout>
<RelativeLayout>
Block#3... etc
</RelativeLAyout>
</ScrollView>
<RelativeLayout>Footer stuff</RelativeLayout>
</LinearLayout>
One way of achieving 'perfect' 1:1 ratio (or other predefined ratios) of a view is to create your custom extension of it and override the onMeasure() method. in your case you might want a SquareRelativeLayout with an onMeasure() method looking something like this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width, width);
}
Edit:
If you want a custom ratio, you can define it somewhere and measure your View like this:
float ratio = 0.5f;
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width, ratio*width);
}
If you know how to create your own xml-attributes you can add the ratio attribute and get it from there, meaning you don't have to create a new View for each different ratio.
Related
I am working on an Android app that is largely driven by code, not xml.
The Adapter for a ListView wants to know the height of the item. But this in turn could depend on the size of UI elements within the item. For example, if the item contains a checkbox, the size of the checkbox could influence the layout of the list item, which in turn could change the height, depending on whether or not a line wrap became necessary.
Question -- is there a way to get the size of an Android UI element without actually creating it? Similar to the way one can use a Paint or TextPaint object to get the size of text before it is drawn.
Without creating it? No, but you could use the MeasureSpec class and measure the View manually after you create it. Why do you need to know the exact size? Can't you just set the LayoutParams to MATCH_PARENT (width) and WRAP_CONTENT (height) for your case?
That said, if you do need to know, you could use the width of your ListView for the width MeasureSpec, and then UNSPECIFIED for the height MeasureSpec:
// Tell the View it should be exactly as wide as the ListView...
int widthSpec = MeasureSpec.makeMeasureSpec(listViewWidth, MeasureSpec.EXACTLY);
// ... and as tall as it wants to be ...
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
// ... measure it with these constraints ...
item.measure(widthSpec, heightSpec);
// ... and retrieve the measured height.
int itemHeight = item.getMeasuredHeight();
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
Basically I'm trying to make an application for tablets.
There going to be a bunch of widgets added and removed by the user inside a table layout. It needs to be dynamic so that the widgets are sized differently based on the width and height of the table layout.
To do this, I'm trying to make a 3x3 grid in a tablelayout. As I'm sure you guessed, each square has 33% width and height of the tablelayout.
This is what I got:
DisplayMetrics metrics;
int totalScreenSizeH;
int totalScreenSizeW;
TableLayout contentTable;
LinearLayout contentLayout;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//Get the UI detail
metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
totalScreenSizeH = metrics.heightPixels;
totalScreenSizeW = metrics.widthPixels;
contentTable=(TableLayout)findViewById(R.id.tableLayout1);
contentLayout=(LinearLayout)findViewById(R.id.contentLayout);
int tableWidth =contentTable.getWidth();
int tableHeight=contentTable.getHeight();
int layoutWidth=contentLayout.getWidth();
int layoutHeight=contentLayout.getHeight();
Log.v("Table Width",""+tableWidth);
Log.v("Table Height",""+tableHeight);
Log.v("Layout Width",""+layoutWidth);
Log.v("Layout Height",""+layoutHeight);
So just to explain this a bit better. The linearLayout contains the tablelayout which has 3 table rows. I figured that I would be able to get the width and height of these but for some reason, both are coming back as 0 in size. One I have the width and height dims, Im going to save that variable and the create the widgets based on those variables/3.
So 2 questions:
1: is this the right way to handle dynamically sized widgets. Or is this the wrong way to handle a problem like this?
2: Any ideas why those 2-4 variables are all coming back as 0?
I would advise against doing pixel-perfect designs on android, due to the vast amount of resolutions and densities you have to deal with.
My suggestion would be to look at android:layout_width/height="wrap_content" and android:layout_weight="INTEGER" that will help you make floating 3x3. You can also achieve this with a GridView
I linked to the source in a comment to the OP, but for the 3x3 Grid, follow some practices Roman has done with his custom DashboardLayout implementation (Apache License FTW). It will correct for horizontal and vertical spacing on various screen dimensions: https://gist.github.com/882650.
If it doesn't fit your use case, I highly suspect you can modify it to do so.
I have a need to create a perfect square TableLayout. The table is a grid of equal sized objects. The problem here is that in landscape layout, the table should take up the maximum amount of space but nothing more. In other words, I need the width of the table equal to the maximum height allowable. An ad appears ~10 seconds or so after the activity starts so that also adds to the complexity.
Here is what I've tried so far to accomplish this:
I created a invisible view that was aligned horizontally center. I then aligned the right side of the table to this view. This works but for some devices, the screen ratio doesn't make this setup perfect. On devices like the droid, the bottom row is squinched in because the table width is smaller than the height.
I created an ImageView with adjustViewBounds set to true. I sourced it with a very large square image. I have it set to be above the adView and align top and align left. I then set the table layout to align to the bounds of that ImageView. This didn't work because it was a memory hog and the image bounds never fully adjusted when the ad popped up. The image source would go to a smaller scaled square but the right bound never adjusted to the new bounds.
I think this could be very easy if I made a custom class of the TableLayout and set the tablewidth = tableheight. I am struggling with this idea because I don't know where all I would need place the necessary logic. I suppose I would need to add it when the table gets initially drawn and again when the Table adjusts after the ad moves into place.
Can someone help with some sample code on the TableLayout class? Is there another way to do this?
Update 3/30 9:05PM PST
I've made some progress with a custom TableLayout class after looking through the suggestion from CommonsWare. I'm closer to achieving the solution using this class but have one left thing to solve. The new TableLayout doesn't adjust it's bounds so the width is still taking up additional space even though the contents are sized correctly. The width looks to be set when there isn't an ad and it never changes after that.
Here is my really simple extended TableLayout class. Note the onMeasure method where I set the width and height both equal to the height:
public class SquareTableLayout extends TableLayout {
public SquareTableLayout(Context context) {
super(context);
}
public SquareTableLayout (Context context, AttributeSet attrs) { super(context, attrs); }
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
}
}
Here is a screenshot of the problem. I added apurple background to the TableLayout to highlight the problem.
http://ribzy.com/images/tile_takedown_landscape.png
TableLayout cannot accomplish what you seek, for the reasons you have determined. There is no built-in layout that has the notion of tying width and height together. Most likely, you will need to create a custom layout manager from scratch, like this one.
Is it possible to create a view that is bigger than the screen?
I need a view that has a bigger width then the screen of the device. I use this view in a rotation animation. During the rotation the parts that were not on the screen before animating the view will become visible.
Is there a way to achieve this effect with the android framework?
Update
I tried to set my parent layout much bigger then the screen and it is working. This will make somethings a little bit uncomfortable but it could work. The next problem now is that my layout still starts at the left side of the screen. I can't think of a method to make the layout to expand itself to the left and the right of the screen.
Ok I got an answer. It is not very nice because it uses a deprecated View class but it works at least on my current testing screen resolution other resolutions are tested tomorrow.
I wrapped the view that I wanted to expand beyond the screen in an absolute layout like this:
<AbsoluteLayout
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true">
<ImageView
android:id="#+id/content"
android:layout_width="600dip"
android:layout_height="420dip"
android:scaleType="centerCrop"
android:layout_x="-200dip"
android:layout_y="60dip"
android:src="#color/testcolor" />
</AbsoluteLayout>
The -200 x coordinate makes the view stick 200dip out of the left side of the screen. If I'm animating the view those parts that are outside the screen will gradually become visible.
E.g. setting negative bottom margin together with setting extra large layout_height (large enough for you) solved the similar issue as for me.
Works fine at least using API 11+ animations/rotations.
Could look like:
android:layout_marginBottom="-1000dp"
android:layout_height="1000dp"
In case anyone still comes up on this page. The key is your root layout, it will only work with a FrameLayout (or the deprecated absolutelayout). Then you have two options to make your child view bigger.
through xml, this is quick and easy but you don't know the actual screen width & height in advance so your off with setting a ridiculously high value for layout_width & layout_height to cover all screens.
Calculate the screen size programatically and make the view's width/height proportional bigger to this..
Also be aware that your bigger view still starts in the top left corner of the screen so to account this you will have to give a negative top & left margin that's half of what you are adding to the view's width/height
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) viewToMakeBigger.getLayoutParams();
int marginLeft = (int) (viewToMakeBigger.getWidth()*0.1);
int marginTop = (int) (viewToMakeBigger.getHeight()*0.1);
params.width = (int) (viewToMakeBigger.getWidth()*1.2);
params.height = (int) (viewToMakeBigger.getHeight()*1.2);
params.leftMargin = -marginLeft;
params.topMargin = -marginTop;
viewToMakeBigger.setLayoutParams(params);
HorizontalScrollView:
http://developer.android.com/reference/android/widget/HorizontalScrollView.html
Layout container for a view hierarchy that can be scrolled by the user, allowing it to be larger than the physical display.
The simple axml below creates an ImageView that is 400dp wider than the screen (even though the layout_width is set to equal the parent's width) using a negative left and right margin of 200dp.
The ImageView is situated 250dp above the top of the screen using a negative top margin, with 450dp of 700dp vertical pixels visible on the screen.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:background="#FFFF0000"
android:layout_height="700dp"
android:layout_marginLeft="-200dp"
android:layout_marginRight="-200dp"
android:layout_marginTop="-250dp"
android:layout_width="match_parent" />
</RelativeLayout>
You can override the views in the onMeasure method. This will set your View dimensions to 1000x1000 px.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(1000, 1000);
}
Is it possible to create a view that is bigger than the screen?
Why not, you can define the layout_width and layout_height in px(or dip) as you want:
android:layout_width="10000px"
android:layout_height="20000px"
You need to change the size of the window, by getWindow().setLayout. This will increase the size for your window. Since the root layout can be as big as its parent you can then increase the size of the view you want to be bigger than the screen size. It works for me let me know
You can use ViewSwitcher to handle that. Used with Animation and a OnGestureListener looks pretty good.
You can do it programmatically:
FrameLayout.LayoutParams rootViewParams = (FrameLayout.LayoutParams) rootView.getLayoutParams();
rootViewParams.height=displayMetrics.heightPixels+(int)dpToPixels(60);
rootViewParams.width=displayMetrics.widthPixels+(int)dpToPixels(60);
rootView.setLayoutParams(rootViewParams);
rootView.setX(rootView.getX() - dpToPixels(30));
rootView.setY(rootView.getY() - dpToPixels(30));
MUST BE ONLY IN
"public void onWindowFocusChanged(boolean hasFocus)" method.
and
rootView = (RelativeLayout) findViewById(R.id.rootLayout);
Inside "protected void onCreate(Bundle savedInstanceState)" method.
Where yout .xml file is like this:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/rootLayout"
tools:context="com.example.Activity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_centerHorizontal="true"
android:layout_margin="30dp"
android:layout_centerVertical="true"
android:layout_height="match_parent">
// Bla bla bla
</RelativeLayout>
and:
public float dpToPixels(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}