1 package com.android.systemui.qs; 2 3 import android.content.Context; 4 import android.content.res.ColorStateList; 5 import android.content.res.TypedArray; 6 import android.graphics.drawable.AnimatedVectorDrawable; 7 import android.util.AttributeSet; 8 import android.util.Log; 9 import android.view.View; 10 import android.view.ViewGroup; 11 import android.widget.ImageView; 12 13 import com.android.systemui.R; 14 15 import java.util.ArrayList; 16 17 public class PageIndicator extends ViewGroup { 18 19 private static final String TAG = "PageIndicator"; 20 private static final boolean DEBUG = false; 21 22 private static final long ANIMATION_DURATION = 250; 23 24 // The size of a single dot in relation to the whole animation. 25 private static final float SINGLE_SCALE = .4f; 26 27 private static final float MINOR_ALPHA = .42f; 28 29 private final ArrayList<Integer> mQueuedPositions = new ArrayList<>(); 30 31 private final int mPageIndicatorWidth; 32 private final int mPageIndicatorHeight; 33 private final int mPageDotWidth; 34 35 private int mPosition = -1; 36 private boolean mAnimating; 37 PageIndicator(Context context, AttributeSet attrs)38 public PageIndicator(Context context, AttributeSet attrs) { 39 super(context, attrs); 40 mPageIndicatorWidth = 41 (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_width); 42 mPageIndicatorHeight = 43 (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_height); 44 mPageDotWidth = (int) (mPageIndicatorWidth * SINGLE_SCALE); 45 } 46 setNumPages(int numPages)47 public void setNumPages(int numPages) { 48 setVisibility(numPages > 1 ? View.VISIBLE : View.GONE); 49 if (mAnimating) { 50 Log.w(TAG, "setNumPages during animation"); 51 } 52 while (numPages < getChildCount()) { 53 removeViewAt(getChildCount() - 1); 54 } 55 TypedArray array = getContext().obtainStyledAttributes( 56 new int[]{android.R.attr.colorControlActivated}); 57 int color = array.getColor(0, 0); 58 array.recycle(); 59 while (numPages > getChildCount()) { 60 ImageView v = new ImageView(mContext); 61 v.setImageResource(R.drawable.minor_a_b); 62 v.setImageTintList(ColorStateList.valueOf(color)); 63 addView(v, new LayoutParams(mPageIndicatorWidth, mPageIndicatorHeight)); 64 } 65 // Refresh state. 66 setIndex(mPosition >> 1); 67 } 68 setLocation(float location)69 public void setLocation(float location) { 70 int index = (int) location; 71 setContentDescription(getContext().getString(R.string.accessibility_quick_settings_page, 72 (index + 1), getChildCount())); 73 int position = index << 1 | ((location != index) ? 1 : 0); 74 if (DEBUG) Log.d(TAG, "setLocation " + location + " " + index + " " + position); 75 76 int lastPosition = mPosition; 77 if (mQueuedPositions.size() != 0) { 78 lastPosition = mQueuedPositions.get(mQueuedPositions.size() - 1); 79 } 80 if (position == lastPosition) return; 81 if (mAnimating) { 82 if (DEBUG) Log.d(TAG, "Queueing transition to " + Integer.toHexString(position)); 83 mQueuedPositions.add(position); 84 return; 85 } 86 87 setPosition(position); 88 } 89 setPosition(int position)90 private void setPosition(int position) { 91 if (isVisibleToUser() && Math.abs(mPosition - position) == 1) { 92 animate(mPosition, position); 93 } else { 94 if (DEBUG) Log.d(TAG, "Skipping animation " + isVisibleToUser() + " " + mPosition 95 + " " + position); 96 setIndex(position >> 1); 97 } 98 mPosition = position; 99 } 100 setIndex(int index)101 private void setIndex(int index) { 102 final int N = getChildCount(); 103 for (int i = 0; i < N; i++) { 104 ImageView v = (ImageView) getChildAt(i); 105 // Clear out any animation positioning. 106 v.setTranslationX(0); 107 v.setImageResource(R.drawable.major_a_b); 108 v.setAlpha(getAlpha(i == index)); 109 } 110 } 111 animate(int from, int to)112 private void animate(int from, int to) { 113 if (DEBUG) Log.d(TAG, "Animating from " + Integer.toHexString(from) + " to " 114 + Integer.toHexString(to)); 115 int fromIndex = from >> 1; 116 int toIndex = to >> 1; 117 118 // Set the position of everything, then we will manually control the two views involved 119 // in the animation. 120 setIndex(fromIndex); 121 122 boolean fromTransition = (from & 1) != 0; 123 boolean isAState = fromTransition ? from > to : from < to; 124 int firstIndex = Math.min(fromIndex, toIndex); 125 int secondIndex = Math.max(fromIndex, toIndex); 126 if (secondIndex == firstIndex) { 127 secondIndex++; 128 } 129 ImageView first = (ImageView) getChildAt(firstIndex); 130 ImageView second = (ImageView) getChildAt(secondIndex); 131 if (first == null || second == null) { 132 // may happen during reInflation or other weird cases 133 return; 134 } 135 // Lay the two views on top of each other. 136 second.setTranslationX(first.getX() - second.getX()); 137 138 playAnimation(first, getTransition(fromTransition, isAState, false)); 139 first.setAlpha(getAlpha(false)); 140 141 playAnimation(second, getTransition(fromTransition, isAState, true)); 142 second.setAlpha(getAlpha(true)); 143 144 mAnimating = true; 145 } 146 147 private float getAlpha(boolean isMajor) { 148 return isMajor ? 1 : MINOR_ALPHA; 149 } 150 151 private void playAnimation(ImageView imageView, int res) { 152 final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getContext().getDrawable(res); 153 imageView.setImageDrawable(avd); 154 avd.forceAnimationOnUI(); 155 avd.start(); 156 // TODO: Figure out how to user an AVD animation callback instead, which doesn't 157 // seem to be working right now... 158 postDelayed(mAnimationDone, ANIMATION_DURATION); 159 } 160 161 private int getTransition(boolean fromB, boolean isMajorAState, boolean isMajor) { 162 if (isMajor) { 163 if (fromB) { 164 if (isMajorAState) { 165 return R.drawable.major_b_a_animation; 166 } else { 167 return R.drawable.major_b_c_animation; 168 } 169 } else { 170 if (isMajorAState) { 171 return R.drawable.major_a_b_animation; 172 } else { 173 return R.drawable.major_c_b_animation; 174 } 175 } 176 } else { 177 if (fromB) { 178 if (isMajorAState) { 179 return R.drawable.minor_b_c_animation; 180 } else { 181 return R.drawable.minor_b_a_animation; 182 } 183 } else { 184 if (isMajorAState) { 185 return R.drawable.minor_c_b_animation; 186 } else { 187 return R.drawable.minor_a_b_animation; 188 } 189 } 190 } 191 } 192 193 @Override 194 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 195 final int N = getChildCount(); 196 if (N == 0) { 197 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 198 return; 199 } 200 final int widthChildSpec = MeasureSpec.makeMeasureSpec(mPageIndicatorWidth, 201 MeasureSpec.EXACTLY); 202 final int heightChildSpec = MeasureSpec.makeMeasureSpec(mPageIndicatorHeight, 203 MeasureSpec.EXACTLY); 204 for (int i = 0; i < N; i++) { 205 getChildAt(i).measure(widthChildSpec, heightChildSpec); 206 } 207 int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth; 208 setMeasuredDimension(width, mPageIndicatorHeight); 209 } 210 211 @Override 212 protected void onLayout(boolean changed, int l, int t, int r, int b) { 213 final int N = getChildCount(); 214 if (N == 0) { 215 return; 216 } 217 for (int i = 0; i < N; i++) { 218 int left = (mPageIndicatorWidth - mPageDotWidth) * i; 219 getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight); 220 } 221 } 222 223 private final Runnable mAnimationDone = new Runnable() { 224 @Override 225 public void run() { 226 if (DEBUG) Log.d(TAG, "onAnimationEnd - queued: " + mQueuedPositions.size()); 227 mAnimating = false; 228 if (mQueuedPositions.size() != 0) { 229 setPosition(mQueuedPositions.remove(0)); 230 } 231 } 232 }; 233 } 234