首次运行时布局动画无法正常工作

55
我有一个带有项目列表的Activity,当您单击一个项目时,我希望该项目的播放控件从屏幕底部向上滑动并变得可见。我已经为滑入和滑出定义了一个动画集,并且它们有效。我在我的Activity中设置了动画监听器,并在单击项目时启动了滑入动画。我的问题是,第一次运行应用程序时,当我单击项目时,onClick回调被执行,但动画不会发生。第二次单击时,滑入动画发生了,但滑出动画没有发生。第三次及以后,它按预期工作。这是我的动画集。
vm_slide_in.xml
    <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" 
           android:fillAfter="true">
    <translate
        android:fromYDelta="800"
        android:toYDelta="0"
        android:duration="600" />
</set>

vm_slide_out.xml

:这是一个XML文件名,与VM滑动动画相关。

    <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" 
           android:fillAfter="true">
    <translate
        android:fromYDelta="0"
        android:toYDelta="800"
        android:duration="600" />
</set>

这是我的活动布局

    <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    style="@style/AppBG">
    <RelativeLayout 
        style="@style/LogoBar" 
        android:id="@+id/logo_bar"      
        android:layout_alignParentTop="true">
        <include layout="@layout/logobar"></include>
    </RelativeLayout>
    <RelativeLayout 
        style="@style/TitleBar" 
        android:id="@+id/title_bar"     
        android:layout_below="@+id/logo_bar">
        <include layout="@layout/titlebar"></include>"
        <Button style="@style/TitleBarButton"
            android:id="@+id/vm_speaker_btn"
            android:text="@string/vm_speaker_btn_label" 
            android:layout_alignParentLeft="true"
            android:layout_margin="4dp">
        </Button>
        <Button style="@style/TitleBarButton"
            android:id="@+id/vm_edit_btn"
            android:text="@string/vm_edit_btn_label" 
            android:layout_alignParentRight="true"
            android:layout_margin="4dp">
        </Button>
    </RelativeLayout>
    <ListView
        android:id="@+id/@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/title_bar"/>
    <RelativeLayout
        android:id="@+id/vm_control_panel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/footer"
        android:visibility="gone">
        <include layout="@layout/vm_control"/>
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/footer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">
        <include layout="@layout/taskbar"/>
    </RelativeLayout>
</RelativeLayout>

这是所包含控件的布局

    <?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <RelativeLayout 
        android:id="@+id/vm_control"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@color/dark_grey">
        <TextView
            android:id="@+id/vm_ctl_branding"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="5dp"
            android:text="@string/branding"
            android:textColor="@color/white">
        </TextView>
        <TextView style="@style/TimeMark"
            android:id="@+id/vm_timestamp"
            android:layout_toLeftOf="@+id/vm_progress"
            android:layout_below="@+id/vm_ctl_branding"
            android:layout_marginRight="3dp"
            android:text="0:00">
        </TextView>
        <SeekBar
            android:id="@+id/vm_progress"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:layout_below="@+id/vm_ctl_branding"
            android:layout_centerHorizontal="true">
        </SeekBar>
        <TextView style="@style/TimeMark"
            android:id="@+id/vm_timeleft"
            android:layout_toRightOf="@+id/vm_progress"
            android:layout_below="@+id/vm_ctl_branding"
            android:layout_marginLeft="3dp"
            android:text="-0:00">
        </TextView>
        <LinearLayout
            android:id="@+id/vm_action_button_layout"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/vm_timestamp"
            android:layout_alignRight="@+id/vm_timeleft"
            android:orientation="horizontal"
            android:layout_below="@+id/vm_progress">
            <TextView style="@style/Vm_Action_Button"
                android:background="@drawable/vm_action_btn_call"
                android:id="@+id/vm_callback_btn"
                android:layout_marginRight="5dp"
                android:text="@string/vm_action_btn_callback">
            </TextView>
            <TextView style="@style/Vm_Action_Button"
                android:background="@drawable/vm_action_btn_delete"
                android:id="@+id/vm_delete_btn"
                android:layout_marginLeft="5dp"
                android:text="@string/vm_action_btn_delete">
            </TextView>
        </LinearLayout>
    </RelativeLayout>
</merge>
从我的onCreate方法开始...
// Handle list item selections
ListView lv = getListView();
lv.setOnItemClickListener(new OnItemClickListener() {
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        final Voicemail vmItem = (Voicemail) vmla.getItem(position);
        Toast.makeText(ctx, "Name: " + vmItem.getCallerName() + " Position: " + position, Toast.LENGTH_SHORT)
                .show();
        vVm_controls.startAnimation(mSlideIn);
    }
});

我的动画回调函数...

@Override
public void onAnimationStart(Animation animation) {
    vm_controls_in = !vm_controls_in;
    if (vm_controls_in) {
        vVm_controls.setVisibility(View.GONE);
    } else {
        vVm_controls.setVisibility(View.VISIBLE);
        // vVm_controls.bringToFront();
    }
}

@Override
public void onAnimationEnd(Animation animation) {
    if (!vm_controls_in) {
        try {
            Thread.sleep(1000);
            vVm_controls.startAnimation(mSlideOut);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

@Override
public void onAnimationRepeat(Animation animation) {
    // TODO Auto-generated method stub

}
11个回答

142

正如你发现的那样,这个问题是由于视图不可见导致的。当一个视图在xml文件中最初被设置为“gone”时,安卓系统将不会渲染该布局,直到其可见性被更改为“visible”或“invisible”。如果你尝试在一个未被渲染的视图上执行动画,动画将在没有布局的情况下出现在该视图上。等到动画完成后,它才会渲染布局,突然间这个视图就出现了,但没有动画效果。之后再次执行时它能正常工作,是因为即使设置为“gone”,该视图仍然有布局。

关键在于,在xml文件中将视图的可见性设置为“invisible”,而不是“gone”,这样它就会被渲染但隐藏。当第一次执行动画时,该视图会出现并按预期移动。


4
这个解决方案在我的情况下不起作用。有没有其他方法手动呈现视图? - suresh cheemalamudi
1
@sureshcheemalamudi 没有更多的信息很难提供帮助,因为您的动画代码可能存在问题。此外,这个答案是基于API 11(蜂巢)编写的。请提出一个新问题,并提供您的具体问题细节,希望有人能够提供答案。 - mindriot
1
有时候,“不可见”的东西并不理想,因为它可能会阻止触摸事件。 :( - Elye
它对我不起作用。我的视图是在一个ViewStub中,我想让我的视图滑入和滑出。第一次我填充viewStub并希望其中嵌套的RecyclerView滑入,但它只是出现没有任何动画。我没有设置任何可见性,这在随后的时间内有效。 - Allen Vork
我必须最初将其设置为VISIBLE。然后在OnCreate中将其设置为INVISIBLE。然后第一次正确执行动画。 如果我以其他顺序设置可见性,它就不起作用。 - Ernesto Vega
显示剩余2条评论

12
当然,在发布了这个问题20分钟之后,我就解决了自己的问题。由于我将布局的可见性设置为“GONE”,因此当我尝试在其上启动动画时,第一次不会触发。不确定为什么第二次及以后会生效,但如果我在启动动画之前将可见性设置为“VISIBLE”,就可以解决问题。下面是我修改后的代码...
// Handle list item selections
ListView lv = getListView();
lv.setOnItemClickListener(new OnItemClickListener() {
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        final Voicemail vmItem = (Voicemail) vmla.getItem(position);
        Toast.makeText(ctx, "Name: " + vmItem.getCallerName() + " Position: " + position, Toast.LENGTH_SHORT)
                .show();
        vVm_controls.setVisibility(View.VISIBLE);
        vVm_controls.startAnimation(mSlideIn);
    }
});

1
你的解决方案比“将可见性设置为不可见”更好。 - Bobs
不是这样的,因为你还要担心视图的位置,以便用户不会看到跳动。如果动画从视图的位置开始,那么就没问题了,但如果动画被带到视图的位置(或其他偏移动画),那就不行了。 - Ben Kane

6

我不知道这个方法是否还有效,但是它与可见性有关。因此,我将xml文件中的可见性设置为不可见,并在视图初始化的位置调用:

view.post(new Runnable() {
    @Override
    public void run() {
        view.animate().translationY(view.getHeight());
    }
};

现在是调用函数的时候。
view.animate().translationY(0).setListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationStart(Animator animation) {
        super.onAnimationStart(animation);
        view.setVisibility(View.VISIBLE);
    }
}

第一次也可以正常工作。


3
如果可见性是问题,您只需要延迟动画渲染后的时间,例如:

vVm_controls.setVisibility(View.INVISIBLE);
vVm_controls.post(() -> vVm_controls.startAnimation(mSlideIn));

3

虽然我在动画开始时将视图设置为可见,但我仍然遇到了完全相同的问题。我遵循brockoli的建议,在启动动画之前将其设置为可见,并且它像魅力一样工作。

之前:

Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_left);
animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        view.setVisibility(View.VISIBLE);
    }

    @Override
    public void onAnimationEnd(Animation animation) { }

    @Override
    public void onAnimationRepeat(Animation animation) {       }
);
view.startAnimation(animation);

之后:

Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_left);
view.setVisibility(View.VISIBLE);
view.startAnimation(animation);

3
许多当前的答案对我不起作用,唯一有用的是使用onFocusWindowChange(),但我不喜欢那个解决方法。
我的解决方法:首先,在xml布局中将你想要动画的当前view设为invisible
接下来,在onCreate中向布局添加监听器,以便在view完成绘制时通知你:
mLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mLayout.setVisibility(View.GONE);
                mLayout.animate()
                        .translationX(-mLayout.getWidth());
                        
                mLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });

它在完成渲染后将其移出屏幕

当重新显示布局时,您必须将布局的起始宽度返回。 - MeLean

2

我在Android 4.4.3中遇到了类似的问题。我尝试了大部分提出的解决方案,但只有一种解决方案对我有效。

你必须在窗口完成渲染后运行动画,最好的方法是在onWindowFocusChanged内运行它。不要通过runnable使用延迟,因为它会影响高速手机的应用程序性能。

@Override
public void onWindowFocusChanged (boolean hasFocus) {
   super.onWindowFocusChanged(hasFocus);
   if (hasFocus)
      yourView.startAnimation(anim);
}

更多信息可以在这篇文章中找到:https://damianflannery.wordpress.com/2011/06/08/start-animation-in-oncreate-or-onresume-on-android/

该文章与Android上的onCreate或onResume中启动动画有关,详细内容请参考链接。

这很好,不过如果你想让你的对象只动画一次,你可能需要检查窗口是否是第一次聚焦。 - marticztn

1
如果您使用RecyclerView并在设置适配器后加载列表项,则请使用此方法。
recyclerView.scheduleLayoutAnimation();

之后

adapter.notifyDataSetChanged();

这对我的情况有效


0

我曾经遇到过完全相同的问题,但是通过使用 ViewPropertyAnimator 解决了。例如,您声明了一个名为 mGridViewGridView。现在,您只需要调用 mGridView.animate() 并进行所需的更改即可。

我的 GridView 的动画被触发了,但由于未知的情况下没有实际的动画可见。我还将所有的 View.GONE 更改为 View.VISIBLE,但这并没有改变任何东西。在我的情况下,我发现我只需要在特定的 GridView 的 XML 中删除 android:requiresFadingEdge="vertical"。可能无法在使用 Fading Edge 与 ViewPropertyAnimator 结合使用。


0

整个SO中的答案都对我没用,即使根本没有使用Visibility.GONE。

所以我最终采取了一种权宜之计,将来有机会再去仔细研究。

我暂时的解决方案是将高度设置为1像素(而不是0dp,因为这样在我的情况下无法工作),同时将可见性设置为INVISIBLE。虽然我会失去1像素,但相当难以注意到。

代码:

ResizeAnimation.kt

class ResizeAnimation(private val view: View, private val newHeight: Int) : Animation() {
    private val initialHeight: Int = view.layoutParams.height
    private val deltaHeight: Int

    init {
        deltaHeight = newHeight - initialHeight
    }

    override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
        view.layoutParams.height = (initialHeight + deltaHeight * interpolatedTime).toInt()
        view.requestLayout()
    }

    override fun willChangeBounds(): Boolean {
        return true
    }
}

使用方法:

private fun displayProgressBar() {
        progressBar.visibility = View.VISIBLE
        val animation = ResizeAnimation(progressBar, 78).apply {
            duration = inAnimationDuration
        }
        progressBar.startAnimation(animation)
    }

private fun removeProgressBar() {
        val animation = ResizeAnimation(progressBar, 1).apply {
            duration = outAnimationDuration
        }
        progressBar.startAnimation(animation)
        progressBar.visibility = View.INVISIBLE
    }

请不要把这个当作最终解决方案,而是把它当作一个权宜之计。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接