Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android layout resizing parent without resizing the childs

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 hereenter 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

like image 891
Y2theZ Avatar asked May 25 '14 14:05

Y2theZ


2 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 ...

like image 198
Guian Avatar answered Oct 19 '22 10:10

Guian


<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.

like image 41
Vikram Avatar answered Oct 19 '22 12:10

Vikram