Merge changes I91733155,If5912504 into nyc-dev

* changes:
  QS header: animation work
  Add TouchAnimator to make QS Animations simpler
This commit is contained in:
Jason Monk
2016-02-18 19:21:19 +00:00
committed by Android (Google) Code Review
6 changed files with 382 additions and 50 deletions

View File

@@ -32,7 +32,6 @@
>
<LinearLayout
android:id="@+id/expanded_group"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:gravity="center"
@@ -80,12 +79,12 @@
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
<ImageView
<com.android.systemui.statusbar.phone.ExpandableIndicator
android:id="@+id/expand_indicator"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="@drawable/ic_expand_less"
android:tint="@android:color/white" />
android:padding="12dp" />
</LinearLayout>
<TextView
@@ -109,6 +108,7 @@
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="4dp"
android:layout_marginStart="16dp"
android:gravity="start"
android:orientation="vertical">
@@ -116,7 +116,6 @@
android:id="@+id/date_time_group"
android:layout_width="wrap_content"
android:layout_height="19dp"
android:layout_marginTop="4dp"
android:orientation="horizontal">
<include layout="@layout/split_clock_view"

View File

@@ -166,6 +166,7 @@
<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_page_indicator_size">12dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>

View File

@@ -17,12 +17,10 @@
package com.android.systemui.qs;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Space;
import com.android.systemui.R;
@@ -103,7 +101,7 @@ public class QuickQSPanel extends QSPanel {
private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
private final ImageView mDownArrow;
private final Space mEndSpacer;
public HeaderTileLayout(Context context) {
super(context);
@@ -112,16 +110,10 @@ public class QuickQSPanel extends QSPanel {
setGravity(Gravity.CENTER_VERTICAL);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
int padding =
mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
mDownArrow = new ImageView(context);
mDownArrow.setImageResource(R.drawable.ic_expand_more);
mDownArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
android.R.color.white, null)));
mDownArrow.setLayoutParams(generateLayoutParams());
mDownArrow.setPadding(padding, padding, padding, padding);
mEndSpacer = new Space(context);
mEndSpacer.setLayoutParams(generateLayoutParams());
updateDownArrowMargin();
addView(mDownArrow);
addView(mEndSpacer);
setOrientation(LinearLayout.HORIZONTAL);
}
@@ -132,10 +124,10 @@ public class QuickQSPanel extends QSPanel {
}
private void updateDownArrowMargin() {
LayoutParams params = (LayoutParams) mDownArrow.getLayoutParams();
LayoutParams params = (LayoutParams) mEndSpacer.getLayoutParams();
params.setMarginStart(mContext.getResources().getDimensionPixelSize(
R.dimen.qs_expand_margin));
mDownArrow.setLayoutParams(params);
mEndSpacer.setLayoutParams(params);
}
@Override

View File

@@ -0,0 +1,241 @@
/*
* 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.animation.Keyframe;
import android.util.MathUtils;
import android.util.Property;
import android.view.animation.Interpolator;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class, that handles similar properties as animators (delay, interpolators)
* but can have a float input as to the amount they should be in effect. This allows
* easier animation that tracks input.
*
* All "delays" and "times" are as fractions from 0-1.
*/
public class TouchAnimator {
private final Object[] mTargets;
private final Property[] mProperties;
private final KeyframeSet[] mKeyframeSets;
private final float mStartDelay;
private final float mEndDelay;
private final float mSpan;
private final Interpolator mInterpolator;
private final Listener mListener;
private float mLastT;
private TouchAnimator(Object[] targets, Property[] properties, KeyframeSet[] keyframeSets,
float startDelay, float endDelay, Interpolator interpolator, Listener listener) {
mTargets = targets;
mProperties = properties;
mKeyframeSets = keyframeSets;
mStartDelay = startDelay;
mEndDelay = endDelay;
mSpan = (1 - mEndDelay - mStartDelay);
mInterpolator = interpolator;
mListener = listener;
}
public void setPosition(float fraction) {
float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1);
if (mInterpolator != null) {
t = mInterpolator.getInterpolation(t);
}
if (mListener != null) {
if (mLastT == 0 || mLastT == 1) {
if (t != 0) {
mListener.onAnimationStarted();
}
} else if (t == 1) {
mListener.onAnimationAtEnd();
} else if (t == 0) {
mListener.onAnimationAtStart();
}
mLastT = t;
}
for (int i = 0; i < mTargets.length; i++) {
Object value = mKeyframeSets[i].getValue(t);
mProperties[i].set(mTargets[i], value);
}
}
public static class ListenerAdapter implements Listener {
@Override
public void onAnimationAtStart() { }
@Override
public void onAnimationAtEnd() { }
@Override
public void onAnimationStarted() { }
}
public interface Listener {
/**
* Called when the animator moves into a position of "0". Start and end delays are
* taken into account, so this position may cover a range of fractional inputs.
*/
void onAnimationAtStart();
/**
* Called when the animator moves into a position of "0". Start and end delays are
* taken into account, so this position may cover a range of fractional inputs.
*/
void onAnimationAtEnd();
/**
* Called when the animator moves out of the start or end position and is in a transient
* state.
*/
void onAnimationStarted();
}
public static class Builder {
private List<Object> mTargets = new ArrayList<>();
private List<Property> mProperties = new ArrayList<>();
private List<KeyframeSet> mValues = new ArrayList<>();
private float mStartDelay;
private float mEndDelay;
private Interpolator mInterpolator;
private Listener mListener;
public Builder addFloat(Object target, String property, float... values) {
add(target, property, KeyframeSet.ofFloat(values));
return this;
}
public Builder addInt(Object target, String property, int... values) {
add(target, property, KeyframeSet.ofInt(values));
return this;
}
private void add(Object target, String property, KeyframeSet keyframeSet) {
mTargets.add(target);
// TODO: Optimize the properties here, to use those in View when possible.
mProperties.add(Property.of(target.getClass(), float.class, property));
mValues.add(keyframeSet);
}
public Builder setStartDelay(float startDelay) {
mStartDelay = startDelay;
return this;
}
public Builder setEndDelay(float endDelay) {
mEndDelay = endDelay;
return this;
}
public Builder setInterpolator(Interpolator intepolator) {
mInterpolator = intepolator;
return this;
}
public Builder setListener(Listener listener) {
mListener = listener;
return this;
}
public TouchAnimator build() {
return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
mProperties.toArray(new Property[mProperties.size()]),
mValues.toArray(new KeyframeSet[mValues.size()]),
mStartDelay, mEndDelay, mInterpolator, mListener);
}
}
private static abstract class KeyframeSet {
private final Keyframe[] mKeyframes;
public KeyframeSet(Keyframe[] keyframes) {
mKeyframes = keyframes;
}
Object getValue(float fraction) {
int i;
for (i = 1; i < mKeyframes.length && fraction > mKeyframes[i].getFraction(); i++) ;
Keyframe first = mKeyframes[i - 1];
Keyframe second = mKeyframes[i];
float amount = (fraction - first.getFraction())
/ (second.getFraction() - first.getFraction());
return interpolate(first, second, amount);
}
protected abstract Object interpolate(Keyframe first, Keyframe second, float amount);
public static KeyframeSet ofInt(int... values) {
int numKeyframes = values.length;
Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
if (numKeyframes == 1) {
keyframes[0] = Keyframe.ofInt(0f);
keyframes[1] = Keyframe.ofInt(1f, values[0]);
} else {
keyframes[0] = Keyframe.ofInt(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] = Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
}
}
return new IntKeyframeSet(keyframes);
}
public static KeyframeSet ofFloat(float... values) {
int numKeyframes = values.length;
Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
if (numKeyframes == 1) {
keyframes[0] = Keyframe.ofFloat(0f);
keyframes[1] = Keyframe.ofFloat(1f, values[0]);
} else {
keyframes[0] = Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] = Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
}
}
return new FloatKeyframeSet(keyframes);
}
}
public static class FloatKeyframeSet extends KeyframeSet {
public FloatKeyframeSet(Keyframe[] keyframes) {
super(keyframes);
}
@Override
protected Object interpolate(Keyframe first, Keyframe second, float amount) {
float firstFloat = (float) first.getValue();
float secondFloat = (float) second.getValue();
return firstFloat + (secondFloat - firstFloat) * amount;
}
}
public static class IntKeyframeSet extends KeyframeSet {
public IntKeyframeSet(Keyframe[] keyframes) {
super(keyframes);
}
@Override
protected Object interpolate(Keyframe first, Keyframe second, float amount) {
int firstFloat = (int) first.getValue();
int secondFloat = (int) second.getValue();
return (int) (firstFloat + (secondFloat - firstFloat) * amount);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.statusbar.phone;
import android.content.Context;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import com.android.systemui.R;
public class ExpandableIndicator extends ImageView {
private boolean mExpanded;
public ExpandableIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
: R.drawable.ic_volume_expand_animation;
setImageResource(res);
}
public void setExpanded(boolean expanded) {
if (expanded == mExpanded) return;
mExpanded = expanded;
final int res = mExpanded ? R.drawable.ic_volume_expand_animation
: R.drawable.ic_volume_collapse_animation;
// workaround to reset drawable
final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getContext()
.getDrawable(res).getConstantState().newDrawable();
setImageDrawable(avd);
avd.start();
}
}

View File

@@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@@ -36,15 +37,21 @@ import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QuickQSPanel;
import com.android.systemui.qs.TouchAnimator;
import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tuner.TunerService;
public class QuickStatusBarHeader extends BaseStatusBarHeader implements
NextAlarmController.NextAlarmChangeCallback, View.OnClickListener {
NextAlarmChangeCallback, OnClickListener, Listener {
private static final String TAG = "QuickStatusBarHeader";
private static final float EXPAND_INDICATOR_THRESHOLD = .8f;
private ActivityStarter mActivityStarter;
private NextAlarmController mNextAlarmController;
private SettingsButton mSettingsButton;
@@ -58,11 +65,12 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
private boolean mExpanded;
private boolean mAlarmShowing;
private ViewGroup mExpandedGroup;
private ViewGroup mDateTimeGroup;
private ViewGroup mDateTimeAlarmGroup;
private TextView mEmergencyOnly;
private ExpandableIndicator mExpandIndicator;
private boolean mListening;
private AlarmManager.AlarmClockInfo mNextAlarm;
@@ -73,8 +81,15 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
private float mDateTimeTranslation;
private float mDateTimeAlarmTranslation;
private float mExpansionFraction;
private float mDateScaleFactor;
private float mGearTranslation;
private TouchAnimator mAnimator;
private TouchAnimator mSecondHalfAnimator;
private TouchAnimator mFirstHalfAnimator;
private TouchAnimator mDateSizeAnimator;
private TouchAnimator mAlarmTranslation;
private float mExpansionAmount;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -89,8 +104,10 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
mDateTimeGroup.setPivotX(0);
mDateTimeGroup.setPivotY(0);
mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
mExpandIndicator = (ExpandableIndicator) findViewById(R.id.expand_indicator);
mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
@@ -131,6 +148,8 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
FontSizeUtils.updateFontSize(mEmergencyOnly, R.dimen.qs_emergency_calls_only_text_size);
mGearTranslation = mContext.getResources().getDimension(R.dimen.qs_header_gear_translation);
mDateTimeTranslation = mContext.getResources().getDimension(
R.dimen.qs_date_anim_translation);
mDateTimeAlarmTranslation = mContext.getResources().getDimension(
@@ -139,8 +158,31 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
R.dimen.qs_date_collapsed_text_size);
float dateExpandedSize = mContext.getResources().getDimension(
R.dimen.qs_date_text_size);
mDateScaleFactor = dateExpandedSize / dateCollapsedSize - 1;
mDateScaleFactor = dateExpandedSize / dateCollapsedSize;
updateDateTimePosition();
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(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, "scaleX", 1, mDateScaleFactor)
.addFloat(mDateTimeGroup, "scaleY", 1, mDateScaleFactor)
.setStartDelay(.36f)
.build();
}
@Override
@@ -165,45 +207,52 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
if (nextAlarm != null) {
mAlarmStatus.setText(KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm));
}
mAlarmShowing = nextAlarm != null;
updateEverything();
if (mAlarmShowing != (nextAlarm != null)) {
mAlarmShowing = nextAlarm != null;
updateEverything();
}
}
@Override
public void setExpansion(float headerExpansionFraction) {
mExpansionFraction = headerExpansionFraction;
mExpansionAmount = headerExpansionFraction;
mAnimator.setPosition(headerExpansionFraction);
mSecondHalfAnimator.setPosition(headerExpansionFraction);
mFirstHalfAnimator.setPosition(headerExpansionFraction);
mDateSizeAnimator.setPosition(headerExpansionFraction);
mAlarmTranslation.setPosition(headerExpansionFraction);
mExpandedGroup.setAlpha(headerExpansionFraction);
mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE);
mHeaderQsPanel.setAlpha(1 - headerExpansionFraction);
mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE);
mAlarmStatus.setAlpha(headerExpansionFraction);
mAlarmStatusCollapsed.setAlpha(1 - headerExpansionFraction);
updateAlarmVisibilities();
float textScale = headerExpansionFraction * mDateScaleFactor;
mDateTimeGroup.setScaleX(1 + textScale);
mDateTimeGroup.setScaleY(1 + textScale);
mDateTimeGroup.setTranslationX(textScale * mDateTimeGroup.getWidth() / 2);
mDateTimeGroup.setTranslationY(textScale * mDateTimeGroup.getHeight() / 2);
updateDateTimePosition();
mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
}
mEmergencyOnly.setAlpha(headerExpansionFraction);
@Override
public void onAnimationAtStart() {
}
@Override
public void onAnimationAtEnd() {
mHeaderQsPanel.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationStarted() {
mHeaderQsPanel.setVisibility(View.VISIBLE);
}
private void updateAlarmVisibilities() {
mAlarmStatus.setVisibility(mAlarmShowing && mExpansionFraction > 0
? View.VISIBLE : View.INVISIBLE);
mAlarmStatusCollapsed.setVisibility(mAlarmShowing && mExpansionFraction < 1
? View.VISIBLE : View.INVISIBLE);
mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
}
private void updateDateTimePosition() {
float translation = mAlarmShowing ? mDateTimeAlarmTranslation
: mDateTimeTranslation;
mDateTimeAlarmGroup.setTranslationY(mExpansionFraction * translation);
// This one has its own because we have to rebuild it every time the alarm state changes.
mAlarmTranslation = new TouchAnimator.Builder()
.addFloat(mDateTimeAlarmGroup, "translationY", 0, mAlarmShowing
? mDateTimeAlarmTranslation : mDateTimeTranslation)
.build();
mAlarmTranslation.setPosition(mExpansionAmount);
}
public void setListening(boolean listening) {