Merge "Import launcher style reveal animations to QS."
This commit is contained in:
committed by
Android (Google) Code Review
commit
f4a368200e
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user