【问题标题】:android layout resizing parent without resizing the childsandroid布局调整父级大小而不调整子级大小
【发布时间】:2014-07-14 09:52:28
【问题描述】:

TLDR 版本:

当我调整父级的大小时:

解释:

在一个 android 应用程序中,我有一个父布局,其中有多个相对布局。

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

这个布局的孩子有一些文字和图片。我正在做的事情如下:

当用户点击一个按钮时,我想折叠父线性布局(动画宽度直到它为 0)。我已经实现了这个功能:

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);
}

我在父视图上调用上述函数。 这工作正常,并且父视图正在调整宽度直到它达到 0,然后我将他的可见性设置为消失。

我的问题

当父视图重新调整大小(折叠)时,子视图也随之调整大小。我的意思是,当父元素的宽度达到其中一个子元素时,子元素也会随之重新调整大小,并导致其中的文本视图也重新调整大小。

以下是一些截图示例:

这里你可以看到初始布局,有绿色的父布局,以及子布局(带有日期、文本和图标)。

然后当我开始减小父布局的大小时,子布局的大小也会受到影响,它会减小其中的 textview 的大小,导致文字换行,如下图 2 所示:

您注意到,随着父项的宽度减小,子项中的文本被换行。

有没有办法让子元素不随父视图调整大小,但保持原样,即使父视图大小正在减小?我需要 chil 元素的大小在整个调整大小动画期间保持固定,并被父级剪切而不是调整大小。

非常感谢您的帮助

【问题讨论】:

    标签: android android-layout width android-animation


    【解决方案1】:

    您的问题来自几个问题。

    • firstsingleLine="false" 上的参数 TextViews > 使用此参数,您不能要求它换行其内容,因为文本可以使用多行,因此没有适当的宽度。如果有不止一行,它将填充其父级。
    • second:“wrap_content”的行为是适应内容大小,除非它大于父级。在这种情况下,它将再次匹配父大小。 (我们可以从 padding 或 margin 的移动看出,边框是跟随父尺寸的。)

    第二个问题可以通过使用固定大小来解决,但第一个问题不是这样:

    您可以将singleLine="true" 设置为android:width="wrap_content" 以查看它在单行上运行。 (设置android:ellipsize="none" 以隐藏'...' 移动) 但是单行文本不是您需要的吗?

    我已经使用 TextView 的扩展进行了测试,以避免这些视图调整大小,即使是多行:

    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);
            }
        }
    
    }
    

    然后在你的布局中使用这个 TextView 扩展:

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

    这不是很漂亮,我不建议过多使用它,因为它可能会破坏 android 的布局机制。但在你的特殊情况下,我想它可以完成这项工作。

    逻辑:

    当您扩展视图以创建自定义视图时,您负责实现onMeasure。这就是为您的视图赋予其大小的原因。这就是它的工作原理:你计算一次大小(或者让默认函数为你做,thanx to getMeasuredW/H)并保存它,然后在每次请求大小时,你只返回相同的值,这样你的视图大小就不会了不改变。

    限制:

    您的视图大小不会改变,当更改屏幕方向/父大小或再次更改屏幕方向/父大小时,它可能会产生不良行为,当里面的文本发生变化时(正如维克拉姆所说)。 这就是我所说的“打破 android 布局机制”。 如果您需要动态更改文本,则必须扩展 setText 方法以允许它此时调整大小...

    【讨论】:

    • 成功了。谢谢。但是你能解释一下它背后的逻辑,为什么它很危险(可能会破坏android的布局机制)
    • 动态更改文本时会怎样?
    • 这非常适合我的情况。所以我会接受这个作为答案和赏金。您没有义务,但是如果您有时间,最好添加一些代码来处理在 setText 中动态设置文本以允许此时调整大小的情况。以防有人稍后需要它并且正在阅读这个问题。谢谢
    【解决方案2】:

    &lt;Edit&gt; 支持要求(见 cmets):

    据我所知,以下是您所追求的。支持您的要求所需的一项更改是使用 FrameLayout 作为父容器。

    现在的布局是:

    <?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 代码:

    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();
                }
            }
        });
    }
    

    &lt;/Edit&gt;

    其实这很简单。来看看吧:

    您只需要以下内容:

    // 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();
    

    如果您的应用的minSdkVersion 低于 11,请使用 NineOldAndroids 库支持 > api 8。

    我正在使用一个相当简单的布局:

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

    动画的视图组是rlToAnimate

    【讨论】:

    • 您好,动画播放正常,但布局未调整大小。因此,如果它之后有一个布局(在线性布局中),它将无法正确调整到第一个折叠的布局。还是谢谢
    • @Youssef 你的意思是viewgroup的右边是否有布局被折叠?
    • 是的,正确。我将它们添加到线性布局中,因此如果右侧有布局,它将留在他的位置。
    • @Youssef 你需要一个FrameLayout 作为父容器才能工作。请参阅上面的编辑。
    【解决方案3】:

    您可以使用 ImageView 作为布局上方的叠加层,因此缩小布局,您只是用黑色背景的 ImageView 覆盖布局,该黑色背景将从左侧滑动,完成后您可以将everytihng 设置为消失了。

    1. 有一个具有匹配父属性和可见性的 ImageView。
    2. 当用户单击按钮时,将 ImageView 的可见性设置为可见并设置动画以从左侧滑入。
    3. OnAnimationEnd 将视图设置为 ImageView 和其他布局,或根据需要调整布局大小。

    【讨论】:

      【解决方案4】:

      我过去处理过相同的要求,这就是我解决它的方法,我相信这是实现它的最简单最灵活的方法。这是经过测试并与您的 xml 和动画代码一起使用的。 两个简单的步骤应该可以解决问题:

      1. 用 FrameLayout 围绕您的父布局 - 这将是您实际调整大小的布局。
      2. 在进行任何实际调整大小之前,我们需要通过为 R.id.parent 布局提供固定大小(以像素为单位)来强制其保持恒定大小。在运行时,布局通过后,我们更新 layoutParams 以设置固定大小。我们在布局传递之后执行此操作,因此我们知道我们设置的固定大小是基于系统对所需大小的计算(即在完成所有匹配父/包装内容布局计算之后)。

      你的布局应该是这样的(刚刚添加了 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>
      

      现在您需要在活动的 onCreate 方法中的代码上添加几行(或者其他地方,如果根据您的代码更有意义的话。但可能是 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);
                      }
                  }
              }
          });
      

      请注意,您正在调整大小的视图是 FrameLayout (R.id.parentContainer) - 虽然此视图随着动画而变小,但布局 R.id.parent 将保持其大小不变。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多