Android FrameLayout儿童负边距奇效

时间:2015-07-23 03:20:34

标签: android android-layout android-animation android-view android-viewgroup

我制作了一个自定义View,用于在UI中显示反馈(通常是为了响应正在采取的操作)。调用FeedbackView.showText时,它会为View设置动画2秒,然后将其设置为动画。这是使用translationY完成的。

如果我在第一次调用FeedbackView.showText时对其应用的负边距大于其高度,则它不会正确显示动画;它只是出现(或在某些情况下根本不显示)。对FeedbackView.showText的后续调用会导致正确的动画。

activity_main.xml下方,FeedbackView的上边距为-36dp,大于其高度(非取消时)。如果边距顶部更改为-35dp,即使第一次调用FeedbackView.showText,它也会正确设置动画。

有谁知道为什么会发生这样的事情?

Romain Guy说it is OK to use negative margins on LinearLayout and RelativeLayout。我唯一的猜测是他们对FrameLayout没有问题。

FeedbackView.java

public class FeedbackView extends FrameLayout {
  public static final int DEFAULT_SHOW_DURATION = 2000;

  private AtomicBoolean showing = new AtomicBoolean(false);
  private AtomicBoolean animating = new AtomicBoolean(false);

  private float heightOffset;

  private Runnable animateOutRunnable = new Runnable() {
    @Override
    public void run() {
      animateContainerOut();
    }
  };

  public FeedbackView(Context context) {
    super(context);
    init();
  }

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

  public FeedbackView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public FeedbackView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init();
  }

  private void init() {
    setAlpha(0);
  }

  public boolean isShowing() {
    return showing.get();
  }

  public void showText(Context context, String text) {
    removeCallbacks(animateOutRunnable);

    heightOffset = getMeasuredHeight();

    removeAllViews();

    final TextView tv = new TextView(context);
    tv.setGravity(Gravity.CENTER);
    tv.setTextColor(Color.WHITE);
    tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
    tv.setText(text);

    addView(tv);

    if(!showing.getAndSet(true)) {
      animateContainerIn();
    }
    else {
      tv.setTranslationY(-getHeight());
      tv.animate().translationY(0).start();
    }

    postDelayed(animateOutRunnable, DEFAULT_SHOW_DURATION);
  }

  private void animateContainerIn() {
    if(animating.getAndSet(true)) {
      animate().cancel();
    }

    ViewPropertyAnimator animator = animate();
    long startDelay = animator.getDuration() / 2;

    animate()
        .alpha(1)
        .setStartDelay(startDelay)
        .start();

    animate()
        .translationY(heightOffset)
        .setStartDelay(0)
        .withEndAction(new Runnable() {
          @Override
          public void run() {
            animating.set(false);
            showing.set(true);
          }
        })
        .start();
  }

  private void animateContainerOut() {
    showing.set(false);

    if(animating.getAndSet(true)) {
      animate().cancel();
    }

    ViewPropertyAnimator animator = animate();
    long duration = animator.getDuration();

    animate()
        .alpha(0)
        .setDuration(duration / 2)
        .start();

    animate()
        .translationY(-heightOffset)
        .setDuration(duration)
        .withEndAction(new Runnable() {
          @Override
          public void run() {
            animating.set(false);
          }
        })
        .start();
  }
}

MainActivity.java

public class MainActivity extends Activity {
  private FeedbackView feedbackView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    feedbackView = (FeedbackView) findViewById(R.id.feedback);

    findViewById(R.id.show_feedback).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        feedbackView.showText(MainActivity.this, "Feedback");
      }
    });
  }
}

activity_main.xml中

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

  <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="70dp"
    android:background="#000"
    android:clickable="true"/>

  <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <FrameLayout
      android:layout_width="match_parent"
      android:layout_height="90dp"/>

    <FrameLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:clickable="true"
      android:background="#e9e9e9"/>

    <negative.margin.FeedbackView
      android:id="@+id/feedback"
      android:layout_width="match_parent"
      android:layout_height="35dp"
      android:layout_marginTop="-36dp"
      android:background="#20ACE0"/>

  </FrameLayout>

  <Button
    android:id="@+id/show_feedback"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|center"
    android:text="Show Feedback"/>

</LinearLayout>

1 个答案:

答案 0 :(得分:0)

我的猜测是负面margin不是导致动画失败的直接原因。

你可能会达到相同的(不受欢迎的)效果 - 动画没有被执行 - 如果你设置例如:layout_marginLeft到等于Activity width的值(所以是积极的值)。

问题在于,您的View完全位于可见区域的“外部”,因此在创建Activity时,View不会立即呈现。

更多信息(例如)here

你可以做些什么来解决它:

  • View在渲染区域内的方式重建布局(因此基本上在可见区域内),但其View设置为visibility。在动画开始时(使用View.INVISIBLEAnimationListener或其他内容;))将其AnimatorListener设置为visibility

  • 重建动画,使其不使用View.VISIBLEViewPropertyAnimator方法调用),而是使用animate() Animation。然后在另一个Object(在您确定已经渲染的那个)上启动它 - 例如在View View's上(您可以使用ViewParent获得)

  • 您可以尝试(我的胆量告诉我应该有效,但您需要对其进行测试)将布局getParent()clipChildren设置为{ {1}},强制您的视图即使在可见区域之外也能呈现。如果您尝试使用该解决方案(我认为您应该这样做,因为您不必更改那么多 - 只需将clipToPaddingfalse添加到此android:clipChildren="false"中的所有android:clipToPadding="false"请告诉我它是否有效。