【问题标题】:3 pane layout fragment animation is stuttering3 窗格布局片段动画卡顿
【发布时间】:2013-02-02 19:12:09
【问题描述】:

实施信息: 根据 CommonsWare 对他自己的问题的回复,我已经实现了一个 3 窗格布局:Complete Working Sample of the Gmail Three-Fragment Animation Scenario?

一般来说,我的布局由以下级别(1 到 3)组成:

  1. MainActivity
  2. SlidingMenu(Side Drawer UI 模式)片段隐藏在左侧,ContentFragment 作为容纳 3 窗格布局的片段。
  3. ContentFragment 内部:LeftListFragment(每行有 3 个 TextView)、MiddleListFragment(每行有 8 个 TextView)、DetailFragment

LeftListFragmentMiddleListFragment 使用 CursorLoaders 从每个列表中的 ContentProvider 加载数据。 DetailFragment 还会在需要时调用带有数据的游标。所以我什至没有实现自定义Adapters(这样设计更好)。然后我添加了 3 窗格布局 + 动画。就工作而言,它按预期工作,没有问题。 Animation 时间为 500 毫秒。

问题: 动画有点卡顿。一些丢帧。当左侧和中间都可见并且我单击中间列表项以打开详细信息时;以及当我点击返回按钮再次查看左侧和中间列表时(实际上没有加载任何内容)。

我的尝试:

  1. 删除了在DetailView 中加载片段的代码。我只需点击MiddleFragment 中的一个项目,动画就开始了,实际上没有加载任何细节。还是口吃。此外,当回击时,没有任何加载并且仍然卡顿,所以我认为加载器/光标不是造成这种情况的原因。
  2. 我使用 dumpsys gfxinfo 查看计算每一帧的平均时间(以毫秒为单位)。实际上,计算的平均时间是 18 毫秒,高于 16 毫秒的阈值。那么这是否意味着口吃是因为在制作动画时再次绘制列表需要时间?如果是这样,为什么?我的意思是......我在行视图中根本没有任何图像。而且我无法搞砸适配器代码,因为我还没有编写任何代码......
  3. Animation 时间从 500 毫秒减少到 200 毫秒。如果你仔细观察,它仍然会卡顿,只是速度更快。

编辑:我从下面的rightPaneWidth 切换到leftPaneWidth(是的,这消除了重新调整尺寸的动画),现在口吃消失了。该列表仍向左侧滑动,但宽度并没有变小。因此,如果现在不再出现口吃,这是否意味着我的代码中的 ObjectAnimator 存在问题?

ObjectAnimator.ofInt(this, "middleWidth", rightPaneWidth, leftPaneWidth)
                    .setDuration(ANIM_DURATION).start();

感谢您的宝贵时间!

3 窗格布局代码:

package com.xyz.view.widget;

import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.LinearLayout;


public class ThreePaneLayout extends LinearLayout
{
    private View leftView = null;
    private View middleView = null;
    private View rightView = null;

private static final int ANIM_DURATION = 500;
private int leftPaneWidth = -1;
private int rightPaneWidth = -1;


// -------------------------------------------------------------------------------------------
// --------------   Constructor
// -------------------------------------------------------------------------------------------


public ThreePaneLayout(Context context, AttributeSet attrs)
{
    super(context, attrs);
    setOrientation(HORIZONTAL);
}

@Override
public void onFinishInflate()
{
    super.onFinishInflate();
    leftView = getChildAt(0);
    middleView = getChildAt(1);
    rightView = getChildAt(2);
}


// -------------------------------------------------------------------------------------------
// --------------   Public methods
// -------------------------------------------------------------------------------------------


public View getLeftView()
{
    return leftView;
}

public View getMiddleView()
{
    return middleView;
}

public View getRightView()
{
    return rightView;
}

@SuppressLint("NewApi")
public void hideLeft()
{
    if (leftPaneWidth == -1)
    {

        leftPaneWidth = leftView.getWidth();
        rightPaneWidth = middleView.getWidth();
        resetWidget(leftView, leftPaneWidth);
        resetWidget(middleView, rightPaneWidth);
        resetWidget(rightView, rightPaneWidth);
        requestLayout();
    }
    translateWidgets(-1 * leftPaneWidth, leftView, middleView, rightView);
    ObjectAnimator.ofInt(this, "middleWidth", rightPaneWidth, leftPaneWidth)
                    .setDuration(ANIM_DURATION).start();
}

@SuppressLint("NewApi")
public void showLeft()
{
    translateWidgets(leftPaneWidth, leftView, middleView, rightView);
    ObjectAnimator.ofInt(this, "middleWidth", leftPaneWidth, rightPaneWidth)
                    .setDuration(ANIM_DURATION)
                    .start();
}


// -------------------------------------------------------------------------------------------
// --------------   Private methods
// -------------------------------------------------------------------------------------------


private void setMiddleWidth(int value)
{
    middleView.getLayoutParams().width = value;
    requestLayout();
}

@TargetApi(12)
private void translateWidgets(int deltaX, View... views)
{
    for (final View view : views)
    {
        ViewPropertyAnimator viewPropertyAnimator = view.animate();
        viewPropertyAnimator.translationXBy(deltaX)
                            .setDuration(ANIM_DURATION);
    }
  }

  private void resetWidget(View view, int width)
  {
      LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)view.getLayoutParams();  
      layoutParams.width = width;
      layoutParams.weight = 0;
  }
}

ContentFragment 的 XML:

    <?xml version="1.0" encoding="utf-8"?>
<com.xyz.view.widget.ThreePaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_content_three_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

<FrameLayout
    android:id="@+id/fragment_content_framelayout_left"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="3" />

<FrameLayout
    android:id="@+id/fragment_content_framelayout_middle"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="7" />

<FrameLayout
    android:id="@+id/fragment_content_framelayout_right"
    android:layout_width="0dp"
    android:layout_height="match_parent" />
</com.xyz.view.widget.ThreePaneLayout>

【问题讨论】:

    标签: android android-fragments android-animation objectanimator


    【解决方案1】:

    问题不在于 ObjectAnimator,而在于您的应用程序在动画的每一帧上都做了太多事情。具体来说,您正在为布局参数设置动画并在每一帧上请求布局。布局功能强大且有用......但在除了最琐碎的视图层次结构之外的任何地方都可能非常昂贵。在动画期间避免每帧昂贵的操作很重要,并且布局属于“昂贵”的类别。滑动东西很好(translationX/Y),淡入/淡出很好(alpha),但实际上在每一帧上都放置东西?直接说不吧。

    【讨论】:

    • 我认为通过“实际上在每一帧上布置东西”,您指的是通过ObjectAnimator 更改中间窗格的宽度。 AFAICT,电子邮件/Gmail 沿着这些思路做一些事情。我在使用这些应用程序时是否误解了我所看到的内容?或者有没有办法以更适合动画的方式调整大小(不仅仅是等到最后)?还是这些应用程序也很笨拙?这里的目标是创建一个可重用的模式来实现电子邮件/Gmail 三窗格 UI 动画效果。谢谢!
    • 好吧,当我在 Gmail 中制作动画时,systrace 没有显示 performTraversals() 调用。我假设我的眼睛在欺骗我,并且 Gmail 在动画期间没有调整对话列表窗格的大小。
    • 如果他们真的调整了容器的大小并导致文本在每一帧上重排,他们会遇到同样的动画问题(或者更多,考虑到典型 Gmail 消息 UI 的复杂性)。我不知道他们在做什么,但我认为这只是一个滑动动画,可能在动画之前将容器布置到最终尺寸,然后简单地将其翻译到位。
    【解决方案2】:

    我最终完全删除了 ObjectAnimator...当然,它仍然有幻灯片动画,但同时没有平滑的重新调整尺寸。并不是说它实际上很顺利......

    无论如何,如果有人确实提出了解决此问题的实际解决方案,请随时分享。谢谢。

    package com.anfuddle.view.widget;
    
    import android.annotation.TargetApi;
    import android.content.Context;
    import android.os.Build;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewPropertyAnimator;
    import android.widget.LinearLayout;
    
    
    public class ThreePaneLayout extends LinearLayout
    {
        private View leftView = null;
        private View middleView = null;
        private View rightView = null;
    
    private static final int ANIM_DURATION = 500;
    private int rootWidth = -1;
    private int leftPaneWidth = -1;
    private int rightPaneWidth = -1;
    
    
    // -------------------------------------------------------------------------------------------
    // --------------   Constructor
    // -------------------------------------------------------------------------------------------
    
    
    public ThreePaneLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setOrientation(HORIZONTAL);
    }
    
    @Override
    public void onFinishInflate()
    {
        super.onFinishInflate();
    
        leftView = getChildAt(0);
        middleView = getChildAt(1);
        rightView = getChildAt(2);
    }
    
    
    // -------------------------------------------------------------------------------------------
    // --------------   Public methods
    // -------------------------------------------------------------------------------------------
    
    
    public View getLeftView()
    {
        return leftView;
    }
    
    public View getMiddleView()
    {
        return middleView;
    }
    
    public View getRightView()
    {
        return rightView;
    }
    
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void hideLeftAndMiddle()
    {
        if (leftPaneWidth == -1)
        {
            rootWidth = getWidth();
            leftPaneWidth = leftView.getWidth();
            rightPaneWidth = middleView.getWidth();
        }
        resetWidget(leftView, leftPaneWidth);
        resetWidget(middleView, rightPaneWidth);
        resetWidget(rightView, rootWidth);
        requestLayout();
    
        translateWidgets(-1 * rootWidth, leftView, middleView, rightView);
    }
    
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void hideLeft()
    {
        if (leftPaneWidth == -1)
        {
            leftPaneWidth = leftView.getWidth();
            rightPaneWidth = middleView.getWidth();
        }
        resetWidget(leftView, leftPaneWidth);
        resetWidget(middleView, leftPaneWidth);
        resetWidget(rightView, rightPaneWidth);
        requestLayout();
        translateWidgets(-1 * leftPaneWidth, leftView, middleView, rightView);
    }
    
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void showLeftAndMiddle()
    {
        translateWidgets(rootWidth, leftView, middleView, rightView);
    }
    
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void showLeft()
    {
        resetWidget(leftView, leftPaneWidth);
        resetWidget(middleView, rightPaneWidth);
        resetWidget(rightView, rightPaneWidth);
        requestLayout();
        translateWidgets(leftPaneWidth, leftView, middleView, rightView);
    }
    
    
    // -------------------------------------------------------------------------------------------
    // --------------   Private methods
    // -------------------------------------------------------------------------------------------
    
    
    @TargetApi(12)
    private void translateWidgets(int deltaX, View... views)
    {
        for (final View view : views)
        {
            ViewPropertyAnimator viewPropertyAnimator = view.animate();
            viewPropertyAnimator.translationXBy(deltaX)
                                .setDuration(ANIM_DURATION);
        }
      }
    
      private void resetWidget(View view, int width)
      {
          LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)view.getLayoutParams();  
          layoutParams.width = width;
          layoutParams.weight = 0;
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 2023-04-02
      • 2016-10-13
      相关资源
      最近更新 更多