42
votes

I'm trying to create a fragment that lays out a series of custom views dynamically. The main content for this layout is a RelativeLayout nested in a LinearLayout (to center it horizontally), nested in a ScrollView.

The RelativeLayout has a few TextViews and a 9 patch ImageView that is meant to scale with the dynamically added custom views. However, the image (achievements_bgImageView below) is ending up as the size of the screen, and is not respecting the size of its parent RelativeLayout even after I've added the appropriate amount of custom views. The image scales fine when I manually set the size of achievements_mainLayout (see the commented out lines below), but does nothing if I try to let that RelativeLayout's wrap_content handle its own sizing.

The ScrollView IS respecting the size of the RelativeLayout, as all the content is present, it's simply the imageView that isn't stretching to match the content at this point.

Any help would be appreciated... My manual calculations don't seem to be good enough to account for different devices, despite the fact I'm accounting for screen density and I'm manually forcing the RelativeLayout to a constant width.

It's worth noting that the measured size of the RelativeLayout is always equal to the height of the screen, regardless of whether or not the sum of its content is greater or less than that height. So, essentially, WRAP_CONTENT is simply not doing what it's supposed to be doing. I have nothing referencing any edge of of the RelativeLayout, so circular dependencies shouldn't be a problem.

fragment_achievements.xml

<?xml version="1.0" encoding="utf-8"?>

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true" >

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal">

        <RelativeLayout
                android:layout_width="320dp"
                android:layout_height="wrap_content"
                android:id="@+id/achievements_mainLayout">

            <ImageView
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:id="@+id/achievements_bgImageView"
                    android:src="@drawable/bkg_achievements9"
                    android:adjustViewBounds="true"
                    android:layout_marginLeft="8dp"
                    android:layout_marginTop="8dp"
                    android:layout_marginRight="8dp"
                    android:layout_centerHorizontal="true"
                    android:scaleType="fitXY"/>

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Name Field"
                    android:id="@+id/achievements_nameTextView"
                    android:layout_alignParentTop="true"
                    android:layout_alignParentLeft="true"
                    android:layout_marginLeft="28dp"
                    android:layout_marginTop="30dp"/>

            <ImageView
                    android:layout_width="52dp"
                    android:layout_height="52dp"
                    android:id="@+id/achievements_avatarImageView"
                    android:layout_below="@+id/achievements_nameTextView"
                    android:layout_alignLeft="@+id/achievements_nameTextView"
                    android:src="@drawable/achieve_avatar"
                    android:layout_marginTop="5dp"/>

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:text="Top Moment:"
                    android:id="@+id/textView2"
                    android:layout_alignBottom="@+id/achievements_avatarImageView"
                    android:layout_toRightOf="@+id/achievements_avatarImageView"
                    android:layout_marginBottom="16dp"
                    android:layout_marginLeft="4dp"
                    android:textSize="12dp"/>

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:text="Me Overall:"
                    android:id="@+id/textView3"
                    android:layout_alignTop="@+id/textView2"
                    android:layout_alignLeft="@+id/textView2"
                    android:layout_marginTop="16dp"
                    android:textSize="12dp"/>

            <TextView
                    android:layout_width="52dp"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:text="153"
                    android:id="@+id/achievements_totalPointsTextView"
                    android:gravity="center"
                    android:layout_alignTop="@+id/achievements_avatarImageView"
                    android:layout_alignRight="@+id/achievements_bgImageView"
                    android:layout_alignEnd="@+id/achievements_bgImageView"
                    android:layout_marginRight="31dp"
                    android:textColor="#f7a033"/>

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Moment"
                    android:id="@+id/achievements_topMomentTextView"
                    android:layout_alignTop="@+id/textView2"
                    android:layout_toRightOf="@+id/textView2"
                    android:layout_marginLeft="5dp"
                    android:textSize="12dp"/>

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="153"
                    android:id="@+id/achievements_overallTextView"
                    android:layout_alignTop="@+id/textView3"
                    android:layout_toRightOf="@+id/textView3"
                    android:layout_marginLeft="5dp"
                    android:textSize="12dp"/>

        </RelativeLayout>
    </LinearLayout>
</ScrollView>

AchievementFragment.java

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View fragmentView = null;

    fragmentView = inflater.inflate(R.layout.fragment_achievements, container, false);

    ImageView avatarImageView = (ImageView)fragmentView.findViewById(R.id.achievements_avatarImageView);

    ...

    // Basic Achievement List Setup
    RelativeLayout mainLayout = (RelativeLayout)fragmentView.findViewById(R.id.achievements_mainLayout);
    AchievementRow currentRow = null;

    List achievementTypeList = CampaignManager.sharedManager().sortedAchievementTypeList();

    int achievementCount = achievementTypeList.size();

    for (int i = 0; i < achievementCount; i++) {
        AchievementType achievementType = (AchievementType)achievementTypeList.get(i);

        // Every third achievement creates a new row.
        if ((i % 3) == 0) {
            AchievementRow row = (AchievementRow)inflater.inflate(R.layout.widget_achievementrow, null);

            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);

            if (currentRow == null) {
                layoutParams.addRule(RelativeLayout.BELOW, avatarImageView.getId());
                layoutParams.setMargins(10, 70, 10, 0);
            } else {
                layoutParams.addRule(RelativeLayout.BELOW, currentRow.getId());
                layoutParams.setMargins(10, 10, 10, 0);
            }

            layoutParams.addRule(RelativeLayout.ALIGN_LEFT, backgroundImageView.getId());
            layoutParams.addRule(RelativeLayout.ALIGN_RIGHT, backgroundImageView.getId());

            row.setLayoutParams(layoutParams);
            row.setId(i+1);
            mainLayout.addView(row);

            currentRow = row;
        }

        // Now setup the Button
        AchievementButton achievementButton = currentRow.buttonForIndex(i % 3);
        achievementButton.achievementType = achievementType;
        achievementButton.setOnClickListener(achievementButtonListener);
        achievementButton.setVisibility(View.VISIBLE);

        CacheManager.sharedManager().fetchAchievementThumbnail(getActivity(), achievementButton, achievementType);
    }

    // This is the manual scaling of mainLayout
    // float scale = getResources().getDisplayMetrics().density;
    // float headerHeight = scale * 150.0f;
    // float rowHeight = scale * 78.0f;
    // ViewGroup.LayoutParams mainLayoutParams = mainLayout.getLayoutParams();
    // mainLayoutParams.height = (int)(headerHeight + (Math.ceil(achievementCount / 3.0) * rowHeight));

    return fragmentView;
}
8
First of all, you should probably use a list view or grid view for this. Secondly, layout parameters aren't applied until after the next layout pass. Have you tried calling invalidate() or requestLayout()?Collin Flynn
invalidate(), requestLayout() and forceLayout() all seem to do nothing... What I don't understand is that this is all still in onCreateView, nothing has been drawn or laid out by the time all of these views have been added.brainfree

8 Answers

18
votes

Try calling requestLayout on the children.

I recently had a similar problem and was similarly frustrated that things like invalidate and requestLayout seemed to do nothing. What I didn't understand is that requestLayout doesn't propagate down to its children; it propagates up to its parents. To re-measure something that was previously measured, I had to call requestLayout on the View that changed rather than the View I actually wanted to resize.

14
votes

Android does NOT refresh layout of views with "wrap_content" once it has been displayed.

So if you add a child view, or modify the content dynamically, you're screwed. I do agree that this is a nightmare and a real flaw in Android UI!

To solve that, I've written a static class that recomputes the sizes and forces the update of the layout for the views with "wrap_content"

The code and instructions to use are available here:

https://github.com/ea167/android-layout-wrap-content-updater

Enjoy!

9
votes

A simple way to update the size of a View with WRAP_CONTENT is change the visibility to GONE and back to the old visibility.

int visibility = view.getVisibility();
view.setVisibility(View.GONE);
view.setVisibility(visibility);

TESTED ON ANDROID JELLY BEAN IN 2014

MAY NOT WORK ON NEWER ANDROID VERSIONS

1
votes

Ok, I solved this by manually measuring the RelativeLayout immediately after adding all the views and setting the mainLayoutParams height explicitly. I wish I was smarter and knew why it wasn't automatically doing this correctly in the first place, but oh well.

    ...
    mainLayout.measure(0, 0);

    ViewGroup.LayoutParams mainLayoutParams = mainLayout.getLayoutParams();
    mainLayoutParams.height = mainLayout.getMeasuredHeight() + 10;
    ...
0
votes

You are getting this problem because you set your layout first and then add its content dynamically.

You are telling the layout to wrap to the content that is not yet their. Try using your layout inflater after you have grabbed your content

0
votes

You should use NestedScrollView Instead of simple scrollview. here is my sample Activity Layout code

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:background="#F0ECE6"
    >

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />


        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabMode="fixed"
            app:tabIndicatorHeight="6dp"
            android:layout_marginTop="-10dp"
            app:tabGravity="fill"/>
    </android.support.design.widget.AppBarLayout>

    <wsit.rentguru.utility.CustomViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"  />
</android.support.design.widget.CoordinatorLayout>

here is code for custom viewpager

public class CustomViewPager extends ViewPager {

        private boolean enabled;

        public CustomViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
            this.enabled = false;
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (this.enabled) {
                return super.onTouchEvent(event);
            }

            return false;
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            if (this.enabled) {
                return super.onInterceptTouchEvent(event);
            }

            return false;
        }

        public void setPagingEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }

setup function for viewpager in the activity

private void setupViewPager(ViewPager viewPager) {
        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        adapter.addFragment(new SampleFragment(), " ");

        viewPager.setAdapter(adapter);
    }

class ViewPagerAdapter extends FragmentPagerAdapter {
        private final List<Fragment> mFragmentList = new ArrayList<>();
        private final List<String> mFragmentTitleList = new ArrayList<>();

        public ViewPagerAdapter(FragmentManager manager) {
            super(manager);
        }

        @Override
        public Fragment getItem(int position) {
            return mFragmentList.get(position);
        }

        @Override
        public int getCount() {
            return mFragmentList.size();
        }

        public void addFragment(Fragment fragment, String title) {
            mFragmentList.add(fragment);
            mFragmentTitleList.add(title);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mFragmentTitleList.get(position);
        }
    }

here is the sample SampleFragment layout code

<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#F0ECE6"
    android:fillViewport="true"
    android:scrollbars="vertical"
    android:animateLayoutChanges="true"
    xmlns:android="http://schemas.android.com/apk/res/android">

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F0ECE6"
    android:focusableInTouchMode="true"
   >

    <Spinner
        android:id="@+id/product_category"
        android:layout_margin="20dp"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="@drawable/edittext_rectangle_box"
        android:gravity="center|left"
        android:textSize="14sp"
        android:paddingLeft="10dp"
        android:drawableRight="@drawable/ic_down_arrow"
        />

    <Spinner
        android:id="@+id/product_sub_category"
        android:layout_below="@+id/product_category"
        android:layout_marginRight="20dp"
        android:layout_marginLeft="20dp"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center|left"
        android:visibility="gone"
        android:paddingLeft="10dp"
        android:background="@android:color/white"
        android:drawableRight="@drawable/ic_down_arrow"
        />
    <EditText
        android:id="@+id/product_title"
        android:layout_below="@+id/product_sub_category"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="15dp"
        android:background="@android:color/white"
        android:hint="PRODUCT TITLE"
        android:singleLine="true"
        android:imeOptions="actionDone"
        android:layout_width="match_parent"
        android:gravity="center|left"
        android:padding="10dp"
        android:textSize="14sp"
        android:textColorHint="#000000"
        android:layout_height="40dp" />

    <LinearLayout
        android:id="@+id/availability_layout"
        android:layout_below="@+id/product_title"
        android:layout_marginRight="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/availability"
            android:textStyle="bold"
            android:paddingBottom="10dp"
            android:textSize="14sp"
            />

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:weightSum="2">

            <Button
                android:id="@+id/from"
                android:background="@android:color/white"
                android:hint="FROM"
                android:layout_weight="1"
                android:gravity="center|left"
                android:padding="10dp"
                android:textSize="14sp"
                android:textStyle="normal"
                android:textColorHint="#000000"
                android:layout_width="0dp"
                android:layout_height="40dp"
                android:layout_marginRight="10dp"/>

            <Button
                android:id="@+id/to"
                android:background="@android:color/white"
                android:hint="TO"
                android:layout_weight="1"
                android:gravity="center|left"
                android:padding="10dp"
                android:textSize="14sp"
                android:textColorHint="#000000"
                android:layout_width="0dp"
                android:layout_height="40dp"
                android:layout_marginLeft="10dp"/>



        </LinearLayout>


    </LinearLayout>


    <LinearLayout
        android:id="@+id/product_location_layout"
        android:layout_below="@+id/availability_layout"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/product_location"
            android:textStyle="bold"
            android:paddingBottom="10dp"
            android:textSize="14sp"
            />


        <Spinner
            android:id="@+id/state_spinner"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:background="@drawable/edittext_rectangle_box"
            android:gravity="center|left"
            android:textSize="14sp"
            android:layout_marginBottom="10dp"
            android:drawableRight="@drawable/ic_down_arrow"
            android:paddingLeft="10dp"
            />

        <EditText
            android:id="@+id/area"
            android:background="@android:color/white"
            android:hint="Area"
            android:gravity="center|left"
            android:padding="10dp"
            android:textSize="14sp"
            android:textColorHint="#000000"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:singleLine="true"
            android:imeOptions="actionNext"
            android:layout_marginBottom="10dp"
            />


        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:weightSum="2">

            <EditText
                android:id="@+id/zipCode"
                android:background="@android:color/white"
                android:hint="Zip Code"
                android:layout_weight="1"
                android:gravity="center|left"
                android:padding="10dp"
                android:textSize="14sp"
                android:textColorHint="#000000"
                android:layout_width="0dp"
                android:layout_height="40dp"
                android:singleLine="true"
                android:imeOptions="actionNext"
                android:layout_marginRight="10dp"/>

            <EditText
                android:id="@+id/city"
                android:background="@android:color/white"
                android:hint="City"
                android:layout_weight="1"
                android:gravity="center|left"
                android:padding="10dp"
                android:textSize="14sp"
                android:singleLine="true"
                android:imeOptions="actionDone"
                android:textColorHint="#000000"
                android:layout_width="0dp"
                android:layout_height="40dp"
                android:layout_marginLeft="10dp"/>



        </LinearLayout>


    </LinearLayout>

    <Button
        android:id="@+id/tab1_next"
        android:layout_width="150dp"
        android:layout_height="40dp"
        android:text="NEXT"
        android:layout_below="@+id/product_location_layout"
        android:layout_margin="20dp"
        android:layout_alignParentRight="true"
        android:background="@color/next_button"
        android:textColor="@android:color/white"
        android:layout_marginBottom="20dp"
        />

</RelativeLayout>

</android.support.v4.widget.NestedScrollView>
0
votes

Set the layout parameters again(width and height), right after adding the view. This worked for me.

if the parent View is a FrameLayout, then do something like this:

    ImageView view = (ImageView) LayoutInflater.from(activity).inflate(R.layout.image_object_view, null);

    imageObjectsHolder.addView(view);
    FrameLayout.LayoutParams param = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    view.setLayoutParams(param);
0
votes

My problem was fixed by seting layout_width to some specific dp.

So changing from "wrap content" or "match parent" to

android:layout_width="300dp"

will fix it, but i know it's not solution for all cases. But maybe you have some parent width, so you can apply the width to the textview. height leave with wrap_content, and it will work.