Home » Android » android layout resizing parent without resizing the childs

android layout resizing parent without resizing the childs

Posted by: admin June 15, 2020 Leave a comment

Questions:

TLDR version:

When I resize the parent:

enter image description here

Explanation:

in an android application, I have a parent layout and inside it multiple relative layouts.

<LinearLayout
    android:id="@+id/parent"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal" >

    <RelativeLayout
        android:id="@+id/child1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/transparent">

     </RelativeLayout>

    <RelativeLayout
        android:id="@+id/child2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/transparent">

     </RelativeLayout>
</LinearLayout>

The children of this layout have some texts and pictures in it. What I’m doing is the following:

When the user clicks on a button, I want to collapse the parent linear layout (animate width until it is 0). I have implemented this function to do it:

public static void collapseHorizontally(final View v) {
    final int initialWidth = v.getMeasuredWidth();

    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if(interpolatedTime == 1){
                v.setVisibility(View.GONE);
            }else{
                v.getLayoutParams().width = initialWidth - (int)(initialWidth * interpolatedTime);
                v.requestLayout();
            }
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // 1dp/ms
    long speed = (int)(initialWidth / v.getContext().getResources().getDisplayMetrics().density);
    a.setDuration(speed);
    v.startAnimation(a);
}

I am calling the above function on the parent view.
This is working correctly, and the parent view is resizing in width until it reaches 0, then I set his visibility as gone.

My Problem

While the parent view is re-sizing (collapsing) the child views are also resizing with it. what I mean is that when the width of the parent reaches one of the child, the child element will also re-size with it and it will cause the textviews in it to re-size too.

Here is some screenshots for an example:

enter image description here

Here you can see the initial layout, there is the parent layout in green, and the child layout (with the date and text and icon).

Then when I start to reduce the size of the parent layout, the child layout size will also be affected and it will reduce the size of the textview in it, causing the words to wrap as seen in the following 2 images:

enter image description here enter image description here

As you notice the text in the child is being wrapped as the width of the parent is reduced.

Is there a way I can have the child element to not resize with the parent view, but remain as it is, even if the parent view size is reducing? I need the size of the chil element to stay fixed during the whole resizing animation nad get cut by the parent instead of resizing with it.

Thank you very much for any help

How to&Answers:

Your problem comes from several issues.

  • first : the singleLine="false" parameter on your TextViews >
    With this parameter, you can’t ask it to wrap its content, since the text can use multiple lines and thus don’t have a proper width. If there are more than one line it will fill its parent.
  • second: The behaviour of “wrap_content” is to fit the content size unless its bigger than the parent. in this case, it will match the parent size again. (we can see, from padding or margin movements, than borders are following the parent size.)

The second issue can be solved by using fixed size but its not the case with the first point :

You could set singleLine="true", with android:width="wrap_content" to see it working on a single line. (set android:ellipsize="none" in order to hide the ‘…’ moving around)
But a single line text isn’t what you need right ?

I’ve made tests with a extend of TextView in order to avoid those views from resizing, even with multiple lines :

public class NoResizeTextView extends TextView {

    int firstWidth = -1;
    int firstHeight = -1;

    public NoResizeTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        Layout layout = getLayout();
        if (layout != null) {

            if (firstWidth == -1)
                firstWidth = getMeasuredWidth();
            if (firstHeight == -1)
                firstHeight = getMeasuredHeight();

            setMeasuredDimension(firstWidth, firstHeight);
        }
    }

}

Then use this TextView extend in your layout :

       <com.guian.collapsetest.NoResizeTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="none"
                android:singleLine="false"
                android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit" >
        </com.guian.collapsetest.NoResizeTextView>

That’s not really beautiful and I don’t advise to use it too much since it could break android’s layout mecanism. But in your particular case, I guess it does the job.

Logic :

When you extend a view to create a custom one, you are responsible for the implementation of onMeasure. This is what give its size to your view. So that’s how it works : you compute once the size ( or let the default function do it for you thanx to getMeasuredW/H) and save it, then on each request for size, you just return the same values so your view size won’t change.

Limitations :

Sine your view size won’t change, it can have a bad behaviour when changing screen orientation / parent size or again, when the text inside change (As Vikram said).
That’s what I called “break android layout mecanism”.
If you need to change the text dynamically, you would have to extend the setText method to allow it to resize at this point …

Answer:

<Edit> to support the requirements (see comments):

From what I can tell, the following is what you’re after. One change that is needed to support your requirements is the use of FrameLayout as the parent container.

enter image description here

The layout now is:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fl"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white" >

    <RelativeLayout
        android:id="@+id/rl1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_blue_bright" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
            android:textColor="@android:color/black"
            android:layout_marginTop="100dp" />

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_blue_dark" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
            android:textColor="@android:color/black"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="200dp" />

    </RelativeLayout>

    <Button
        android:id="@+id/bClickToAnimate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click to Resize" />

</FrameLayout>

Java code:

FrameLayout fl;
RelativeLayout rl1;
RelativeLayout rl2;
Button b;
boolean isOverlapping;
int toTranslate;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ....

    rl1.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {

           rl2.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            // Get width of the viewgroup
            int width = rl1.getWidth();

            // We will keep the second viewgroup outside bounds - we need
            // FrameLayout for this     
            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) rl2.getLayoutParams();
            lp.leftMargin = width;
            lp.width = width;

            rl2.setLayoutParams(lp);

            // Amount to translate
            toTranslate = width;

        }
    });

    b.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            // Viewgroup `rl2` is currently not overlapping
            // Translate `rl2` over `rl1`
            if (!isOverlapping) {
                ObjectAnimator oa = ObjectAnimator.ofFloat(rl2, 
                                               "translationX", 0, -toTranslate);
                oa.setDuration(2000L);
                oa.setInterpolator(new AccelerateInterpolator(2.0f));
                oa.start();
            } else {

                // Viewgroup `rl2` is currently overlapping
                // Translate `rl2` off `rl1`
                ObjectAnimator oa = ObjectAnimator.ofFloat(rl2, 
                                               "translationX", -toTranslate, 0);
                oa.setDuration(2000L);
                oa.setInterpolator(new AccelerateInterpolator(2.0f));
                oa.start();
            }
        }
    });
}

</Edit>

Well, this is quite straightforward in fact. Here, take a look:

All you need is the following:

// Animate property `right` - final width is zero
ObjectAnimator oa = ObjectAnimator.ofInt(YOUR_VIEWGROUP, "right", 
                                            YOUR_VIEWGROUP.getWidth(), 0);

// Set animation duration - 5 seconds
oa.setDuration(5000L);

// Set interpolator
oa.setInterpolator(new AccelerateInterpolator(2.0f));

// Start animation
oa.start();

If your app’s minSdkVersion is lower than 11, use NineOldAndroids library to support > api 8.

I am using a fairly simple layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/white" >

    <Button
        android:id="@+id/bClickToAnimate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click to Resize" />

    <RelativeLayout
        android:id="@+id/rlToAnimate"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_blue_bright" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Lorem ipsum dolor sit amet, consectetur 
                              adipisicing elit, sed do eiusmod tempor 
                              incididunt ut labore et dolore magna aliqua."
            android:textColor="@android:color/black"
            android:layout_centerVertical="true" />

    </RelativeLayout>

</LinearLayout>

And the viewgroup that is being animated is rlToAnimate.

Answer:

You can use an ImageView that will act as an overlay above the Layout, so instead shrinking the layout, you are just covering the layout with the ImageView with black background which will slide from left and after is done you can set everytihng to GONE.

  1. Have an ImageView with match parent attr and visibility gone.
  2. When user clicks the button, set ImageView visibilty to visible and animate to slide in from the left.
  3. OnAnimationEnd set view to gone on ImageView and other Layouts or resize the layouts whatever you need.

Answer:

I’ve dealt with the same requirements in the past in this is how I solved it, I believe it’s the simplest most flexible way to implement it. This is tested and working with your xml and your animation code.
Two simple steps should do the trick:

  1. Surround your parent layout with a FrameLayout – This will be the layout you’re actually resizing.
  2. Before doing any actual resizing we need to force the R.id.parent layout to keep a constant size by providing it with a fixed size in pixels. In run time, after the layout pass we update the layoutParams to set the fixed size. We do it after a layout pass so we know that the fixed size we set is based on the system’s calculations of the required size (i.e. after all the match parent/wrap content layout calculations are done).

Here’s how your layout should look (just added the FrameLayout):

<FrameLayout
android:id="@+id/parentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<LinearLayout
    android:id="@+id/parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#0f0"
    android:gravity="center_vertical"
    android:orientation="horizontal" >

    <RelativeLayout
        android:id="@+id/child1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/transparent">

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/child2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/transparent">

    </RelativeLayout>
</LinearLayout>

Now you need to add a few lines on code in onCreate method of the activity (or elsewhere if it makes more sense depending on your code. But probably onCreate):

final View fixedSizeLayout = findViewById(R.id.parent);

    // Here we take the requested calculated size of the layout (as calculated by the layout pass)
    // and apply it as a fixed size to the layout
    fixedSizeLayout.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            if (fixedSizeLayout.getWidth() > 0) {
                LayoutParams params = fixedSizeLayout.getLayoutParams();
                // this will be different on the first time because the layout width will be a constant (for match_parent) and not actual size
                if (params.width != fixedSizeLayout.getWidth()) {
                    // Setting a fixed size in px
                    params.width = fixedSizeLayout.getWidth();
                    fixedSizeLayout.setLayoutParams(params);
                    fixedSizeLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
            }
        }
    });

Note that the view you’re resizing is the FrameLayout (R.id.parentContainer) – While this view gets smaller with the animation, the layout R.id.parent will keep it’s constant size.