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 );
Related
I'm developing an Android app with avatar (the image icon), the ImageView is inside an RelativeLayout like this
<ImageView
android:id="#+id/indicators"
android:layout_width="#dimen/conversation_avatar_size"
android:layout_height="#dimen/conversation_list_left_indicator_height"
android:layout_below="#id/contact_image"
android:layout_alignWithParentIfMissing="true"
android:layout_marginEnd="#dimen/smaller_def_margin"
android:layout_marginTop="#dimen/smaller_def_margin"
android:scaleType="fitCenter"
tools:src="#drawable/ic_reply"/>
However, I cannot change the width of this ImageView when I make the contact_image invisible because indicator takes less space. The code is like below
indicators.getLayoutParams().width = width;
indicatorsWidth = indicators.getWidth();
indicators.requestLayout();
After indicators.requestLayout(); the width of the indicator still keeps the old one not changed to width. I'm wondering why this can happen? I also tried indicators.setLayoutParams(layoutParams); but still doesn't work.
I did find some interesting, even though indicators.getWidth() is still the old value, indicators.getLayoutParams().width is the new width now, and ImageView size doesn't change at all.
Update : I found what view.getWidth() is, and then I used indicators.setLeft() and indicators.setRight(), this time indicators.getWidth() is also the new width, however, the size still doesn't change, I checked it and found that indicators.getMeasuredWidth() is still the old value, any idea?
Your issue may be android:scaleType="fitCenter" if the height of the ImageView is the constraining factor. The view will be resized to fit the height and maintain the aspect ratio by adjusting the width. See this.
Compute a scale that will maintain the original src aspect ratio, but will also ensure that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. The result is centered inside dst.
Here is a small app to play with this concept. You will see that even though the width is change, the height is not with android:scaleType="fitCenter". If you change it to android:scaleType="fitXY", you will see a change. This may not be your issue exactly, but you can use this code which is an MCVE to test out some ideas.
MainActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView indicators = findViewById(R.id.indicators);
indicators.postDelayed(new Runnable() {
#Override
public void run() {
indicators.getLayoutParams().width = 500;
indicators.requestLayout();
}
}, 2000);
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="#+id/indicators"
android:layout_width="100px"
android:layout_height="100px"
android:layout_alignWithParentIfMissing="true"
android:scaleType="fitCenter"
android:src="#mipmap/ic_launcher"/>
</RelativeLayout>
When modifying LayoutParams, you have to reassign the object back to the view or there will be no effect.
ViewGroup.LayoutParams params = myView.getLayoutParams();
params.width = newWidth;
myView.setLayoutParams(params);
This last line is what actually changes the width of the view.
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
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);
}
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.