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