| /* |
| * Copyright (C) 2018 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.wallpaper.widget; |
| |
| import android.content.Context; |
| import android.content.res.ColorStateList; |
| import android.content.res.TypedArray; |
| import android.graphics.drawable.Animatable2; |
| import android.graphics.drawable.AnimatedVectorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ImageView; |
| |
| import com.android.wallpaper.R; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| |
| /** |
| * Page indicator widget, based on QS's page indicator: |
| * |
| * Based on QS PageIndicator |
| * Path: frameworks/base/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java |
| */ |
| public class PageIndicator extends ViewGroup { |
| |
| private static final String TAG = "PageIndicator"; |
| private static final boolean DEBUG = false; |
| |
| // The size of a single dot in relation to the whole animation. |
| private static final float SINGLE_SCALE = .4f; |
| |
| static final float MINOR_ALPHA = .42f; |
| |
| private final ArrayList<Integer> mQueuedPositions = new ArrayList<>(); |
| |
| private final int mPageIndicatorWidth; |
| private final int mPageIndicatorHeight; |
| private final int mPageDotWidth; |
| |
| private int mPosition = -1; |
| private boolean mAnimating; |
| |
| private static Method sMethodForceAnimationOnUI = null; |
| private final Animatable2.AnimationCallback mAnimationCallback = |
| new Animatable2.AnimationCallback() { |
| |
| @Override |
| public void onAnimationEnd(Drawable drawable) { |
| super.onAnimationEnd(drawable); |
| if (drawable instanceof AnimatedVectorDrawable) { |
| ((AnimatedVectorDrawable) drawable).unregisterAnimationCallback( |
| mAnimationCallback); |
| } |
| if (DEBUG) { |
| Log.d(TAG, "onAnimationEnd - queued: " + mQueuedPositions.size()); |
| } |
| mAnimating = false; |
| if (mQueuedPositions.size() != 0) { |
| setPosition(mQueuedPositions.remove(0)); |
| } |
| } |
| }; |
| |
| |
| public PageIndicator(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| mPageIndicatorWidth = |
| (int) context.getResources().getDimension(R.dimen.preview_indicator_width); |
| mPageIndicatorHeight = |
| (int) context.getResources().getDimension(R.dimen.preview_indicator_height); |
| mPageDotWidth = (int) (mPageIndicatorWidth * SINGLE_SCALE); |
| } |
| |
| public void setNumPages(int numPages) { |
| setVisibility(numPages > 1 ? View.VISIBLE : View.INVISIBLE); |
| if (mAnimating) { |
| Log.w(TAG, "setNumPages during animation"); |
| } |
| while (numPages < getChildCount()) { |
| removeViewAt(getChildCount() - 1); |
| } |
| TypedArray array = getContext().obtainStyledAttributes( |
| new int[]{android.R.attr.colorControlActivated}); |
| int color = array.getColor(0, 0); |
| array.recycle(); |
| while (numPages > getChildCount()) { |
| ImageView v = new ImageView(getContext()); |
| v.setImageResource(R.drawable.minor_a_b); |
| v.setImageTintList(ColorStateList.valueOf(color)); |
| addView(v, new LayoutParams(mPageIndicatorWidth, mPageIndicatorHeight)); |
| } |
| // Refresh state. |
| setIndex(mPosition >> 1); |
| } |
| |
| public void setLocation(float location) { |
| int index = (int) location; |
| setContentDescription(getContext().getString(R.string.accessibility_preview_pager, |
| (index + 1), getChildCount())); |
| int position = index << 1 | ((location != index) ? 1 : 0); |
| if (DEBUG) { |
| Log.d(TAG, "setLocation " + location + " " + index + " " + position); |
| } |
| |
| int lastPosition = mPosition; |
| if (mQueuedPositions.size() != 0) { |
| lastPosition = mQueuedPositions.get(mQueuedPositions.size() - 1); |
| } |
| if (DEBUG) { |
| Log.d(TAG, position + " " + lastPosition); |
| } |
| if (position == lastPosition) return; |
| if (mAnimating) { |
| if (DEBUG) { |
| Log.d(TAG, "Queueing transition to " + Integer.toHexString(position)); |
| } |
| mQueuedPositions.add(position); |
| return; |
| } |
| |
| setPosition(position); |
| } |
| |
| private void setPosition(int position) { |
| if (mPosition >= 0 && Math.abs(mPosition - position) == 1) { |
| animate(mPosition, position); |
| } else { |
| if (DEBUG) { |
| Log.d(TAG, "Skipping animation " + mPosition |
| + " " + position); |
| } |
| setIndex(position >> 1); |
| } |
| mPosition = position; |
| } |
| |
| private void setIndex(int index) { |
| final int N = getChildCount(); |
| for (int i = 0; i < N; i++) { |
| ImageView v = (ImageView) getChildAt(i); |
| // Clear out any animation positioning. |
| v.setTranslationX(0); |
| v.setImageResource(R.drawable.major_a_b); |
| v.setAlpha(getAlpha(i == index)); |
| } |
| } |
| |
| private void animate(int from, int to) { |
| if (DEBUG) { |
| Log.d(TAG, "Animating from " + Integer.toHexString(from) + " to " |
| + Integer.toHexString(to)); |
| } |
| int fromIndex = from >> 1; |
| int toIndex = to >> 1; |
| |
| // Set the position of everything, then we will manually control the two views involved |
| // in the animation. |
| setIndex(fromIndex); |
| |
| boolean fromTransition = (from & 1) != 0; |
| boolean isAState = fromTransition ? from > to : from < to; |
| int firstIndex = Math.min(fromIndex, toIndex); |
| int secondIndex = Math.max(fromIndex, toIndex); |
| if (secondIndex == firstIndex) { |
| secondIndex++; |
| } |
| ImageView first = (ImageView) getChildAt(firstIndex); |
| ImageView second = (ImageView) getChildAt(secondIndex); |
| if (first == null || second == null) { |
| // may happen during reInflation or other weird cases |
| return; |
| } |
| // Lay the two views on top of each other. |
| second.setTranslationX(first.getX() - second.getX()); |
| |
| playAnimation(first, getTransition(fromTransition, isAState, false)); |
| first.setAlpha(getAlpha(false)); |
| |
| playAnimation(second, getTransition(fromTransition, isAState, true)); |
| second.setAlpha(getAlpha(true)); |
| |
| mAnimating = true; |
| } |
| |
| private float getAlpha(boolean isMajor) { |
| return isMajor ? 1 : MINOR_ALPHA; |
| } |
| |
| private void playAnimation(ImageView imageView, int res) { |
| Drawable drawable = getContext().getDrawable(res); |
| if (!(drawable instanceof AnimatedVectorDrawable)) { |
| return; |
| } |
| final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) drawable; |
| imageView.setImageDrawable(avd); |
| try { |
| forceAnimationOnUI(avd); |
| } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { |
| Log.e(TAG, "Catch an exception in playAnimation", e); |
| } |
| avd.registerAnimationCallback(mAnimationCallback); |
| avd.start(); |
| } |
| |
| private void forceAnimationOnUI(AnimatedVectorDrawable avd) |
| throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { |
| if (sMethodForceAnimationOnUI == null) { |
| sMethodForceAnimationOnUI = AnimatedVectorDrawable.class.getMethod( |
| "forceAnimationOnUI"); |
| } |
| if (sMethodForceAnimationOnUI != null) { |
| sMethodForceAnimationOnUI.invoke(avd); |
| } |
| } |
| |
| private int getTransition(boolean fromB, boolean isMajorAState, boolean isMajor) { |
| if (isMajor) { |
| if (fromB) { |
| if (isMajorAState) { |
| return R.drawable.major_b_a_animation; |
| } else { |
| return R.drawable.major_b_c_animation; |
| } |
| } else { |
| if (isMajorAState) { |
| return R.drawable.major_a_b_animation; |
| } else { |
| return R.drawable.major_c_b_animation; |
| } |
| } |
| } else { |
| if (fromB) { |
| if (isMajorAState) { |
| return R.drawable.minor_b_c_animation; |
| } else { |
| return R.drawable.minor_b_a_animation; |
| } |
| } else { |
| if (isMajorAState) { |
| return R.drawable.minor_c_b_animation; |
| } else { |
| return R.drawable.minor_a_b_animation; |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| final int N = getChildCount(); |
| if (N == 0) { |
| super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| return; |
| } |
| final int widthChildSpec = MeasureSpec.makeMeasureSpec(mPageIndicatorWidth, |
| MeasureSpec.EXACTLY); |
| final int heightChildSpec = MeasureSpec.makeMeasureSpec(mPageIndicatorHeight, |
| MeasureSpec.EXACTLY); |
| for (int i = 0; i < N; i++) { |
| getChildAt(i).measure(widthChildSpec, heightChildSpec); |
| } |
| int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth; |
| setMeasuredDimension(width, mPageIndicatorHeight); |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| final int N = getChildCount(); |
| if (N == 0) { |
| return; |
| } |
| for (int i = 0; i < N; i++) { |
| int left = (mPageIndicatorWidth - mPageDotWidth) * i; |
| getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight); |
| } |
| } |
| } |