Files
lawnchair/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
Sunny Goyal 1c581c6d61 Bug fix: QSB sometimes gets stuck to transparent.
At some places, we were calling removeAllListeners before calling cancel
 on an animation. AnimationListeners are also used to track states, and
 removing listeners before canceling will prevent onAnimationEnd to be
 called, thus preventing state cleanup.
 PinchAnimationManager was causing ZeroAlphaAnimatorListener to be removing
 from Qsb alpha animation, making the MultiStateAlphaController think there
 is a zeroAlpha animation running.

> Removing all instances of removeAllListeners
> Updating various affected listeners to handle onAnimatinoCancel
> Fixing WorkspaceStateTransitionAnimation, which was animation QSB alpha
  on page scroll index

Bug: 31910152
Change-Id: Ie7f31b67d4c502badcdd41f7b04867d1f35f5d27
2016-11-24 15:32:45 +05:30

338 lines
12 KiB
Java

/*
* 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.launcher3.pageindicators;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Property;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
/**
* {@link PageIndicator} which shows dots per page. The active page is shown with the current
* accent color.
*/
public class PageIndicatorDots extends PageIndicator {
private static final float SHIFT_PER_ANIMATION = 0.5f;
private static final float SHIFT_THRESHOLD = 0.1f;
private static final long ANIMATION_DURATION = 150;
private static final int ENTER_ANIMATION_START_DELAY = 300;
private static final int ENTER_ANIMATION_STAGGERED_DELAY = 150;
private static final int ENTER_ANIMATION_DURATION = 400;
// This value approximately overshoots to 1.5 times the original size.
private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
private static final RectF sTempRect = new RectF();
private static final Property<PageIndicatorDots, Float> CURRENT_POSITION
= new Property<PageIndicatorDots, Float>(float.class, "current_position") {
@Override
public Float get(PageIndicatorDots obj) {
return obj.mCurrentPosition;
}
@Override
public void set(PageIndicatorDots obj, Float pos) {
obj.mCurrentPosition = pos;
obj.invalidate();
obj.invalidateOutline();
}
};
private final Paint mCirclePaint;
private final float mDotRadius;
private final int mActiveColor;
private final int mInActiveColor;
private final boolean mIsRtl;
private int mActivePage;
/**
* The current position of the active dot including the animation progress.
* For ex:
* 0.0 => Active dot is at position 0
* 0.33 => Active dot is at position 0 and is moving towards 1
* 0.50 => Active dot is at position [0, 1]
* 0.77 => Active dot has left position 0 and is collapsing towards position 1
* 1.0 => Active dot is at position 1
*/
private float mCurrentPosition;
private float mFinalPosition;
private ObjectAnimator mAnimator;
private float[] mEntryAnimationRadiusFactors;
public PageIndicatorDots(Context context) {
this(context, null);
}
public PageIndicatorDots(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PageIndicatorDots(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setStyle(Style.FILL);
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
setOutlineProvider(new MyOutlineProver());
mActiveColor = Utilities.getColorAccent(context);
mInActiveColor = getResources().getColor(R.color.page_indicator_dot_color);
mIsRtl = Utilities.isRtl(getResources());
}
@Override
public void setScroll(int currentScroll, int totalScroll) {
if (mNumPages > 1) {
if (mIsRtl) {
currentScroll = totalScroll - currentScroll;
}
int scrollPerPage = totalScroll / (mNumPages - 1);
int pageToLeft = currentScroll / scrollPerPage;
int pageToLeftScroll = pageToLeft * scrollPerPage;
int pageToRightScroll = pageToLeftScroll + scrollPerPage;
float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
if (currentScroll < pageToLeftScroll + scrollThreshold) {
// scroll is within the left page's threshold
animateToPosition(pageToLeft);
} else if (currentScroll > pageToRightScroll - scrollThreshold) {
// scroll is far enough from left page to go to the right page
animateToPosition(pageToLeft + 1);
} else {
// scroll is between left and right page
animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
}
}
}
private void animateToPosition(float position) {
mFinalPosition = position;
if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
mCurrentPosition = mFinalPosition;
}
if (mAnimator == null && Float.compare(mCurrentPosition, mFinalPosition) != 0) {
float positionForThisAnim = mCurrentPosition > mFinalPosition ?
mCurrentPosition - SHIFT_PER_ANIMATION : mCurrentPosition + SHIFT_PER_ANIMATION;
mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim);
mAnimator.addListener(new AnimationCycleListener());
mAnimator.setDuration(ANIMATION_DURATION);
mAnimator.start();
}
}
public void stopAllAnimations() {
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
mFinalPosition = mActivePage;
CURRENT_POSITION.set(this, mFinalPosition);
}
/**
* Sets up up the page indicator to play the entry animation.
* {@link #playEntryAnimation()} must be called after this.
*/
public void prepareEntryAnimation() {
mEntryAnimationRadiusFactors = new float[mNumPages];
invalidate();
}
public void playEntryAnimation() {
int count = mEntryAnimationRadiusFactors.length;
if (count == 0) {
mEntryAnimationRadiusFactors = null;
invalidate();
return;
}
Interpolator interpolator = new OvershootInterpolator(ENTER_ANIMATION_OVERSHOOT_TENSION);
AnimatorSet animSet = new AnimatorSet();
for (int i = 0; i < count; i++) {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1).setDuration(ENTER_ANIMATION_DURATION);
final int index = i;
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mEntryAnimationRadiusFactors[index] = (Float) animation.getAnimatedValue();
invalidate();
}
});
anim.setInterpolator(interpolator);
anim.setStartDelay(ENTER_ANIMATION_START_DELAY + ENTER_ANIMATION_STAGGERED_DELAY * i);
animSet.play(anim);
}
animSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mEntryAnimationRadiusFactors = null;
invalidateOutline();
invalidate();
}
});
animSet.start();
}
@Override
public void setActiveMarker(int activePage) {
if (mActivePage != activePage) {
mActivePage = activePage;
}
}
@Override
protected void onPageCountChanged() {
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Add extra spacing of mDotRadius on all sides so than entry animation could be run.
int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius);
int height= MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ?
MeasureSpec.getSize(heightMeasureSpec) : (int) (4 * mDotRadius);
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
// Draw all page indicators;
float circleGap = 3 * mDotRadius;
float startX = (getWidth() - mNumPages * circleGap + mDotRadius) / 2;
float x = startX + mDotRadius;
float y = canvas.getHeight() / 2;
if (mEntryAnimationRadiusFactors != null) {
// During entry animation, only draw the circles
if (mIsRtl) {
x = getWidth() - x;
circleGap = -circleGap;
}
for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
mCirclePaint.setColor(i == mActivePage ? mActiveColor : mInActiveColor);
canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], mCirclePaint);
x += circleGap;
}
} else {
mCirclePaint.setColor(mInActiveColor);
for (int i = 0; i < mNumPages; i++) {
canvas.drawCircle(x, y, mDotRadius, mCirclePaint);
x += circleGap;
}
mCirclePaint.setColor(mActiveColor);
canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mCirclePaint);
}
}
private RectF getActiveRect() {
float startCircle = (int) mCurrentPosition;
float delta = mCurrentPosition - startCircle;
float diameter = 2 * mDotRadius;
float circleGap = 3 * mDotRadius;
float startX = (getWidth() - mNumPages * circleGap + mDotRadius) / 2;
sTempRect.top = getHeight() * 0.5f - mDotRadius;
sTempRect.bottom = getHeight() * 0.5f + mDotRadius;
sTempRect.left = startX + startCircle * circleGap;
sTempRect.right = sTempRect.left + diameter;
if (delta < SHIFT_PER_ANIMATION) {
// dot is capturing the right circle.
sTempRect.right += delta * circleGap * 2;
} else {
// Dot is leaving the left circle.
sTempRect.right += circleGap;
delta -= SHIFT_PER_ANIMATION;
sTempRect.left += delta * circleGap * 2;
}
if (mIsRtl) {
float rectWidth = sTempRect.width();
sTempRect.right = getWidth() - sTempRect.left;
sTempRect.left = sTempRect.right - rectWidth;
}
return sTempRect;
}
private class MyOutlineProver extends ViewOutlineProvider {
@Override
public void getOutline(View view, Outline outline) {
if (mEntryAnimationRadiusFactors == null) {
RectF activeRect = getActiveRect();
outline.setRoundRect(
(int) activeRect.left,
(int) activeRect.top,
(int) activeRect.right,
(int) activeRect.bottom,
mDotRadius
);
}
}
}
/**
* Listener for keep running the animation until the final state is reached.
*/
private class AnimationCycleListener extends AnimatorListenerAdapter {
private boolean mCancelled = false;
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
if (!mCancelled) {
mAnimator = null;
animateToPosition(mFinalPosition);
}
}
}
}