0

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

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

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

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

Romain Guy 说过在上使用负边距是可以的LinearLayoutRelativeLayout。我唯一的猜测是他们对FrameLayouts 不满意。

反馈视图.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>
4

1 回答 1

0

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

您可能会获得相同的(不希望的)效果 - 不执行动画 - 如果您设置例如:layout_marginLeft等于的值Activity width(所以是一个正值)。

问题是您View完全在可见区域“外部”,因此当您Activity创建时,View并没有立即渲染。View并且在尚未渲染的 a 上运行动画将不会执行。

更多信息(例如)在这里

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

  • 以您在渲染区域内的方式重建布局View(因此基本上在可见区域内),但其visibility设置为View.INVISIBLE. 在动画开始时(使用AnimationListenerorAnimatorListener或其他东西;))将其设置visibilityView.VISIBLE.

  • 重建您的动画,使其不使用ViewPropertyAnimatoranimate()方法调用),而是使用Animation Object. 并在不同的地方启动它View(在你确定已经渲染过的地方) - 例如在View's ViewParent(你可以用getParent())上。

  • 您可以尝试(我的直觉告诉我这应该可行,但您需要对其进行测试)设置您的布局clipChildrenclipToPaddingto false,即使在可见区域之外也强制渲染您的视图。如果您尝试该解决方案(我认为您应该这样做,因为您不必进行太多更改 - 只需将android:clipChildren="false",添加android:clipToPadding="false"到您的所有内容layoutsActivity)请告诉我它是否有效。

于 2015-07-23T08:07:27.360 回答