xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tile_page"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false" />
<com.android.systemui.qs.PagedTileLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false">
<FrameLayout
android:id="@+id/page_decor"
android:layout_marginTop="@dimen/status_bar_header_height"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingBottom="8dp" />
+ android:paddingBottom="8dp"
+ android:clipToPadding="false"
+ android:clipChildren="false" />
<include layout="@layout/quick_status_bar_expanded_header" />
<dimen name="qs_date_alarm_anim_translation">26dp</dimen>
<dimen name="qs_date_collapsed_text_size">14sp</dimen>
<dimen name="qs_date_text_size">16sp</dimen>
- <dimen name="qs_header_gear_translation">120dp</dimen>
+ <dimen name="qs_header_gear_translation">150dp</dimen>
<dimen name="qs_page_indicator_size">12dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
private int mNumPages;
private View mDecorGroup;
+ private PageListener mPageListener;
public PagedTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
public void onPageSelected(int position) {
if (mPageIndicator == null) return;
mPageIndicator.setLocation(position);
+ if (mPageListener != null) {
+ mPageListener.onPageChanged(position);
+ }
}
@Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ public void onPageScrolled(int position, float positionOffset,
+ int positionOffsetPixels) {
if (mPageIndicator == null) return;
mPageIndicator.setLocation(position + positionOffset);
}
}
}
+ public void setPageListener(PageListener listener) {
+ mPageListener = listener;
+ }
+
private void postDistributeTiles() {
removeCallbacks(mDistribute);
post(mDistribute);
return view == object;
}
};
+
+ public interface PageListener {
+ void onPageChanged(int page);
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.TextView;
+import com.android.systemui.Interpolators;
+import com.android.systemui.qs.PagedTileLayout.PageListener;
+import com.android.systemui.qs.QSPanel.QSTileLayout;
+import com.android.systemui.qs.QSTile.Host.Callback;
+import com.android.systemui.qs.TouchAnimator.Builder;
+import com.android.systemui.qs.TouchAnimator.Listener;
+import com.android.systemui.statusbar.phone.QSTileHost;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener {
+
+ private static final String TAG = "QSAnimator";
+
+ public static final PathInterpolator TRANSLATION_Y_INTERPOLATOR =
+ new PathInterpolator(.1f, .3f, 1, 1);
+
+ public static final float EXPANDED_TILE_DELAY = .7f;
+
+ private final ArrayList<View> mAllViews = new ArrayList<>();
+ private final QuickQSPanel mQuickQsPanel;
+ private final QSPanel mQsPanel;
+ private final QSContainer mQsContainer;
+
+ private boolean mOnFirstPage = true;
+ private TouchAnimator mFirstPageAnimator;
+ private TouchAnimator mFirstPageDelayedAnimator;
+ private TouchAnimator mTranslationYAnimator;
+ private TouchAnimator mNonfirstPageAnimator;
+
+ public QSAnimator(QSContainer container, QuickQSPanel quickPanel, QSPanel panel) {
+ mQsContainer = container;
+ mQuickQsPanel = quickPanel;
+ mQsPanel = panel;
+ mQuickQsPanel.addOnLayoutChangeListener(this);
+ mQsPanel.addOnLayoutChangeListener(this);
+ QSTileLayout tileLayout = mQsPanel.getTileLayout();
+ if (tileLayout instanceof PagedTileLayout) {
+ ((PagedTileLayout) tileLayout).setPageListener(this);
+ } else {
+ Log.w(TAG, "QS Not using page layout");
+ }
+ }
+
+ public void setHost(QSTileHost qsh) {
+ qsh.addCallback(this);
+ }
+
+ @Override
+ public void onPageChanged(int page) {
+ mOnFirstPage = page == 0;
+ if (!mOnFirstPage) {
+ clearAnimationState();
+ }
+ }
+
+ private void updateAnimators() {
+ TouchAnimator.Builder firstPageBuilder = new Builder();
+ TouchAnimator.Builder translationYBuilder = new Builder();
+ TouchAnimator.Builder firstPageDelayedBuilder = new Builder();
+ Collection<QSTile<?>> tiles = mQsPanel.getHost().getTiles();
+ int count = 0;
+ int[] loc1 = new int[2];
+ int[] loc2 = new int[2];
+ firstPageDelayedBuilder.setStartDelay(EXPANDED_TILE_DELAY);
+ firstPageBuilder.setListener(this);
+ translationYBuilder.setInterpolator(TRANSLATION_Y_INTERPOLATOR);
+ mAllViews.clear();
+ for (QSTile<?> tile : tiles) {
+ QSTileBaseView tileView = mQsPanel.getTileView(tile);
+ final TextView label = ((QSTileView) tileView).getLabel();
+ if (count++ < 5) {
+ // Quick tiles.
+ QSTileBaseView quickTileView = mQuickQsPanel.getTileView(tile);
+ final View tileIcon = tileView.getIcon();
+
+ getRelativePosition(loc1, quickTileView.getIcon(), mQsContainer);
+ getRelativePosition(loc2, tileIcon, mQsContainer);
+ final int xDiff = loc2[0] - loc1[0];
+ final int yDiff = loc2[1] - loc1[1];
+ // Move the quick tile right from its location to the new one.
+ firstPageBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
+ translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
+
+ // Counteract the parent translation on the tile. So we have a static base to
+ // animate off from.
+ firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
+
+ // Move the real tile's icon and label from the quick tile position to its final
+ // location.
+ firstPageBuilder.addFloat(tileIcon, "translationX", -xDiff, 0);
+ translationYBuilder.addFloat(tileIcon, "translationY", -yDiff, 0);
+ firstPageBuilder.addFloat(label, "translationX", -xDiff, 0);
+ translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
+
+ // Fade in the label as we reach the final position.
+ firstPageDelayedBuilder.addFloat(label, "alpha", 0, 1);
+ mAllViews.add(quickTileView);
+ } else {
+ firstPageDelayedBuilder.addFloat(tileView, "alpha", 0, 1);
+ }
+ mAllViews.add(tileView);
+ mAllViews.add(label);
+ }
+ mFirstPageAnimator = firstPageBuilder.build();
+ mFirstPageDelayedAnimator = firstPageDelayedBuilder.build();
+ mTranslationYAnimator = translationYBuilder.build();
+ mNonfirstPageAnimator = new TouchAnimator.Builder()
+ .addFloat(mQuickQsPanel, "alpha", 1, 0)
+ .setEndDelay(.5f)
+ .build();
+ }
+
+ private void getRelativePosition(int[] loc1, View view, View parent) {
+ loc1[0] = 0 + view.getWidth() / 2;
+ loc1[1] = 0;
+ getRelativePositionInt(loc1, view, parent);
+ }
+
+ private void getRelativePositionInt(int[] loc1, View view, View parent) {
+ if(view == parent || view == null) return;
+ loc1[0] += view.getLeft();
+ loc1[1] += view.getTop();
+ getRelativePositionInt(loc1, (View) view.getParent(), parent);
+ }
+
+ public void setPosition(float position) {
+ if (mFirstPageAnimator == null) return;
+ if (mOnFirstPage) {
+ mQuickQsPanel.setAlpha(1);
+ mFirstPageAnimator.setPosition(position);
+ mFirstPageDelayedAnimator.setPosition(position);
+ mTranslationYAnimator.setPosition(position);
+ } else {
+ mNonfirstPageAnimator.setPosition(position);
+ }
+ }
+
+ @Override
+ public void onAnimationAtStart() {
+
+ }
+
+ @Override
+ public void onAnimationAtEnd() {
+ mQuickQsPanel.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onAnimationStarted() {
+ mQuickQsPanel.setVisibility(View.VISIBLE);
+ }
+
+ private void clearAnimationState() {
+ final int N = mAllViews.size();
+ mQuickQsPanel.setAlpha(0);
+ for (int i = 0; i < N; i++) {
+ View v = mAllViews.get(i);
+ v.setAlpha(1);
+ v.setTranslationX(1);
+ v.setTranslationY(1);
+ }
+ }
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ updateAnimators();
+ }
+
+ @Override
+ public void onTilesChanged() {
+ // Give the QS panels a moment to generate their new tiles, then create all new animators
+ // hooked up to the new views.
+ mQsPanel.post(mUpdateAnimators);
+ }
+
+ private Runnable mUpdateAnimators = new Runnable() {
+ @Override
+ public void run() {
+ updateAnimators();
+ }
+ };
+}
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.BaseStatusBarHeader;
+import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
private boolean mStackScrollerOverscrolling;
private long mDelay;
+ private QSAnimator mQSAnimator;
public QSContainer(Context context, AttributeSet attrs) {
super(context, attrs);
mQSDetail = (QSDetail) findViewById(R.id.qs_detail);
mQSDetail.setQsPanel(mQSPanel);
mHeader = (BaseStatusBarHeader) findViewById(R.id.header);
+ mQSAnimator = new QSAnimator(this, (QuickQSPanel) mHeader.findViewById(R.id.quick_qs_panel),
+ mQSPanel);
+ }
+
+ public void setHost(QSTileHost qsh) {
+ mQSPanel.setHost(qsh);
+ mHeader.setQSPanel(mQSPanel);
+ mQSDetail.setHost(qsh);
+ mQSAnimator.setHost(qsh);
}
@Override
mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight());
mQSDetail.setFullyExpanded(expansion == 1);
+ mQSAnimator.setPosition(expansion);
updateBottom();
}
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
import java.util.Collection;
/** View that represents the quick settings tile panel. **/
-public class QSPanel extends FrameLayout implements Tunable {
+public class QSPanel extends LinearLayout implements Tunable {
public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
protected QSFooter mFooter;
private boolean mGridContentVisible = true;
- protected LinearLayout mQsContainer;
protected QSTileLayout mTileLayout;
private QSCustomizer mCustomizePanel;
super(context, attrs);
mContext = context;
-
- mQsContainer = new LinearLayout(mContext);
- mQsContainer.setOrientation(LinearLayout.VERTICAL);
- mQsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.WRAP_CONTENT));
- addView(mQsContainer);
+ setOrientation(VERTICAL);
mBrightnessView = LayoutInflater.from(context).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
- mQsContainer.addView(mBrightnessView);
+ addView(mBrightnessView);
mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
- R.layout.qs_paged_tile_layout, mQsContainer, false);
- mQsContainer.addView((View) mTileLayout);
+ R.layout.qs_paged_tile_layout, this, false);
+ addView((View) mTileLayout);
findViewById(android.R.id.edit).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
});
mFooter = new QSFooter(this, context);
- mQsContainer.addView(mFooter.getView());
+ addView(mFooter.getView());
updateResources();
final Resources res = mContext.getResources();
mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
- mQsContainer.setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
+ setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
for (TileRecord r : mRecords) {
r.tile.clearState();
}
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
+ if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
+ ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ }
MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
if (!mExpanded) {
closeDetail();
}
public int getGridHeight() {
- return mQsContainer.getMeasuredHeight();
+ return getMeasuredHeight();
}
protected void handleShowDetail(Record r, boolean show) {
void setGridContentVisibility(boolean visible) {
int newVis = visible ? VISIBLE : INVISIBLE;
- mQsContainer.setVisibility(newVis);
+ setVisibility(newVis);
if (mGridContentVisible != visible) {
MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
}
}
}
+ QSTileLayout getTileLayout() {
+ return mTileLayout;
+ }
+
+ QSTileBaseView getTileView(QSTile<?> tile) {
+ for (TileRecord r : mRecords) {
+ if (r.tile == tile) {
+ return r.tileView;
+ }
+ }
+ return null;
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
// Default to Quick Tile padding, and QSTileView will specify its own padding.
int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
setPadding(padding, padding, padding, padding);
+ setClipChildren(false);
+ setClipToPadding(false);
}
private Drawable newTileBackground() {
setContentDescription(state.contentDescription);
}
+ View getIcon() {
+ return mIcon;
+ }
+
private class H extends Handler {
private static final int STATE_CHANGED = 1;
public H() {
setGravity(Gravity.CENTER);
}
+ TextView getLabel() {
+ return mLabel;
+ }
+
private void updateTopPadding() {
Resources res = getResources();
int padding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top);
for (int i = 0; i < mRecords.size(); i++) {
mTileLayout.removeTile(mRecords.get(i));
}
- mQsContainer.removeView((View) mTileLayout);
+ removeView((View) mTileLayout);
}
mTileLayout = new HeaderTileLayout(context);
- mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
+ addView((View) mTileLayout, 1 /* Between brightness and footer */);
}
@Override
}
}
- public static class FloatKeyframeSet extends KeyframeSet {
+ private static class FloatKeyframeSet extends KeyframeSet {
public FloatKeyframeSet(Keyframe[] keyframes) {
super(keyframes);
}
}
}
- public static class IntKeyframeSet extends KeyframeSet {
+ private static class IntKeyframeSet extends KeyframeSet {
public IntKeyframeSet(Keyframe[] keyframes) {
super(keyframes);
}
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.qs.QSDetail;
+import com.android.systemui.qs.QSContainer;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.recents.events.EventBus;
mCastController, mFlashlightController,
mUserSwitcherController, mUserInfoController, mKeyguardMonitor,
mSecurityController, mBatteryController, mIconController);
- mQSPanel.setHost(qsh);
mQSPanel.setTiles(qsh.getTiles());
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
- mHeader.setQSPanel(mQSPanel);
- QSDetail qsDetail = (QSDetail) mStatusBarWindow.findViewById(R.id.qs_detail);
- qsDetail.setHost(qsh);
+ QSContainer qsContainer = (QSContainer) mStatusBarWindow.findViewById(
+ R.id.quick_settings_container);
+ qsContainer.setHost(qsh);
qsh.addCallback(new QSTileHost.Callback() {
@Override
public void onTilesChanged() {
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.qs.QSAnimator;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QuickQSPanel;
private static final String TAG = "QuickStatusBarHeader";
- private static final float EXPAND_INDICATOR_THRESHOLD = .8f;
+ private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
private ActivityStarter mActivityStarter;
private NextAlarmController mNextAlarmController;
private TouchAnimator mFirstHalfAnimator;
private TouchAnimator mDateSizeAnimator;
private TouchAnimator mAlarmTranslation;
+ private TouchAnimator mSettingsAlpha;
private float mExpansionAmount;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
mAnimator = new TouchAnimator.Builder()
.addFloat(mSettingsContainer, "translationY", -mGearTranslation, 0)
.addFloat(mMultiUserSwitch, "translationY", -mGearTranslation, 0)
- .addFloat(mSettingsButton, "rotation", -90, 0)
.setListener(this)
.build();
mSecondHalfAnimator = new TouchAnimator.Builder()
- .addFloat(mSettingsButton, "rotation", -90, 0)
+ .addFloat(mSettingsButton, "rotation", -180, 0)
.addFloat(mAlarmStatus, "alpha", 0, 1)
.addFloat(mEmergencyOnly, "alpha", 0, 1)
.setStartDelay(.5f)
.build();
mFirstHalfAnimator = new TouchAnimator.Builder()
.addFloat(mAlarmStatusCollapsed, "alpha", 1, 0)
- .addFloat(mHeaderQsPanel, "alpha", 1, 0)
.setEndDelay(.5f)
.build();
mDateSizeAnimator = new TouchAnimator.Builder()
.addFloat(mDateTimeGroup, "scaleY", 1, mDateScaleFactor)
.setStartDelay(.36f)
.build();
+ mSettingsAlpha = new TouchAnimator.Builder()
+ .addFloat(mSettingsContainer, "alpha", 0, 1)
+ .addFloat(mMultiUserSwitch, "alpha", 0, 1)
+ .setStartDelay(QSAnimator.EXPANDED_TILE_DELAY)
+ .build();
}
@Override
mFirstHalfAnimator.setPosition(headerExpansionFraction);
mDateSizeAnimator.setPosition(headerExpansionFraction);
mAlarmTranslation.setPosition(headerExpansionFraction);
+ mSettingsAlpha.setPosition(headerExpansionFraction);
updateAlarmVisibilities();