问题
The implementation info: I have implemented a 3 pane layout following CommonsWare response posted on his own question over here: Complete Working Sample of the Gmail Three-Fragment Animation Scenario?
As a general idea, I have the layout comprised of following levels (1 through 3):
MainActivity
SlidingMenu
(Side Drawer UI pattern) fragment hiding on the left side and aContentFragment
as the fragment that houses the 3 Pane Layout.- Inside the
ContentFragment
:LeftListFragment
(rows with 3 TextViews each),MiddleListFragment
(rows with 8 TextViews each),DetailFragment
.
LeftListFragment
and MiddleListFragment
use CursorLoaders
to load data from a ContentProvider
inside each list. DetailFragment
also calls for a cursor with data when needed. So I didn't even implement custom Adapters
(much nicer design this way). Then I have added the 3 Pane layout + animations. As far as working, it works as intended, no problems there. Animation
time is 500ms.
The problem: The animations stutter a little. A few dropped frames. Both when the Left and Middle are visible and I click on a Middle list item to open a Detail; and also when I tap the Back button to see again Left and Middle lists (when nothing is actually loading).
What I've tried:
- Removed the code that loads the fragment in
DetailView
. I just tap an item inMiddleFragment
and the animation begins, without any Detail actually loading. Still stutters. Also, when hitting back, nothing loads and it still stutters, so I presume the loaders/cursors are not the cause of this. - I used dumpsys gfxinfo to see the average time in ms to compute each frame. Indeed, the avg time for a computation is 18ms, which is above the 16ms threshold. So does this mean the stuttering is because of the time it takes to draw again the lists, when animating ? If so, why ? I mean... I don't have any images at all inside row views. And I couldn't screw up the Adapters code, because I haven't written any...
- Reducing the
Animation
time from 500ms to 200ms. It still stutters if you watch it really carefully, it's just faster.
EDIT: I switched from rightPaneWidth
to leftPaneWidth
below (yes, that removes the re-dimensioning animation) and the stuttering is now gone. The list still slides to the left side, but it just doesn't get smaller in width. So if there is no more stuttering now, does that mean there is a problem with the ObjectAnimator in my code ?
ObjectAnimator.ofInt(this, "middleWidth", rightPaneWidth, leftPaneWidth)
.setDuration(ANIM_DURATION).start();
Thanks for your time !
Code for 3 Pane Layout:
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;
}
}
XML for the ContentFragment:
<?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>
回答1:
The problem is not ObjectAnimator, but rather that your application is simply doing too much on every frame of the animation. Specifically, you are animating layout params and requesting layout on every frame. Layout is powerful and useful... but can be quite expensive in any but the most trivial of view hierarchies. It's important to avoid expensive operations per-frame during animations, and layout falls into that "expensive" category. Sliding things around is fine (translationX/Y), fading things in/out is good (alpha), but actually laying things out on every frame? Just say no.
回答2:
I ended up removing the ObjectAnimator completely... It still has the slide animation, of course, but not the smooth re-dimensioning at the same time. Not that it was actually smooth...
Anyway, if somebody does come up with an actual solution to this problem, feel free to share. Thanks.
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;
}
}
来源:https://stackoverflow.com/questions/14665469/3-pane-layout-fragment-animation-is-stuttering