Merge "Import launcher style reveal animations to QS."

This commit is contained in:
TreeHugger Robot
2018-02-28 03:48:35 +00:00
committed by Android (Google) Code Review
5 changed files with 240 additions and 29 deletions

View File

@@ -24,6 +24,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
import java.util.Set;
public final class Prefs {
private Prefs() {} // no instantation
@@ -87,6 +88,7 @@ public final class Prefs {
String NUM_APPS_LAUNCHED = "NumAppsLaunched";
String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
String SEEN_RINGER_GUIDANCE_COUNT = "RingerGuidanceCount";
String QS_TILE_SPECS_REVEALED = "QsTileSpecsRevealed";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
@@ -121,6 +123,15 @@ public final class Prefs {
get(context).edit().putString(key, value).apply();
}
public static void putStringSet(Context context, @Key String key, Set<String> value) {
get(context).edit().putStringSet(key, value).apply();
}
public static Set<String> getStringSet(
Context context, @Key String key, Set<String> defaultValue) {
return get(context).getStringSet(key, defaultValue);
}
public static Map<String, ?> getAll(Context context) {
return get(context).getAll();
}

View File

@@ -1,5 +1,10 @@
package com.android.systemui.qs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -8,20 +13,34 @@ import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.Scroller;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanel.TileRecord;
import java.util.ArrayList;
import java.util.Set;
public class PagedTileLayout extends ViewPager implements QSTileLayout {
private static final boolean DEBUG = false;
private static final String TAG = "PagedTileLayout";
private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
private static final long BOUNCE_ANIMATION_DURATION = 450L;
private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
private static final Interpolator SCROLL_CUBIC = (t) -> {
t -= 1.0f;
return t * t * t + 1.0f;
};
private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>();
private final ArrayList<TilePage> mPages = new ArrayList<TilePage>();
@@ -34,37 +53,17 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
private int mPosition;
private boolean mOffPage;
private boolean mListening;
private Scroller mScroller;
private AnimatorSet mBounceAnimatorSet;
private int mAnimatingToPage = -1;
public PagedTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context, SCROLL_CUBIC);
setAdapter(mAdapter);
setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
if (mPageIndicator == null) return;
if (mPageListener != null) {
mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
: position == 0);
}
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
if (mPageIndicator == null) return;
setCurrentPage(position, positionOffset != 0);
mPageIndicator.setLocation(position + positionOffset);
if (mPageListener != null) {
mPageListener.onPageChanged(positionOffsetPixels == 0 &&
(isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
setCurrentItem(0);
setOnPageChangeListener(mOnPageChangeListener);
setCurrentItem(0, false);
}
@Override
@@ -99,6 +98,45 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Suppress all touch event during reveal animation.
if (mAnimatingToPage != -1) {
return true;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Suppress all touch event during reveal animation.
if (mAnimatingToPage != -1) {
return true;
}
return super.onTouchEvent(ev);
}
@Override
public void computeScroll() {
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
float pageFraction = (float) getScrollX() / getWidth();
int position = (int) pageFraction;
float positionOffset = pageFraction - position;
mOnPageChangeListener.onPageScrolled(position, positionOffset, getScrollX());
// Keep on drawing until the animation has finished.
postInvalidateOnAnimation();
return;
}
if (mAnimatingToPage != -1) {
setCurrentItem(mAnimatingToPage, true);
mBounceAnimatorSet.start();
setOffscreenPageLimit(1);
mAnimatingToPage = -1;
}
super.computeScroll();
}
/**
* Sets individual pages to listening or not. If offPage it will set
* the next page after position to listening as well since we are in between
@@ -257,9 +295,84 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
return mPages.get(0).mColumns;
}
public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0) {
// Do not start the reveal animation unless there are tiles to animate, multiple
// TilePages available and the user has not already started dragging.
return;
}
final int lastPageNumber = mPages.size() - 1;
final TilePage lastPage = mPages.get(lastPageNumber);
final ArrayList<Animator> bounceAnims = new ArrayList<>();
for (TileRecord tr : lastPage.mRecords) {
if (tileSpecs.contains(tr.tile.getTileSpec())) {
bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
}
}
if (bounceAnims.isEmpty()) {
// All tileSpecs are on the first page. Nothing to do.
// TODO: potentially show a bounce animation for first page QS tiles
return;
}
mBounceAnimatorSet = new AnimatorSet();
mBounceAnimatorSet.playTogether(bounceAnims);
mBounceAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBounceAnimatorSet = null;
postAnimation.run();
}
});
mAnimatingToPage = lastPageNumber;
setOffscreenPageLimit(mAnimatingToPage); // Ensure the page to reveal has been inflated.
mScroller.startScroll(getScrollX(), getScrollY(), getWidth() * mAnimatingToPage, 0,
REVEAL_SCROLL_DURATION_MILLIS);
postInvalidateOnAnimation();
}
private static Animator setupBounceAnimator(View view, int ordinal) {
view.setAlpha(0f);
view.setScaleX(0f);
view.setScaleY(0f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat(View.ALPHA, 1),
PropertyValuesHolder.ofFloat(View.SCALE_X, 1),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1));
animator.setDuration(BOUNCE_ANIMATION_DURATION);
animator.setStartDelay(ordinal * TILE_ANIMATION_STAGGER_DELAY);
animator.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
return animator;
}
private final ViewPager.OnPageChangeListener mOnPageChangeListener =
new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
if (mPageIndicator == null) return;
if (mPageListener != null) {
mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
: position == 0);
}
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
if (mPageIndicator == null) return;
setCurrentPage(position, positionOffset != 0);
mPageIndicator.setLocation(position + positionOffset);
if (mPageListener != null) {
mPageListener.onPageChanged(positionOffsetPixels == 0 &&
(isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
}
}
};
public static class TilePage extends TileLayout {
private int mMaxRows = 3;
public TilePage(Context context, AttributeSet attrs) {
super(context, attrs);
updateResources();

View File

@@ -290,6 +290,7 @@ public class QSFragment extends Fragment implements QS {
// Let the views animate their contents correctly by giving them the necessary context.
mHeader.setExpansion(mKeyguardShowing, expansion, panelTranslationY);
mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
mQSPanel.getQsTileRevealController().setExpansion(expansion);
mQSPanel.setTranslationY(translationScaleY * heightDiff);
mQSDetail.setFullyExpanded(fullyExpanded);

View File

@@ -60,11 +60,12 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
public static final String QS_SHOW_HEADER = "qs_show_header";
protected final Context mContext;
protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
protected final View mBrightnessView;
private final H mHandler = new H();
private final View mPageIndicator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final QSTileRevealController mQsTileRevealController;
protected boolean mExpanded;
protected boolean mListening;
@@ -108,6 +109,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
addView(mPageIndicator);
((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator);
mQsTileRevealController = new QSTileRevealController(mContext, this,
((PagedTileLayout) mTileLayout));
addDivider();
@@ -136,6 +139,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return mPageIndicator;
}
public QSTileRevealController getQsTileRevealController() {
return mQsTileRevealController;
}
public boolean isShowingCustomize() {
return mCustomizePanel != null && mCustomizePanel.isCustomizing();
}
@@ -352,6 +359,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
if (!collapsedView) {
mQsTileRevealController.updateRevealedTiles(tiles);
}
for (TileRecord record : mRecords) {
mTileLayout.removeTile(record);
record.tile.removeCallback(record.callback);

View File

@@ -0,0 +1,76 @@
package com.android.systemui.qs;
import static com.android.systemui.Prefs.Key.QS_TILE_SPECS_REVEALED;
import android.content.Context;
import android.os.Handler;
import android.util.ArraySet;
import com.android.systemui.Prefs;
import com.android.systemui.plugins.qs.QSTile;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
public class QSTileRevealController {
private static final long QS_REVEAL_TILES_DELAY = 500L;
private final Context mContext;
private final QSPanel mQSPanel;
private final PagedTileLayout mPagedTileLayout;
private final ArraySet<String> mTilesToReveal = new ArraySet<>();
private final Handler mHandler = new Handler();
private final Runnable mRevealQsTiles = new Runnable() {
@Override
public void run() {
mPagedTileLayout.startTileReveal(mTilesToReveal, () -> {
if (mQSPanel.isExpanded()) {
addTileSpecsToRevealed(mTilesToReveal);
mTilesToReveal.clear();
}
});
}
};
QSTileRevealController(Context context, QSPanel qsPanel, PagedTileLayout pagedTileLayout) {
mContext = context;
mQSPanel = qsPanel;
mPagedTileLayout = pagedTileLayout;
}
public void setExpansion(float expansion) {
if (expansion == 1f) {
mHandler.postDelayed(mRevealQsTiles, QS_REVEAL_TILES_DELAY);
} else {
mHandler.removeCallbacks(mRevealQsTiles);
}
}
public void updateRevealedTiles(Collection<QSTile> tiles) {
ArraySet<String> tileSpecs = new ArraySet<>();
for (QSTile tile : tiles) {
tileSpecs.add(tile.getTileSpec());
}
final Set<String> revealedTiles = Prefs.getStringSet(
mContext, QS_TILE_SPECS_REVEALED, Collections.EMPTY_SET);
if (revealedTiles.isEmpty() || mQSPanel.isShowingCustomize()) {
// Do not reveal QS tiles the user has upon first load or those that they directly
// added through customization.
addTileSpecsToRevealed(tileSpecs);
} else {
// Animate all tiles that the user has not directly added themselves.
tileSpecs.removeAll(revealedTiles);
mTilesToReveal.addAll(tileSpecs);
}
}
private void addTileSpecsToRevealed(ArraySet<String> specs) {
final ArraySet<String> revealedTiles = new ArraySet<>(
Prefs.getStringSet(mContext, QS_TILE_SPECS_REVEALED, Collections.EMPTY_SET));
revealedTiles.addAll(specs);
Prefs.putStringSet(mContext, QS_TILE_SPECS_REVEALED, revealedTiles);
}
}