Bug 1055606 - Animate tab strip when tabs added added or removed (r=mcomella)

This commit is contained in:
Lucas Rocha 2014-11-04 15:49:06 +00:00
parent 714140e316
commit bb787d958f
2 changed files with 119 additions and 8 deletions

View File

@ -82,9 +82,7 @@ public class TabStrip extends ThemedLinearLayout {
switch (msg) {
case RESTORED:
case ADDED:
// Refresh the list to make sure the new tab is
// added in the right position.
tabStripView.refreshTabs();
tabStripView.addTab(tab);
break;
case CLOSED:

View File

@ -11,11 +11,16 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
import java.util.ArrayList;
import java.util.List;
@ -27,6 +32,10 @@ import org.mozilla.gecko.widget.TwoWayView;
public class TabStripView extends TwoWayView {
private static final String LOGTAG = "GeckoTabStrip";
private static final int ANIM_TIME_MS = 200;
private static final AccelerateDecelerateInterpolator ANIM_INTERPOLATOR =
new AccelerateDecelerateInterpolator();
private final TabStripAdapter adapter;
private final Drawable divider;
@ -71,14 +80,110 @@ public class TabStripView extends TwoWayView {
setItemChecked(selected, true);
}
private void updateSelectedPosition() {
private void updateSelectedPosition(boolean ensureVisible) {
final int selected = getPositionForSelectedTab();
if (selected != -1) {
updateSelectedStyle(selected);
ensurePositionIsVisible(selected);
if (ensureVisible) {
ensurePositionIsVisible(selected);
}
}
}
private void animateRemoveTab(Tab removedTab) {
final int removedPosition = adapter.getPositionForTab(removedTab);
final View removedView = getViewForTab(removedTab);
// The removed position might not have a matching child view
// when it's not within the visible range of positions in the strip.
if (removedView == null) {
return;
}
// We don't animate the removed child view (it just disappears)
// but we still need its size of animate all affected children
// within the visible viewport.
final int removedSize = removedView.getWidth() + getItemMargin();
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
final int firstPosition = getFirstVisiblePosition();
final List<Animator> childAnimators = new ArrayList<Animator>();
final int childCount = getChildCount();
for (int i = removedPosition - firstPosition; i < childCount; i++) {
final View child = getChildAt(i);
// TODO: optimize with Valueresolver
final ObjectAnimator animator =
ObjectAnimator.ofFloat(child, "translationX", removedSize, 0);
childAnimators.add(animator);
}
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(childAnimators);
animatorSet.setDuration(ANIM_TIME_MS);
animatorSet.setInterpolator(ANIM_INTERPOLATOR);
animatorSet.start();
return true;
}
});
}
private void animateNewTab(Tab newTab) {
final int newPosition = adapter.getPositionForTab(newTab);
if (newPosition < 0) {
return;
}
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
final int firstPosition = getFirstVisiblePosition();
final View newChild = getChildAt(newPosition - firstPosition);
if (newChild == null) {
return true;
}
final List<Animator> childAnimators = new ArrayList<Animator>();
childAnimators.add(
ObjectAnimator.ofFloat(newChild, "translationY", newChild.getHeight(), 0));
// This will momentaneously add a gap on the right side
// because TwoWayView doesn't provide APIs to control
// view recycling programatically to handle these transitory
// states in the container during animations.
final int tabSize = newChild.getWidth();
final int newIndex = newPosition - firstPosition;
final int childCount = getChildCount();
for (int i = newIndex + 1; i < childCount; i++) {
final View child = getChildAt(i);
childAnimators.add(
ObjectAnimator.ofFloat(child, "translationX", -tabSize, 0));
}
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(childAnimators);
animatorSet.setDuration(ANIM_TIME_MS);
animatorSet.setInterpolator(ANIM_INTERPOLATOR);
animatorSet.start();
return true;
}
});
}
private void ensurePositionIsVisible(final int position) {
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
@ -111,16 +216,24 @@ public class TabStripView extends TwoWayView {
}
adapter.refresh(tabs);
updateSelectedPosition();
updateSelectedPosition(true);
}
void clearTabs() {
adapter.clear();
}
void addTab(Tab tab) {
// Refresh the list to make sure the new tab is
// added in the right position.
refreshTabs();
animateNewTab(tab);
}
void removeTab(Tab tab) {
animateRemoveTab(tab);
adapter.removeTab(tab);
updateSelectedPosition();
updateSelectedPosition(false);
}
void selectTab(Tab tab) {
@ -128,7 +241,7 @@ public class TabStripView extends TwoWayView {
isPrivate = tab.isPrivate();
refreshTabs();
} else {
updateSelectedPosition();
updateSelectedPosition(true);
}
}