Bug 1097121 - Animate items being removed from the tabs panel grid (r=lucasr)

This commit is contained in:
Martyn Haigh 2014-11-27 16:37:42 +00:00
parent 5d4cc314af
commit 63f1621378

View File

@ -6,6 +6,7 @@
package org.mozilla.gecko.tabs;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.GeckoAppShell;
@ -18,13 +19,22 @@ import org.mozilla.gecko.Tabs;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.widget.GridView;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.GridView;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.PropertyValuesHolder;
import com.nineoldandroids.animation.ValueAnimator;
/**
* A tabs layout implementation for the tablet redesign (bug 1014156).
@ -36,12 +46,18 @@ class TabsGridLayout extends GridView
Tabs.OnTabsChangedListener {
private static final String LOGTAG = "Gecko" + TabsGridLayout.class.getSimpleName();
private static final int ANIM_TIME_MS = 200;
public static final int ANIM_DELAY_MULTIPLE_MS = 20;
private static final DecelerateInterpolator ANIM_INTERPOLATOR = new DecelerateInterpolator();
private final Context mContext;
private TabsPanel mTabsPanel;
private final SparseArray<PointF> mTabLocations = new SparseArray<PointF>();
final private boolean mIsPrivate;
private final TabsLayoutAdapter mTabsAdapter;
private final int mColumnWidth;
public TabsGridLayout(Context context, AttributeSet attrs) {
super(context, attrs, R.attr.tabGridLayoutViewStyle);
@ -67,9 +83,13 @@ class TabsGridLayout extends GridView
setGravity(Gravity.CENTER);
setNumColumns(GridView.AUTO_FIT);
// The clipToPadding setting in the styles.xml doesn't seem to be working (bug 1101784)
// so lets set it manually in code for the moment as it's needed for the padding animation
setClipToPadding(false);
final Resources resources = getResources();
final int columnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_column_width);
setColumnWidth(columnWidth);
mColumnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_column_width);
setColumnWidth(mColumnWidth);
final int padding = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding);
final int paddingTop = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding_top);
@ -87,9 +107,7 @@ class TabsGridLayout extends GridView
mCloseClickListener = new Button.OnClickListener() {
@Override
public void onClick(View v) {
TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
Tabs.getInstance().closeTab(tab);
closeTab(v);
}
};
@ -121,6 +139,47 @@ class TabsGridLayout extends GridView
}
}
private void populateTabLocations(final Tab removedTab) {
mTabLocations.clear();
final int firstPosition = getFirstVisiblePosition();
final int lastPosition = getLastVisiblePosition();
final int numberOfColumns = getNumColumns();
final int childCount = getChildCount();
final int removedPosition = mTabsAdapter.getPositionForTab(removedTab);
for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
final View child = getChildAt(i);
if (child != null) {
mTabLocations.append(x, new PointF(child.getX(), child.getY()));
}
}
final boolean firstChildOffScreen = ((firstPosition > 0) || getChildAt(0).getY() < 0);
final boolean lastChildVisible = (lastPosition - childCount == firstPosition - 1);
final boolean oneItemOnLastRow = (lastPosition % numberOfColumns == 0);
if (firstChildOffScreen && lastChildVisible && oneItemOnLastRow) {
// We need to set the view's bottom padding to prevent a sudden jump as the
// last item in the row is being removed. We then need to remove the padding
// via a sweet animation
final int removedHeight = getChildAt(0).getMeasuredHeight();
final int verticalSpacing = getVerticalSpacing();
ValueAnimator paddingAnimator = ValueAnimator.ofInt(getPaddingBottom() + removedHeight + verticalSpacing, getPaddingBottom());
paddingAnimator.setDuration(ANIM_TIME_MS * 2);
paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (Integer) animation.getAnimatedValue());
}
});
paddingAnimator.start();
}
}
@Override
public void setTabsPanel(TabsPanel panel) {
mTabsPanel = panel;
@ -160,6 +219,9 @@ class TabsGridLayout extends GridView
break;
case CLOSED:
if(mTabsAdapter.getCount() > 0) {
animateRemoveTab(tab);
}
if (tab.isPrivate() == mIsPrivate && mTabsAdapter.getCount() > 0) {
if (mTabsAdapter.removeTab(tab)) {
int selected = mTabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
@ -244,4 +306,90 @@ class TabsGridLayout extends GridView
}
}
}
private View getViewForTab(Tab tab) {
final int position = mTabsAdapter.getPositionForTab(tab);
return getChildAt(position - getFirstVisiblePosition());
}
void closeTab(View v) {
TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
Tabs.getInstance().closeTab(tab);
updateSelectedPosition();
}
private void animateRemoveTab(final Tab removedTab) {
final int removedPosition = mTabsAdapter.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;
}
final int removedHeight = removedView.getMeasuredHeight();
populateTabLocations(removedTab);
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
// We don't animate the removed child view (it just disappears)
// but we still need its size to animate all affected children
// within the visible viewport.
final int childCount = getChildCount();
final int firstPosition = getFirstVisiblePosition();
final int numberOfColumns = getNumColumns();
final List<Animator> childAnimators = new ArrayList<>();
PropertyValuesHolder translateX, translateY;
for (int x = 0, i = removedPosition - firstPosition ; i < childCount; i++, x++) {
final View child = getChildAt(i);
ObjectAnimator animator;
if (i % numberOfColumns == numberOfColumns - 1) {
// Animate X & Y
translateX = PropertyValuesHolder.ofFloat("translationX", -(mColumnWidth * numberOfColumns), 0);
translateY = PropertyValuesHolder.ofFloat("translationY", removedHeight, 0);
animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX, translateY);
} else {
// Just animate X
translateX = PropertyValuesHolder.ofFloat("translationX", mColumnWidth, 0);
animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX);
}
animator.setStartDelay(x * ANIM_DELAY_MULTIPLE_MS);
childAnimators.add(animator);
}
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(childAnimators);
animatorSet.setDuration(ANIM_TIME_MS);
animatorSet.setInterpolator(ANIM_INTERPOLATOR);
animatorSet.start();
// Set the starting position of the child views - because we are delaying the start
// of the animation, we need to prevent the items being drawn in their final position
// prior to the animation starting
for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
final View child = getChildAt(i);
final PointF targetLocation = mTabLocations.get(x+1);
if (targetLocation == null) {
continue;
}
child.setX(targetLocation.x);
child.setY(targetLocation.y);
}
return true;
}
});
}
}