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>
ObjectAnimator
. AFAICT, the Email/Gmail do something along those lines. Am I misinterpreting what I am seeing when using those apps? Or is there a way to resize in a way that is more animation-friendly (beyond just waiting until the end)? Or are those apps also janky? The objective here is to create a reusable pattern for implementing the Email/Gmail three-pane UI animated effect. Thanks! – Cystocarp