在活动第一次启动时,自定义视图动画不够流畅。

5

我是一名有用的助手,可以为您翻译文本。

我有一个自定义视图,在以下情况下执行动画:

  1. Activity首次启动。
  2. Action Bar下拉导航中选择更改。

代码如下:

DividendBarChartFragment.java

public class DividendBarChartFragment extends SherlockFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.dividend_bar_chart_fragment, container, false);

        // barChartCompositeViewByYear is custom view.
        this.barChartCompositeViewByYear = (BarChartCompositeView)v.findViewById(R.id.bar_chart_composite_view_by_year);

        final ViewTreeObserver viewTreeObserver0 = this.barChartCompositeViewByYear.getViewTreeObserver();

        // Only perform animation when view is ready?!
        viewTreeObserver0.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            @SuppressLint("NewApi")
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    DividendBarChartFragment.this.barChartCompositeViewByYear.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                } else {
                    DividendBarChartFragment.this.barChartCompositeViewByYear.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }

                DividendBarChartFragment.this.barChartCompositeViewByYear.animateCurrentBarHeight();
            }

        });  

我希望只有在Fragment准备好后才开始动画(animateCurrentBarHeight)。

我使用了addOnGlobalLayoutListener。然而,如您在视频中所见,似乎动画甚至发生在Fragment对用户可见之前。

https://www.youtube.com/watch?v=87_DOuZw88w&feature=youtu.be

如果我仅在ActivityonNavigationItemSelected中执行动画(使用相同的动画代码,即animateCurrentBarHeight),则效果会更加流畅自然。

https://www.youtube.com/watch?v=yvJqtOSKKok&feature=youtu.be

请问,当活动首次启动时,最好的时间触发动画代码,以使动画出现自然而流畅?

animateCurrentBarHeight的代码

public void animateCurrentBarHeight() {
    PropertyValuesHolder barHeightScalePropertyValuesHolder = PropertyValuesHolder.ofFloat("barHeightScale", barHeightScale, 1.0f);        
    ValueAnimator valueAnimator = ObjectAnimator.ofPropertyValuesHolder(this, barHeightScalePropertyValuesHolder);
    valueAnimator.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
    valueAnimator.setRepeatCount(0);
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.start();        
}

阅读所有建议回答后的最终答案

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.dividend_bar_chart_fragment, container, false);

    // barChartCompositeViewByYear is custom view.
    this.barChartCompositeViewByYear = (BarChartCompositeView)v.findViewById(R.id.bar_chart_composite_view_by_year);

    final ViewTreeObserver viewTreeObserver0 = this.barChartCompositeViewByYear.getViewTreeObserver();

    viewTreeObserver0.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @SuppressLint("NewApi")
        @Override
        public void onGlobalLayout() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                DividendBarChartFragment.this.barChartCompositeViewByYear.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            } else {
                DividendBarChartFragment.this.barChartCompositeViewByYear.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }

            final int activityTransitionDuration = getResources().getInteger(android.R.integer.config_mediumAnimTime);
            final BarChartCompositeView barChartCompositeView = DividendBarChartFragment.this.barChartCompositeViewByYear;

            // Key to the solution!
            barChartCompositeView.postDelayed(new Runnable() {
                @Override
                public void run() {
                    barChartCompositeView.animateCurrentBarHeight();
                }
            }, activityTransitionDuration);
        }

    });        

当您使用onPreDraw()监听器而不是onGlobalLayout监听器时会发生什么? - Alex Lockwood
那么观察事件= windowfocus changed,然后分派您的动画怎么样?请参见此处接受的答案:https://dev59.com/YWw05IYBdhLWcg3w41wp - Robert Rowntree
@AlexLockwood 使用 onPreDraw 似乎并没有什么不同。动画仍然不够流畅。 - Cheok Yan Cheng
是的,我也不这么认为...但还是值得一试。通常情况下,在动画方面,onPreDraw()onGlobalLayout监听器更受欢迎,因为它在绘制第一帧显示框之前立即调用。 :) - Alex Lockwood
3个回答

5
我不相信有一种方法可以设置活动转换的结束监听器,因此一个可能的解决方案是简单地在动画上设置起始延迟。这将确保动画不会在第二个活动仍在动画/淡入屏幕时开始。
可以通过调用以下方式获取默认的活动转换持续时间:
getResources().getInteger(android.R.integer.config_mediumAnimTime);

谢谢。你怎么知道config_mediumAnimTime是Activity转换的默认延迟时间?我通过谷歌搜索,但找不到相关信息。 - Cheok Yan Cheng
源代码中定义了一堆动画在这里。例如,看一下task_open_enter.xml - Alex Lockwood

2
一般来说,动画可以通过UI线程上的消息进行控制。在不知道视图具体实现方式的情况下,我建议尝试使用post方法,当所有当前消息都已发送时,运行一个可运行的任务。
不要使用...animateCurrentBarHeight();,而是:
    new Handler().post(new Runnable(){
        @Override
        public void run() {
            DividendBarChartFragment.this.barChartCompositeViewByYear.animateCurrentBarHeight();
        }
    });

2
除非使用postDelayed,否则这并没有帮助。 - Cheok Yan Cheng
谢谢你的努力和完整性。但是请注意,正如Cheok在上面所说,仅使用post()是没有任何效果的,您必须至少使用postDelayed并设置50毫秒。(在Nexus 5和Galaxy S3上测试过) - Odaym

0

很难确定是什么导致了卡顿(在我肉眼观察的情况下,第二个视频中的1个动画似乎有几乎同样的卡顿),因此这是一个理论上的答案,最坏的情况下应该会使事情变得更好。

首先,就我个人而言,在仅仅启动一个活动时使用ViewTreeObserver方法并没有太大的价值。如果您正在通过启动Activity来执行生命周期,则在适当的生命周期事件中使用它将更有意义。为了确切地了解当前配置中发生了什么,建议查看ActivityFragment生命周期(特别是生命周期流程图)。

根据您想要实现的功能,您希望动画尽可能晚地在显示活动的生命周期中发生。通过尝试在onGlobalLayout()事件中执行动画,您正在尝试在布局操作完成时执行动画。在Android中,布局操作和绘图(更不用说显示在屏幕上了)是非常不同的操作。例如,基本的Viewlifecycle(您基本上是通过onGlobalLayout()监听器来观察它)会经历onMeasure()onLayout()onDraw()。基于此,您试图在View甚至还没有被绘制到屏幕上之前执行动画(这个结论是基于一些简化的假设,但总体意思仍然成立)。

在活动创建事件的onResume()方法中执行此动画会更有意义,无论是在您的Activity还是Fragment中(取决于您的应用程序设置-但看起来Fragment.onResume()方法比较合适)。这样做的副作用是,每当用户重新打开您的应用程序时,动画都会发生。如果这不是您想要的,您可以在onCreate()方法中设置一个标志,以确保仅运行一次动画。或者您也可以查看onStart(),因为这可能是一个更好的位置,具体取决于您的应用程序结构。以下是最基本解决方案的示例代码:

public class DividendBarChartFragment extends SherlockFragment {
    // ... existing code ...

    @Override
    public void onResume() {
        super.onResume();
        DividendBarChartFragment.this.barChartCompositeViewByYear.animateCurrentBarHeight();
    }
}

这是你在Activity/Fragment生命周期中能够访问到的最晚时间(你可以到Activity.onPostResume(),但这不是必要的,也不建议在方法文档中这样做)。如果这不能解决问题,那么你的下一个选择是仍然在这里启动你的动画,但包括一个延迟来启动它。这要弱得多,但应该保证能起作用。鉴于你没有提供animateCurrentBarHeight()函数的实现,我只能建议你扩展它以接受一个延迟int参数并进行相应调整(Animation.startOffset()是你可以根据你执行动画的方式之一实现这个目的)。在onResume()中加入延迟的代码仍然基本相同:

public class DividendBarChartFragment extends SherlockFragment {
    // ... existing code ...

    @Override
    public void onResume() {
        super.onResume();
        int delayMS = 1000;
        DividendBarChartFragment.this.barChartCompositeViewByYear.animateCurrentBarHeight(delayMS);
    }
}

如果这不能解决卡顿问题,如果您能提供您正在执行动画的代码,那将非常有帮助。

使用onResume并不能解决问题。请注意,onGlobalLayout事件在onResume之后发生。因此,我认为更好的触发动画的位置是在onGlobalLayout中。 - Cheok Yan Cheng

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