Android get Fragment width - android

I have a layout with 3 fragments:
<LinearLayout android:id="#+id/acciones"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:id="#+id/fragment1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</LinearLayout>
<LinearLayout
android:id="#+id/fragment2"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:id="#+id/f3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</LinearLayout>
</LinearLayout>
In the first fragment I have a TableLayout in which I have one custom TextView in each row.
I want to know the width of the fragment because if the custom TextView is wider than the fragment, I'll set the number of lines necessary.
This is what I've done in my custom TextView:
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMaxWidth = (float) (getMeasuredWidth());
}
With this line I got the width from the three Fragments, not only the one which contains the custom TextView.
Thanks.

You should be able to set the width of the TextView to be fill_parent, in which case it will do the wrapping for you. You should not set the widths of your layouts to be match_parent since it is inefficient when you're using layout weights.
Since android's layout system is occasionally mysterious with regards to view sizes, if setting the TextView width to be fill_parent actually makes it take up the whole screen (as your question appears to be implying) do the following:
Set your TextView width to 0 by default. In onCreate of your activity, after setting the content view:
findViewById(R.id.acciones).getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
final int fragmentWidth = findViewById(R.id.releventFragmentId).getWidth();
if (fragmentWidth != 0){
findViewById(R.id.yourTextViewId).getLayoutParams().width = fragmentWidth;
}
}
});
By setting the TextView's width to 0 initially, you prevent it from changing the widths of the fragments. Then you can use a view tree observer to get the width of whatever fragment you're interested in (by looking at its root view) after layout has occurred. Finally you can set your TextView to be that exact width, which in turn will do the wrapping for you automatically.
Note that onGlobalLayout can be called multiple times and is regularly called before all of the views have been completely laid out, hence the != 0 check. You will also probably want to do some kind of check to make sure that you only set the width of the text view once, or otherwise you can get into an infinite layout loop (not the end of the world, but not good for performance).

Related

Difference in RelativeLayout and other in onMeasure

So, basically i have a custom view :
public class CustomView extends PercentFrameLayout
As you can see i've inherited it from PercentFrameLayout(might be the reason for this issue, but i'm not sure)
Inside of this view i've inflated layout .xml file:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center">
<View
style="#style/SomeStyle"
app:layout_widthPercent="98%"
app:layout_heightPercent="98%"/>
....
</merge>
Also, in this custom view, i've overriden onMeasure method to multiply it's size by 2(just for test):
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.getSize(widthMeasureSpec) * 2|MeasureSpec.EXACTLY, MeasureSpec.getSize(heightMeasureSpec) * 2|MeasureSpec.EXACTLY);
}
Currently this custom view is in RelativeLayout:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center">
<com.example.CustomView
android:id="#+id/progress_new_collagen"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
The issue: Currently, its size not multiplied by two, its multiplied by, like 4 for width and 3 for height, and i don't know why.
The strange thing which made me want to ask a question: If it's not RelativeLayout as parent to this view, but any other ViewGroup class, like FrameLayout, LinearLayout and so on - code is working. It's also working if i just remove onMeasure. Is there any difference in this ViewGroups in case of onMeasure ?
RelativeLayout will call onMeasure() twice each time it is rendered. I think that this is why you are getting your strange results. If the second onMeasure() uses the results from the first call, that you will not double, but quadruple your dimensions.

wrap_content on recycleview, take height of item (horizontal list)

I have a RecycleView, set to horizontal. Now i would like the height to match the height of an item in the view. I don't know the height in advance.
Currently i have this:
Main fragment:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="horizontal"
android:layout_marginLeft="1dp"
android:layout_marginRight="1dp"
android:layout_marginTop="1dp"
android:layout_gravity="top"
android:id="#+id/my_listview"/>
</RelativeLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
The image
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/banner_image"
android:adjustViewBounds="true"
android:scaleType="fitXY"
android:background="#color/dark_gray"
/>
but it always takes up the entire screen. so i have a lot of whitespace in between. Is there a way that i could use wrap_content on the recycleview items?
This is what it currently looks like:
I have placed 2 banners currently, the top one with the big whitespace and small image is the one with the recyclerview. The one below is just the image placed in an imageview (is what it should look like, but i want it with a recyclerview so i can scroll through the banners if i have multiple)
Now i would like the height to match the height of an item in the
view. I don't know the height in advance.
During layout pass, LinearLayoutManager cannot predict the height of all the views down in the adapter that are yet to be created, ahead of time.
You can measure() an item view, probably the first one, from the adapter and then set the height of RecycleView through code.
If all items can have different height then you'll have to measure them all, which is certainly not the correct way for a good UI.
You are probably inflating the layout incorrectly.
Did you use?
layoutInflater.inflate(R.layout.your_layout, null);
Try using instead. (The other way messes with measuring)
layoutInflater.inflate(R.layout.your_layout, parent, false);
Is there a reason why you have the RecycleView wrapped inside a RelativeLayout ?
I would try to move the RecycleView out of the RelativeLayout first.
Secondly i see that your ViewPager has a layout_weight but i do not see a android:weightSum attribute in the parent layout. Is there a reason you are specifying the ViewPager to have a weight but not the RecycleView ? Have you tried fixing the size of the RecyleView to some dp to check if the arrangement is correct and the ViewPager is using the weight attribute correctly ?
Thirdly i would try to play around with the Imageview. Try maybe wrapping it inside a RelativeLayout maybe ? You will have to play around with the parameters there and see if it works out for you. Because you want something custom there is not a straight answer to this question.
I also faced the same problem and I wanted to view two images in two columns with the same size in RecyclerView and I could solve the problem by using below code. Please try this.
// inner class to hold a reference to each item of RecyclerView
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView imgViewIcon;
public ViewHolder(View itemLayoutView) {
super(itemLayoutView);
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
int colSpan = 2;
imgViewIcon.getLayoutParams().height = (int) (displayMetrics.widthPixels / colSpan);
imgViewIcon.getLayoutParams().width = (int) (displayMetrics.widthPixels / colSpan);
}
}

How do I make a FrameLayout fill the height of its parent so the whole page scrolls together?

I have a FrameLayout that loads Fragments by tapping on tabs in a TabWidget. I can't figure out how to make the height of the FrameLayout as tall as its content, so that the whole containing ScrollView will scroll together as one instead of a separate scrolling view.
Here's a visual example of this Fragment's structure:
As you can see, the Frame Layout Visible Height only reveals one row of the Fragment, when in fact, there are a few. I can scroll within the FrameLayout to see the other rows as it is now, but that's not what I'm going for. The FrameLayout is made up of a LinearLayout containing a GridView with their layout_heights set to wrap_content.
I tried hardcoding the height of the FrameLayout to something like 500dp and it works great except for the fact that it's no longer dynamically sized. Would I need to resize the FrameLayout programmatically each time a new image is loaded into the inner content? Is there a layout attribute I can set so it'll stretch its height to match its inner content?
Here's my layout xml file:
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="200dp">
<!-- CONTAINS USER INFO AND STATS -->
</RelativeLayout>
<android.support.v4.app.FragmentTabHost
android:id="#android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#ffffff">
<TabWidget
android:id="#android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:weightSum="2">
</TabWidget>
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
</FrameLayout>
</LinearLayout>
</android.support.v4.app.FragmentTabHost>
</LinearLayout>
</ScrollView>
Thank you!
Since I'm going to set a bounty on this, I thought I'd share what I've figured out so far.
In the thumbnails, onSuccess when each image is loaded, I'm calling a function in the GridLayout that holds the images that counts the images and sets the height of the GridLayout. This works fine, although it seems like it'd be a bit inefficient.
What I'm doing is setting the GridLayout height and then calling requestLayout and invalidate on it and it's parent(s). This works, but not as the images loading. It'll work if I go to a different tab and return to the thumbnails, oddly enough. Which makes me think I'm not updating at the right time or on the right object.
Anyway, that said. Does anyone know how to make the height of a GridLayout expand to hold its contents (instead of scrolling) so I can scroll the entire page (including the top section)?
I should also add the GridView layout:
<GridView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/grid"
android:stretchMode="columnWidth"
android:gravity="center"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="false"
android:fastScrollAlwaysVisible="false"
android:fastScrollEnabled="false"
android:numColumns="3"
android:choiceMode="none">
</GridView>
I was in a similar situation but I had a ListView instead of a GridView. You are right in the part when you have to set the height dynamically each time you add an item or if you call notifyDataSetChanged().
THIS CODE IS FOR LISTVIEW WITH DIFFERENT HEIGHT FOR EACH ROW
private void setListViewHeightBasedOnChildren(MyQueueAdapter listAdapter) {
int desiredWidth = MeasureSpec.makeMeasureSpec(
mListView.getWidth(), MeasureSpec.AT_MOST);
int totalHeight = 0;
View view = null;
int i;
for (i = 0; i < listAdapter.getCount(); i++) {
view = listAdapter.getView(i, view, mListView);
view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = mListView.getLayoutParams();
params.height = heightList
+ (mListView.getDividerHeight() * (listAdapter
.getCount() + 3));
heightListComplete = params.height;
mListView.setLayoutParams(params);
}
You need to modify this code according to your needs, you don't need the loop as the height of each row is static in your case. If you need more help let me know.
ALTERNATIVE
If you know the height of the view in dp you can easily convert the dp in px and set the height of your gridview according to number of rows.
When using dynamic sizes you'll run into problems once you put match_parent inside a wrap_content thing. One tries to get a small as it's content and the content tries to be as big as it's parent. Neither side will know how to scale properly in the end.
ScrollView is such a thing that falls in this category. It's a scalable window to it's content so it can't be wrap_content and the content can't be match_parent because it's parent is a virtual infinite space.
Change <ScrollView android:layout_height="wrap_content" to match_parent (or a fixed size).
To solve the size of the content
set the root layout (LinearLayout in your case) of your ScrollView to be a fixed size so it's content can be match_parent again.
use wrap_content all the way.
combine the two: wrap_content until a child defines an absolute size, then match_parent inside there.
The wrap_content route will only work if all the elements in the layout from inner to outer most expand properly based on their content. Nothing can rely on parent bounds unless you add some.
Your content looks rather dynamic in size. So it is likely that you need to use some code to manually set sizes based on content. E.g. if those images inside your tab frame are a GridView (essentially ScrollView with grid content again) you'll need to set it's size manually. More than 1 degree of freedom in wrapping dynamically sizing containers isn't solvable automatically.
Parent of your frame layout is linear layout whose height is wrap_content. also, your framelayout's height is wrap_content. change both of them to fill_parent. using match_parent is more preferred now a days insted of fill_parent

Android - Layout that fills the screen (using weight) and scrollable when soft keyboard is out

I need to build a layout that fills the screen. I do that using a LinearLayout with weights in their child views.
This layout is a form, so I also need it to be scrollable when the soft keyboard is out.
What happens is that the screen height is shrunk when the keyboard is out, so the views are shrunk too, crowded together. They look awful.
Do you know any way to build a form layout that fills the screen but at the same time preserves its aspect and can be scrollable when the soft keyboard is out? This is really easy in iOS but in Android... I can't think of a solution.
Thank you
iOS always has the same screensize, Android doesnt. You need to use a responsive design in Android since you dont know how big the screen is that the user is using.
I dont think you need weights(not verticle anyway) to fix this.
Just use a ScrollView and add a LinearLayout(orientation:vertical) inside it.
Add your views inside this LinearLayout, and if you need to have multiple views horizontally. Add another LinearLayout(orientation:horizontal)
Set margin on your views to get a nice distance between them.
This will allow users that have small screens to scroll in your layout, and if the user has the keyboard out they can scroll aswell.
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
The first two will lie beneath each other.
The next two will lie next to each other, apply weights here if you want them to be of different sizes
Define your form within scroll view and in manifest make the windowSoftInputMode, configChanges entries as shown below
<activity
android:name="com.example.MainActivity"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize"
android:configChanges="keyboardHidden|orientation">
</activity>
Hopefully this can help you.
I have built the following scheduleConsolideHeights method to solve the problem. Thanks to Joakim for his comments.
Although it works fine for me, I'm sure it can be improved. Any suggestion will be welcome.
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.XXX);
// LinearLayout from your R.layout.XXX that has vertical orientation and
// its children have weights instead of absolute heights
final LinearLayout layout = (LinearLayout) findViewById(R.id.layout_with_weights);
scheduleConsolideHeights(layout);
}
/**
* Adds a listener to the layout {#link ViewTreeObserver} to wait for the view measurements
* and then calls {#link #consolideHeights(LinearLayout)}.
*/
private static void scheduleConsolideHeights(final LinearLayout layout) {
final ViewTreeObserver viewTreeObserver = layout.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
consolideHeights(layout);
}
});
}
}
/**
* Note: you might want to use {#link #scheduleConsolideHeights(android.widget.LinearLayout)}.
*
* Takes a {#link android.widget.LinearLayout} and, for each of its child views, sets the height
* of the child's LayoutParams to the current height of the child.
*
* Useful when a {#link android.widget.LinearLayout} that has relative heights (with weights)
* may display the keyboard. With absolute heights, the layout maintains its aspect when the soft
* keyboard appears.
*
* See: http://stackoverflow.com/q/22534107/1121497
*/
private static void consolideHeights(LinearLayout layout) {
for (int i = 0; i < layout.getChildCount(); i++)
{
final View child = layout.getChildAt(i);
final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) child.getLayoutParams();
params.height = child.getHeight();
params.weight = 0;
child.setLayoutParams(params);
}
}
}
As SiGangteng response in that question that must help you to solve the problem (inside the specific in the manifest)
android:windowSoftInputMode="adjustPan"

Fullscreen App, Layout shifted down by height of notification bar

I have an App that has a toolbar at the bottom of the screen and the rest is filled with a custom View (see xml below). Now when I make the App full screen (I tried all possibilities, programmatically and via Manifest.xml), when it's started the whole layout seems to be shifted down by about the height of the notification bar. The buttons in the toolbar are only visible half-way. Sometimes, all of it moves up after a few seconds, or when I click a button in the toolbar.
I'm pretty sure, that it's a problem with my custom view, because I do not get this effect if I replace it with a Button or the like. I guess it must have something to do with the onMeasure method. I don't really know how to implement it, my version is shown below. The custom view is used for drawing inside, so basically it wants to be as large as possible.
Any help would be much appreciated. I searched for several hours already, but no clue yet.
layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<com.example.MyCanvasView
android:id="#+id/canvas"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<!-- Buttonbar -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:orientation="horizontal"
android:background="#android:drawable/bottom_bar"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3"
/>
</LinearLayout>
</LinearLayout>
And this is my onMeasure method:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
You're not taking the mode into account when you're setting your measurement.
The mode of a MeasureSpec can be one of MeasureSpec.EXACTLY, MeasureSpec.AT_MOST, or MeasureSpec.UNSPECIFIED. Simply accepting the size component and setting your measured size to that is appropriate for EXACTLY, but it isn't often the right thing for the others.
Because you're trying to use layout_weight in addition to a height of wrap_content on this custom view, the following is happening:
Your custom view is the first child of the LinearLayout with a height of wrap_content so LinearLayout measures it. Since LinearLayout has been told by the LayoutParams that it should wrap_content, it measures your custom view with a MeasureSpec mode of AT_MOST and a size of the entire available space.
But your custom view is greedy. It decides to take all of the space available. In essence, you have implemented your measurement to treat wrap_content as match_parent.
Now there's no more space left. The lower button bar gets measured accordingly but it's not done yet, there's a child with weight. In a LinearLayout any space left over after all normal measurement is complete is divided among the weighted children according to their weight values. This isn't the behavior you want.
When you use weight to fill available vertical space like you're doing in this layout, you normally want to set the layout_height to 0dip. This will make LinearLayout skip the first measure pass on that child and only use the weighted measurement pass to measure your view, giving it the remaining available space.
I found the reason for the described behaviour. I had set the view to be focusable in touchmode via setFocusableInTouchMode(true) in the onCreate() method. As soon as I removed this, it works fine. Thanks to adamp though -- your description of what goes on during layout and measuring was very interesting.
But that leaves me with the problem that I do not receive any key/button clicks any more :-(

Categories

Resources