blob: d5c5ba401e997ed106b1799cbc4d6a511e0e6ba4 [file] [log] [blame]
Jason Monkcaf37622015-08-18 12:33:50 -04001package com.android.systemui.qs;
2
3import android.content.Context;
Jason Monk32508852017-01-18 09:17:13 -05004import android.content.res.ColorStateList;
5import android.content.res.TypedArray;
Jason Monk61133972016-03-17 13:10:09 -04006import android.graphics.drawable.AnimatedVectorDrawable;
Jason Monkcaf37622015-08-18 12:33:50 -04007import android.util.AttributeSet;
Jason Monk61133972016-03-17 13:10:09 -04008import android.util.Log;
Jason Monkcaf37622015-08-18 12:33:50 -04009import android.view.View;
Jason Monk61133972016-03-17 13:10:09 -040010import android.view.ViewGroup;
11import android.widget.ImageView;
Gus Prevasab336792018-11-14 13:52:20 -050012
Jason Monkcaf37622015-08-18 12:33:50 -040013import com.android.systemui.R;
14
Jason Monk61133972016-03-17 13:10:09 -040015import java.util.ArrayList;
Jason Monkcaf37622015-08-18 12:33:50 -040016
Jason Monk61133972016-03-17 13:10:09 -040017public 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
Amin Shaikh62155922018-02-27 11:13:31 -050027 private static final float MINOR_ALPHA = .42f;
Jason Monk61133972016-03-17 13:10:09 -040028
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;
Jason Monkcaf37622015-08-18 12:33:50 -040037
38 public PageIndicator(Context context, AttributeSet attrs) {
39 super(context, attrs);
Jason Monk61133972016-03-17 13:10:09 -040040 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);
Jason Monkcaf37622015-08-18 12:33:50 -040045 }
46
47 public void setNumPages(int numPages) {
Rohan Shah3090e792018-04-12 00:01:00 -040048 setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
Jason Monk61133972016-03-17 13:10:09 -040049 if (mAnimating) {
50 Log.w(TAG, "setNumPages during animation");
51 }
Jason Monkcaf37622015-08-18 12:33:50 -040052 while (numPages < getChildCount()) {
53 removeViewAt(getChildCount() - 1);
54 }
Jason Monk32508852017-01-18 09:17:13 -050055 TypedArray array = getContext().obtainStyledAttributes(
Amin Shaikh62155922018-02-27 11:13:31 -050056 new int[]{android.R.attr.colorControlActivated});
Jason Monk32508852017-01-18 09:17:13 -050057 int color = array.getColor(0, 0);
58 array.recycle();
Jason Monkcaf37622015-08-18 12:33:50 -040059 while (numPages > getChildCount()) {
Jason Monk61133972016-03-17 13:10:09 -040060 ImageView v = new ImageView(mContext);
61 v.setImageResource(R.drawable.minor_a_b);
Jason Monk32508852017-01-18 09:17:13 -050062 v.setImageTintList(ColorStateList.valueOf(color));
Jason Monk61133972016-03-17 13:10:09 -040063 addView(v, new LayoutParams(mPageIndicatorWidth, mPageIndicatorHeight));
Jason Monkcaf37622015-08-18 12:33:50 -040064 }
Jason Monk61133972016-03-17 13:10:09 -040065 // Refresh state.
66 setIndex(mPosition >> 1);
Jason Monkcaf37622015-08-18 12:33:50 -040067 }
68
69 public void setLocation(float location) {
70 int index = (int) location;
Jason Monk2977ba22016-04-13 13:01:14 -040071 setContentDescription(getContext().getString(R.string.accessibility_quick_settings_page,
72 (index + 1), getChildCount()));
Jason Monk61133972016-03-17 13:10:09 -040073 int position = index << 1 | ((location != index) ? 1 : 0);
74 if (DEBUG) Log.d(TAG, "setLocation " + location + " " + index + " " + position);
Jason Monkcaf37622015-08-18 12:33:50 -040075
Jason Monk61133972016-03-17 13:10:09 -040076 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
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
101 private void setIndex(int index) {
Jason Monkcaf37622015-08-18 12:33:50 -0400102 final int N = getChildCount();
103 for (int i = 0; i < N; i++) {
Jason Monk61133972016-03-17 13:10:09 -0400104 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
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);
Selim Cinek85325582016-04-09 20:43:40 -0700131 if (first == null || second == null) {
132 // may happen during reInflation or other weird cases
Selim Cinek20e84c72016-04-19 09:47:20 -0700133 return;
Jason Monk339df072016-04-11 11:00:46 -0400134 }
Jason Monk61133972016-03-17 13:10:09 -0400135 // 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 }
Jason Monkcaf37622015-08-18 12:33:50 -0400175 }
Jason Monk61133972016-03-17 13:10:09 -0400176 } 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 }
Jason Monkcaf37622015-08-18 12:33:50 -0400190 }
191 }
192
Jason Monk61133972016-03-17 13:10:09 -0400193 @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;
Jason Monkcaf37622015-08-18 12:33:50 -0400199 }
Jason Monk61133972016-03-17 13:10:09 -0400200 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);
Jason Monkcaf37622015-08-18 12:33:50 -0400206 }
Jason Monk32508852017-01-18 09:17:13 -0500207 int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth;
Jason Monk61133972016-03-17 13:10:09 -0400208 setMeasuredDimension(width, mPageIndicatorHeight);
209 }
Jason Monkcaf37622015-08-18 12:33:50 -0400210
Jason Monk61133972016-03-17 13:10:09 -0400211 @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() {
Jason Monkcaf37622015-08-18 12:33:50 -0400224 @Override
Jason Monk61133972016-03-17 13:10:09 -0400225 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 }
Jason Monkcaf37622015-08-18 12:33:50 -0400231 }
Jason Monk61133972016-03-17 13:10:09 -0400232 };
Jason Monkcaf37622015-08-18 12:33:50 -0400233}