blob: b5a0a55e46582da206c595502e4a290666ce6687 [file] [log] [blame]
Jim Miller24ccf3c2009-11-10 21:37:45 -08001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.widget;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
Jim Miller521d4002009-11-15 16:19:24 -080022import android.graphics.Canvas;
Jim Miller24ccf3c2009-11-10 21:37:45 -080023import android.graphics.Rect;
Jim Miller753401a2009-11-11 17:53:53 -080024import android.graphics.drawable.Drawable;
Jim Miller24ccf3c2009-11-10 21:37:45 -080025import android.os.Handler;
26import android.os.Message;
27import android.os.Vibrator;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.view.Gravity;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewGroup;
Jim Miller521d4002009-11-15 16:19:24 -080034import android.view.animation.AlphaAnimation;
35import android.view.animation.Animation;
36import android.view.animation.AnimationSet;
37import android.view.animation.LinearInterpolator;
38import android.view.animation.TranslateAnimation;
39import android.view.animation.Animation.AnimationListener;
Jim Miller24ccf3c2009-11-10 21:37:45 -080040import android.widget.ImageView;
41import android.widget.TextView;
42import android.widget.ImageView.ScaleType;
43import com.android.internal.R;
44
45/**
46 * A special widget containing two Sliders and a threshold for each. Moving either slider beyond
Jim Miller425ca592009-11-13 19:20:28 -080047 * the threshold will cause the registered OnTriggerListener.onTrigger() to be called with
Jim Miller4df2c542009-11-12 03:39:14 -080048 * whichHandle being {@link OnTriggerListener#LEFT_HANDLE} or {@link OnTriggerListener#RIGHT_HANDLE}
Jim Miller425ca592009-11-13 19:20:28 -080049 * Equivalently, selecting a tab will result in a call to
Jim Miller4df2c542009-11-12 03:39:14 -080050 * {@link OnTriggerListener#onGrabbedStateChange(View, int)} with one of these two states. Releasing
51 * the tab will result in whichHandle being {@link OnTriggerListener#NO_HANDLE}.
Jim Miller24ccf3c2009-11-10 21:37:45 -080052 *
53 */
54public class SlidingTab extends ViewGroup {
55 private static final String LOG_TAG = "SlidingTab";
56 private static final boolean DBG = false;
57 private static final int HORIZONTAL = 0; // as defined in attrs.xml
58 private static final int VERTICAL = 1;
Jim Miller24ccf3c2009-11-10 21:37:45 -080059
60 // TODO: Make these configurable
Jim Miller4df2c542009-11-12 03:39:14 -080061 private static final float THRESHOLD = 2.0f / 3.0f;
Jim Miller24ccf3c2009-11-10 21:37:45 -080062 private static final long VIBRATE_SHORT = 30;
63 private static final long VIBRATE_LONG = 40;
Jim Miller521d4002009-11-15 16:19:24 -080064 private static final int TRACKING_MARGIN = 50;
65 private static final int ANIM_DURATION = 250; // Time for most animations (in ms)
66 private static final int ANIM_TARGET_TIME = 500; // Time to show targets (in ms)
Jim Miller24ccf3c2009-11-10 21:37:45 -080067
68 private OnTriggerListener mOnTriggerListener;
69 private int mGrabbedState = OnTriggerListener.NO_HANDLE;
70 private boolean mTriggered = false;
71 private Vibrator mVibrator;
72 private float mDensity; // used to scale dimensions for bitmaps.
73
Jim Miller24ccf3c2009-11-10 21:37:45 -080074 /**
75 * Either {@link #HORIZONTAL} or {@link #VERTICAL}.
76 */
77 private int mOrientation;
78
79 private Slider mLeftSlider;
80 private Slider mRightSlider;
81 private Slider mCurrentSlider;
82 private boolean mTracking;
Jim Miller4df2c542009-11-12 03:39:14 -080083 private float mThreshold;
Jim Miller24ccf3c2009-11-10 21:37:45 -080084 private Slider mOtherSlider;
85 private boolean mAnimating;
Jim Miller521d4002009-11-15 16:19:24 -080086 private Rect mTmpRect;
Jim Miller24ccf3c2009-11-10 21:37:45 -080087
88 /**
89 * Interface definition for a callback to be invoked when a tab is triggered
Jim Miller4df2c542009-11-12 03:39:14 -080090 * by moving it beyond a threshold.
Jim Miller24ccf3c2009-11-10 21:37:45 -080091 */
92 public interface OnTriggerListener {
93 /**
94 * The interface was triggered because the user let go of the handle without reaching the
Jim Miller4df2c542009-11-12 03:39:14 -080095 * threshold.
Jim Miller24ccf3c2009-11-10 21:37:45 -080096 */
97 public static final int NO_HANDLE = 0;
98
99 /**
100 * The interface was triggered because the user grabbed the left handle and moved it past
Jim Miller4df2c542009-11-12 03:39:14 -0800101 * the threshold.
Jim Miller24ccf3c2009-11-10 21:37:45 -0800102 */
103 public static final int LEFT_HANDLE = 1;
104
105 /**
106 * The interface was triggered because the user grabbed the right handle and moved it past
Jim Miller4df2c542009-11-12 03:39:14 -0800107 * the threshold.
Jim Miller24ccf3c2009-11-10 21:37:45 -0800108 */
109 public static final int RIGHT_HANDLE = 2;
110
111 /**
Jim Miller4df2c542009-11-12 03:39:14 -0800112 * Called when the user moves a handle beyond the threshold.
Jim Miller24ccf3c2009-11-10 21:37:45 -0800113 *
114 * @param v The view that was triggered.
115 * @param whichHandle Which "dial handle" the user grabbed,
116 * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}.
117 */
118 void onTrigger(View v, int whichHandle);
119
120 /**
121 * Called when the "grabbed state" changes (i.e. when the user either grabs or releases
122 * one of the handles.)
123 *
124 * @param v the view that was triggered
125 * @param grabbedState the new state: {@link #NO_HANDLE}, {@link #LEFT_HANDLE},
126 * or {@link #RIGHT_HANDLE}.
127 */
128 void onGrabbedStateChange(View v, int grabbedState);
129 }
130
Jim Miller521d4002009-11-15 16:19:24 -0800131 // TODO: For debugging; remove after glitches debugged.
132 @Override
133 protected void dispatchDraw(Canvas canvas) {
134 super.dispatchDraw(canvas);
135 }
136
Jim Miller24ccf3c2009-11-10 21:37:45 -0800137 /**
Jim Miller425ca592009-11-13 19:20:28 -0800138 * Simple container class for all things pertinent to a slider.
Jim Miller24ccf3c2009-11-10 21:37:45 -0800139 * A slider consists of 3 Views:
Jim Miller425ca592009-11-13 19:20:28 -0800140 *
Jim Miller24ccf3c2009-11-10 21:37:45 -0800141 * {@link #tab} is the tab shown on the screen in the default state.
142 * {@link #text} is the view revealed as the user slides the tab out.
143 * {@link #target} is the target the user must drag the slider past to trigger the slider.
144 *
145 */
146 private static class Slider {
147 /**
148 * Tab alignment - determines which side the tab should be drawn on
149 */
150 public static final int ALIGN_LEFT = 0;
151 public static final int ALIGN_RIGHT = 1;
152 public static final int ALIGN_TOP = 2;
153 public static final int ALIGN_BOTTOM = 3;
Jim Miller521d4002009-11-15 16:19:24 -0800154 public static final int ALIGN_UNKNOWN = 4;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800155
156 /**
157 * States for the view.
158 */
159 private static final int STATE_NORMAL = 0;
160 private static final int STATE_PRESSED = 1;
161 private static final int STATE_ACTIVE = 2;
162
163 private final ImageView tab;
164 private final TextView text;
165 private final ImageView target;
Jim Miller4df2c542009-11-12 03:39:14 -0800166 private int currentState = STATE_NORMAL;
Jim Miller521d4002009-11-15 16:19:24 -0800167 private int alignment = ALIGN_UNKNOWN;
168 private int alignment_value;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800169
170 /**
171 * Constructor
Jim Miller425ca592009-11-13 19:20:28 -0800172 *
Jim Miller24ccf3c2009-11-10 21:37:45 -0800173 * @param parent the container view of this one
174 * @param tabId drawable for the tab
175 * @param barId drawable for the bar
176 * @param targetId drawable for the target
177 */
178 Slider(ViewGroup parent, int tabId, int barId, int targetId) {
179 // Create tab
180 tab = new ImageView(parent.getContext());
181 tab.setBackgroundResource(tabId);
182 tab.setScaleType(ScaleType.CENTER);
183 tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
184 LayoutParams.WRAP_CONTENT));
185
186 // Create hint TextView
187 text = new TextView(parent.getContext());
188 text.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
189 LayoutParams.FILL_PARENT));
190 text.setBackgroundResource(barId);
191 text.setTextAppearance(parent.getContext(), R.style.TextAppearance_SlidingTabNormal);
192 // hint.setSingleLine(); // Hmm.. this causes the text to disappear off-screen
193
194 // Create target
195 target = new ImageView(parent.getContext());
196 target.setImageResource(targetId);
197 target.setScaleType(ScaleType.CENTER);
198 target.setLayoutParams(
199 new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
200 target.setVisibility(View.INVISIBLE);
201
202 parent.addView(target); // this needs to be first - relies on painter's algorithm
203 parent.addView(tab);
204 parent.addView(text);
205 }
206
207 void setIcon(int iconId) {
208 tab.setImageResource(iconId);
209 }
Jim Miller425ca592009-11-13 19:20:28 -0800210
Jim Miller24ccf3c2009-11-10 21:37:45 -0800211 void setTabBackgroundResource(int tabId) {
212 tab.setBackgroundResource(tabId);
213 }
Jim Miller425ca592009-11-13 19:20:28 -0800214
Jim Miller24ccf3c2009-11-10 21:37:45 -0800215 void setBarBackgroundResource(int barId) {
216 text.setBackgroundResource(barId);
217 }
Jim Miller425ca592009-11-13 19:20:28 -0800218
Jim Miller24ccf3c2009-11-10 21:37:45 -0800219 void setHintText(int resId) {
Jim Miller425ca592009-11-13 19:20:28 -0800220 text.setText(resId);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800221 }
222
223 void hide() {
Jim Miller521d4002009-11-15 16:19:24 -0800224 boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
225 int dx = horiz ? (alignment == ALIGN_LEFT ? alignment_value - tab.getRight()
226 : alignment_value - tab.getLeft()) : 0;
227 int dy = horiz ? 0 : (alignment == ALIGN_TOP ? alignment_value - tab.getBottom()
228 : alignment_value - tab.getTop());
229
230 Animation trans = new TranslateAnimation(0, dx, 0, dy);
231 trans.setDuration(ANIM_DURATION);
232 trans.setFillAfter(true);
233 tab.startAnimation(trans);
234 text.startAnimation(trans);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800235 target.setVisibility(View.INVISIBLE);
236 }
237
Jim Miller521d4002009-11-15 16:19:24 -0800238 void show(boolean animate) {
239 text.setVisibility(View.VISIBLE);
240 tab.setVisibility(View.VISIBLE);
241 //target.setVisibility(View.INVISIBLE);
242 if (animate) {
243 boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
244 int dx = horiz ? (alignment == ALIGN_LEFT ? tab.getWidth() : -tab.getWidth()) : 0;
245 int dy = horiz ? 0: (alignment == ALIGN_TOP ? tab.getHeight() : -tab.getHeight());
246
247 Animation trans = new TranslateAnimation(-dx, 0, -dy, 0);
248 trans.setDuration(ANIM_DURATION);
249 tab.startAnimation(trans);
250 text.startAnimation(trans);
251 }
252 }
253
Jim Miller24ccf3c2009-11-10 21:37:45 -0800254 void setState(int state) {
255 text.setPressed(state == STATE_PRESSED);
256 tab.setPressed(state == STATE_PRESSED);
257 if (state == STATE_ACTIVE) {
258 final int[] activeState = new int[] {com.android.internal.R.attr.state_active};
259 if (text.getBackground().isStateful()) {
260 text.getBackground().setState(activeState);
261 }
262 if (tab.getBackground().isStateful()) {
263 tab.getBackground().setState(activeState);
264 }
265 text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabActive);
266 } else {
267 text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
268 }
Jim Miller4df2c542009-11-12 03:39:14 -0800269 currentState = state;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800270 }
271
272 void showTarget() {
Jim Miller521d4002009-11-15 16:19:24 -0800273 AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
274 alphaAnim.setDuration(ANIM_TARGET_TIME);
275 target.startAnimation(alphaAnim);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800276 target.setVisibility(View.VISIBLE);
Jim Miller521d4002009-11-15 16:19:24 -0800277 target.startAnimation(alphaAnim);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800278 }
279
Jim Miller521d4002009-11-15 16:19:24 -0800280 void reset(boolean animate) {
Jim Miller24ccf3c2009-11-10 21:37:45 -0800281 setState(STATE_NORMAL);
282 text.setVisibility(View.VISIBLE);
283 text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
284 tab.setVisibility(View.VISIBLE);
285 target.setVisibility(View.INVISIBLE);
Jim Miller521d4002009-11-15 16:19:24 -0800286 final boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
287 int dx = horiz ? (alignment == ALIGN_LEFT ? alignment_value - tab.getLeft()
288 : alignment_value - tab.getRight()) : 0;
289 int dy = horiz ? 0 : (alignment == ALIGN_TOP ? alignment_value - tab.getTop()
290 : alignment_value - tab.getBottom());
291 if (animate) {
292 TranslateAnimation trans = new TranslateAnimation(0, dx, 0, dy);
293 trans.setDuration(ANIM_DURATION);
294 trans.setFillAfter(false);
295 text.startAnimation(trans);
296 tab.startAnimation(trans);
297 } else {
298 if (horiz) {
299 text.offsetLeftAndRight(dx);
300 tab.offsetLeftAndRight(dx);
301 } else {
302 text.offsetTopAndBottom(dy);
303 tab.offsetTopAndBottom(dy);
304 }
305 }
Jim Miller24ccf3c2009-11-10 21:37:45 -0800306 }
307
308 void setTarget(int targetId) {
309 target.setImageResource(targetId);
310 }
311
312 /**
313 * Layout the given widgets within the parent.
314 *
315 * @param l the parent's left border
316 * @param t the parent's top border
317 * @param r the parent's right border
318 * @param b the parent's bottom border
319 * @param alignment which side to align the widget to
320 */
321 void layout(int l, int t, int r, int b, int alignment) {
Jim Miller521d4002009-11-15 16:19:24 -0800322 this.alignment = alignment;
Jim Miller753401a2009-11-11 17:53:53 -0800323 final Drawable tabBackground = tab.getBackground();
324 final int handleWidth = tabBackground.getIntrinsicWidth();
325 final int handleHeight = tabBackground.getIntrinsicHeight();
326 final Drawable targetDrawable = target.getDrawable();
327 final int targetWidth = targetDrawable.getIntrinsicWidth();
328 final int targetHeight = targetDrawable.getIntrinsicHeight();
Jim Miller24ccf3c2009-11-10 21:37:45 -0800329 final int parentWidth = r - l;
330 final int parentHeight = b - t;
331
Jim Miller4df2c542009-11-12 03:39:14 -0800332 final int leftTarget = (int) (THRESHOLD * parentWidth) - targetWidth + handleWidth / 2;
333 final int rightTarget = (int) ((1.0f - THRESHOLD) * parentWidth) - handleWidth / 2;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800334 final int left = (parentWidth - handleWidth) / 2;
335 final int right = left + handleWidth;
336
337 if (alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT) {
338 // horizontal
339 final int targetTop = (parentHeight - targetHeight) / 2;
340 final int targetBottom = targetTop + targetHeight;
341 final int top = (parentHeight - handleHeight) / 2;
342 final int bottom = (parentHeight + handleHeight) / 2;
343 if (alignment == ALIGN_LEFT) {
344 tab.layout(0, top, handleWidth, bottom);
345 text.layout(0 - parentWidth, top, 0, bottom);
346 text.setGravity(Gravity.RIGHT);
347 target.layout(leftTarget, targetTop, leftTarget + targetWidth, targetBottom);
Jim Miller521d4002009-11-15 16:19:24 -0800348 alignment_value = l;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800349 } else {
350 tab.layout(parentWidth - handleWidth, top, parentWidth, bottom);
351 text.layout(parentWidth, top, parentWidth + parentWidth, bottom);
352 target.layout(rightTarget, targetTop, rightTarget + targetWidth, targetBottom);
353 text.setGravity(Gravity.TOP);
Jim Miller521d4002009-11-15 16:19:24 -0800354 alignment_value = r;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800355 }
356 } else {
357 // vertical
358 final int targetLeft = (parentWidth - targetWidth) / 2;
359 final int targetRight = (parentWidth + targetWidth) / 2;
Jim Miller4df2c542009-11-12 03:39:14 -0800360 final int top = (int) (THRESHOLD * parentHeight) + handleHeight / 2 - targetHeight;
361 final int bottom = (int) ((1.0f - THRESHOLD) * parentHeight) - handleHeight / 2;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800362 if (alignment == ALIGN_TOP) {
363 tab.layout(left, 0, right, handleHeight);
364 text.layout(left, 0 - parentHeight, right, 0);
365 target.layout(targetLeft, top, targetRight, top + targetHeight);
Jim Miller521d4002009-11-15 16:19:24 -0800366 alignment_value = t;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800367 } else {
368 tab.layout(left, parentHeight - handleHeight, right, parentHeight);
369 text.layout(left, parentHeight, right, parentHeight + parentHeight);
370 target.layout(targetLeft, bottom, targetRight, bottom + targetHeight);
Jim Miller521d4002009-11-15 16:19:24 -0800371 alignment_value = b;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800372 }
373 }
374 }
375
Jim Miller4df2c542009-11-12 03:39:14 -0800376 public void updateDrawableStates() {
377 setState(currentState);
378 }
379
Jim Miller4811d622009-11-12 17:45:07 -0800380 /**
381 * Ensure all the dependent widgets are measured.
382 */
383 public void measure() {
384 tab.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
385 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
386 text.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
387 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
Jim Miller24ccf3c2009-11-10 21:37:45 -0800388 }
389
Jim Miller4811d622009-11-12 17:45:07 -0800390 /**
391 * Get the measured tab width. Must be called after {@link Slider#measure()}.
392 * @return
393 */
394 public int getTabWidth() {
395 return tab.getMeasuredWidth();
396 }
397
398 /**
399 * Get the measured tab width. Must be called after {@link Slider#measure()}.
400 * @return
401 */
Jim Miller24ccf3c2009-11-10 21:37:45 -0800402 public int getTabHeight() {
Jim Miller4811d622009-11-12 17:45:07 -0800403 return tab.getMeasuredHeight();
Jim Miller24ccf3c2009-11-10 21:37:45 -0800404 }
Jim Miller521d4002009-11-15 16:19:24 -0800405
406 public void startAnimation(Animation animation) {
407 tab.startAnimation(animation);
408 text.startAnimation(animation);
409 target.setVisibility(View.GONE);
410 }
Jim Miller24ccf3c2009-11-10 21:37:45 -0800411 }
412
413 public SlidingTab(Context context) {
414 this(context, null);
415 }
416
417 /**
418 * Constructor used when this widget is created from a layout file.
419 */
420 public SlidingTab(Context context, AttributeSet attrs) {
421 super(context, attrs);
422
Jim Miller521d4002009-11-15 16:19:24 -0800423 // Allocate a temporary once that can be used everywhere.
424 mTmpRect = new Rect();
425
Jim Miller24ccf3c2009-11-10 21:37:45 -0800426 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingTab);
427 mOrientation = a.getInt(R.styleable.SlidingTab_orientation, HORIZONTAL);
428 a.recycle();
429
430 Resources r = getResources();
431 mDensity = r.getDisplayMetrics().density;
432 if (DBG) log("- Density: " + mDensity);
433
Jim Miller425ca592009-11-13 19:20:28 -0800434 mLeftSlider = new Slider(this,
435 R.drawable.jog_tab_left_generic,
Jim Miller24ccf3c2009-11-10 21:37:45 -0800436 R.drawable.jog_tab_bar_left_generic,
437 R.drawable.jog_tab_target_gray);
Jim Miller425ca592009-11-13 19:20:28 -0800438 mRightSlider = new Slider(this,
439 R.drawable.jog_tab_right_generic,
Jim Miller24ccf3c2009-11-10 21:37:45 -0800440 R.drawable.jog_tab_bar_right_generic,
441 R.drawable.jog_tab_target_gray);
442
443 // setBackgroundColor(0x80808080);
444 }
445
446 @Override
447 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
448 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
449 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
450
451 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
452 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
453
454 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
455 throw new RuntimeException(LOG_TAG + " cannot have UNSPECIFIED dimensions");
456 }
457
Jim Miller4811d622009-11-12 17:45:07 -0800458 mLeftSlider.measure();
459 mRightSlider.measure();
460 final int leftTabWidth = mLeftSlider.getTabWidth();
461 final int rightTabWidth = mRightSlider.getTabWidth();
462 final int leftTabHeight = mLeftSlider.getTabHeight();
463 final int rightTabHeight = mRightSlider.getTabHeight();
Jim Miller24ccf3c2009-11-10 21:37:45 -0800464 final int width;
465 final int height;
466 if (isHorizontal()) {
467 width = Math.max(widthSpecSize, leftTabWidth + rightTabWidth);
468 height = Math.max(leftTabHeight, rightTabHeight);
469 } else {
470 width = Math.max(leftTabWidth, rightTabHeight);
471 height = Math.max(heightSpecSize, leftTabHeight + rightTabHeight);
472 }
473 setMeasuredDimension(width, height);
474 }
475
476 @Override
477 public boolean onInterceptTouchEvent(MotionEvent event) {
478 final int action = event.getAction();
479 final float x = event.getX();
480 final float y = event.getY();
481
Jim Miller24ccf3c2009-11-10 21:37:45 -0800482 if (mAnimating) {
483 return false;
484 }
485
486 View leftHandle = mLeftSlider.tab;
Jim Miller521d4002009-11-15 16:19:24 -0800487 leftHandle.getHitRect(mTmpRect);
488 boolean leftHit = mTmpRect.contains((int) x, (int) y);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800489
490 View rightHandle = mRightSlider.tab;
Jim Miller521d4002009-11-15 16:19:24 -0800491 rightHandle.getHitRect(mTmpRect);
492 boolean rightHit = mTmpRect.contains((int)x, (int) y);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800493
494 if (!mTracking && !(leftHit || rightHit)) {
495 return false;
496 }
497
498 switch (action) {
499 case MotionEvent.ACTION_DOWN: {
500 mTracking = true;
501 mTriggered = false;
502 vibrate(VIBRATE_SHORT);
503 if (leftHit) {
504 mCurrentSlider = mLeftSlider;
505 mOtherSlider = mRightSlider;
Jim Miller4df2c542009-11-12 03:39:14 -0800506 mThreshold = isHorizontal() ? THRESHOLD : 1.0f - THRESHOLD;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800507 setGrabbedState(OnTriggerListener.LEFT_HANDLE);
508 } else {
509 mCurrentSlider = mRightSlider;
510 mOtherSlider = mLeftSlider;
Jim Miller4df2c542009-11-12 03:39:14 -0800511 mThreshold = isHorizontal() ? 1.0f - THRESHOLD : THRESHOLD;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800512 setGrabbedState(OnTriggerListener.RIGHT_HANDLE);
513 }
514 mCurrentSlider.setState(Slider.STATE_PRESSED);
515 mCurrentSlider.showTarget();
516 mOtherSlider.hide();
517 break;
518 }
519 }
520
521 return true;
522 }
523
524 @Override
525 public boolean onTouchEvent(MotionEvent event) {
526 if (mTracking) {
527 final int action = event.getAction();
528 final float x = event.getX();
529 final float y = event.getY();
Jim Miller521d4002009-11-15 16:19:24 -0800530
Jim Miller24ccf3c2009-11-10 21:37:45 -0800531 switch (action) {
532 case MotionEvent.ACTION_MOVE:
Jim Miller521d4002009-11-15 16:19:24 -0800533 if (withinView(x, y, this) ) {
534 moveHandle(x, y);
535 float position = isHorizontal() ? x : y;
536 float target = mThreshold * (isHorizontal() ? getWidth() : getHeight());
537 boolean thresholdReached;
538 if (isHorizontal()) {
539 thresholdReached = mCurrentSlider == mLeftSlider ?
540 position > target : position < target;
541 } else {
542 thresholdReached = mCurrentSlider == mLeftSlider ?
543 position < target : position > target;
544 }
545 if (!mTriggered && thresholdReached) {
546 mTriggered = true;
547 mTracking = false;
548 mCurrentSlider.setState(Slider.STATE_ACTIVE);
549 dispatchTriggerEvent(mCurrentSlider == mLeftSlider ?
550 OnTriggerListener.LEFT_HANDLE : OnTriggerListener.RIGHT_HANDLE);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800551
Jim Miller521d4002009-11-15 16:19:24 -0800552 startAnimating();
553 setGrabbedState(OnTriggerListener.NO_HANDLE);
554 }
Jim Miller24ccf3c2009-11-10 21:37:45 -0800555 break;
556 }
557 // Intentionally fall through - we're outside tracking rectangle
558
559 case MotionEvent.ACTION_UP:
560 case MotionEvent.ACTION_CANCEL:
561 mTracking = false;
562 mTriggered = false;
Jim Miller521d4002009-11-15 16:19:24 -0800563 mOtherSlider.show(true);
564 mCurrentSlider.reset(false);
565 mCurrentSlider = null;
566 mOtherSlider = null;
Jim Miller24ccf3c2009-11-10 21:37:45 -0800567 setGrabbedState(OnTriggerListener.NO_HANDLE);
568 break;
569 }
570 }
571
572 return mTracking || super.onTouchEvent(event);
573 }
574
Jim Miller521d4002009-11-15 16:19:24 -0800575 void startAnimating() {
576 mAnimating = true;
577 final Animation appear = new AlphaAnimation(0.5f, 1.0f); appear.setDuration(ANIM_DURATION);
578 final Animation trans;
579 Slider slider = mCurrentSlider;
580 int dx;
581 int dy;
582 if (isHorizontal()) {
583 int right = slider.tab.getRight();
584 int width = slider.tab.getWidth();
585 int left = slider.tab.getLeft();
586 int viewWidth = getWidth();
587 dx = slider == mRightSlider ? - (right + viewWidth - width)
588 : (viewWidth - left) + viewWidth - width;
589 dy = 0;
590 } else {
591 int top = slider.tab.getTop();
592 int bottom = slider.tab.getBottom();
593 int height = slider.tab.getHeight();
594 int viewHeight = getHeight();
595 dx = 0;
596 dy = slider == mRightSlider ? (top + viewHeight - height)
597 : - ((viewHeight - bottom) + viewHeight - height);
598 }
599 trans = new TranslateAnimation(0, dx, 0, dy);
600 trans.setDuration(ANIM_DURATION);
601 trans.setInterpolator(new LinearInterpolator());
602
603 trans.setAnimationListener(new AnimationListener() {
604 public void onAnimationEnd(Animation animation) {
605 resetView();
606 mLeftSlider.startAnimation(appear);
607 mRightSlider.startAnimation(appear);
608 mAnimating = false;
609 }
610
611 public void onAnimationRepeat(Animation animation) {
612
613 }
614
615 public void onAnimationStart(Animation animation) {
616
617 }
618
619 });
620
621 slider.startAnimation(trans);
622 }
623
624 private boolean withinView(final float x, final float y, final View view) {
625 return isHorizontal() && y > - TRACKING_MARGIN && y < TRACKING_MARGIN + view.getHeight()
626 || !isHorizontal() && x > -TRACKING_MARGIN && x < TRACKING_MARGIN + view.getWidth();
627 }
628
Jim Miller24ccf3c2009-11-10 21:37:45 -0800629 private boolean isHorizontal() {
630 return mOrientation == HORIZONTAL;
631 }
632
633 private void resetView() {
Jim Miller521d4002009-11-15 16:19:24 -0800634 mLeftSlider.reset(false);
635 mRightSlider.reset(false);
636 // onLayout(true, getLeft(), getTop(), getLeft() + getWidth(), getTop() + getHeight());
Jim Miller24ccf3c2009-11-10 21:37:45 -0800637 }
638
639 @Override
640 protected void onLayout(boolean changed, int l, int t, int r, int b) {
641 if (!changed) return;
642
643 // Center the widgets in the view
644 mLeftSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_LEFT : Slider.ALIGN_BOTTOM);
645 mRightSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_RIGHT : Slider.ALIGN_TOP);
646
647 invalidate(); // TODO: be more conservative about what we're invalidating
648 }
649
650 private void moveHandle(float x, float y) {
651 final View handle = mCurrentSlider.tab;
652 final View content = mCurrentSlider.text;
653 if (isHorizontal()) {
654 int deltaX = (int) x - handle.getLeft() - (handle.getWidth() / 2);
655 handle.offsetLeftAndRight(deltaX);
656 content.offsetLeftAndRight(deltaX);
657 } else {
658 int deltaY = (int) y - handle.getTop() - (handle.getHeight() / 2);
659 handle.offsetTopAndBottom(deltaY);
660 content.offsetTopAndBottom(deltaY);
661 }
662 invalidate(); // TODO: be more conservative about what we're invalidating
663 }
664
665 /**
666 * Sets the left handle icon to a given resource.
667 *
668 * The resource should refer to a Drawable object, or use 0 to remove
669 * the icon.
670 *
671 * @param iconId the resource ID of the icon drawable
672 * @param targetId the resource of the target drawable
673 * @param barId the resource of the bar drawable (stateful)
Jim Miller425ca592009-11-13 19:20:28 -0800674 * @param tabId the resource of the
Jim Miller24ccf3c2009-11-10 21:37:45 -0800675 */
676 public void setLeftTabResources(int iconId, int targetId, int barId, int tabId) {
677 mLeftSlider.setIcon(iconId);
Jim Miller425ca592009-11-13 19:20:28 -0800678 mLeftSlider.setTarget(targetId);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800679 mLeftSlider.setBarBackgroundResource(barId);
680 mLeftSlider.setTabBackgroundResource(tabId);
Jim Miller4df2c542009-11-12 03:39:14 -0800681 mLeftSlider.updateDrawableStates();
Jim Miller24ccf3c2009-11-10 21:37:45 -0800682 }
683
684 /**
685 * Sets the left handle hint text to a given resource string.
686 *
687 * @param resId
688 */
689 public void setLeftHintText(int resId) {
Jim Miller425ca592009-11-13 19:20:28 -0800690 if (isHorizontal()) {
691 mLeftSlider.setHintText(resId);
692 }
Jim Miller24ccf3c2009-11-10 21:37:45 -0800693 }
694
695 /**
696 * Sets the right handle icon to a given resource.
697 *
698 * The resource should refer to a Drawable object, or use 0 to remove
699 * the icon.
700 *
701 * @param iconId the resource ID of the icon drawable
702 * @param targetId the resource of the target drawable
703 * @param barId the resource of the bar drawable (stateful)
Jim Miller425ca592009-11-13 19:20:28 -0800704 * @param tabId the resource of the
Jim Miller24ccf3c2009-11-10 21:37:45 -0800705 */
706 public void setRightTabResources(int iconId, int targetId, int barId, int tabId) {
707 mRightSlider.setIcon(iconId);
Jim Miller425ca592009-11-13 19:20:28 -0800708 mRightSlider.setTarget(targetId);
Jim Miller24ccf3c2009-11-10 21:37:45 -0800709 mRightSlider.setBarBackgroundResource(barId);
710 mRightSlider.setTabBackgroundResource(tabId);
Jim Miller4df2c542009-11-12 03:39:14 -0800711 mRightSlider.updateDrawableStates();
Jim Miller24ccf3c2009-11-10 21:37:45 -0800712 }
713
714 /**
715 * Sets the left handle hint text to a given resource string.
716 *
717 * @param resId
718 */
719 public void setRightHintText(int resId) {
Jim Miller425ca592009-11-13 19:20:28 -0800720 if (isHorizontal()) {
721 mRightSlider.setHintText(resId);
722 }
Jim Miller24ccf3c2009-11-10 21:37:45 -0800723 }
724
725 /**
726 * Triggers haptic feedback.
727 */
728 private synchronized void vibrate(long duration) {
729 if (mVibrator == null) {
730 mVibrator = (android.os.Vibrator)
731 getContext().getSystemService(Context.VIBRATOR_SERVICE);
732 }
733 mVibrator.vibrate(duration);
734 }
735
736 /**
737 * Registers a callback to be invoked when the user triggers an event.
738 *
739 * @param listener the OnDialTriggerListener to attach to this view
740 */
741 public void setOnTriggerListener(OnTriggerListener listener) {
742 mOnTriggerListener = listener;
743 }
744
745 /**
746 * Dispatches a trigger event to listener. Ignored if a listener is not set.
747 * @param whichHandle the handle that triggered the event.
748 */
749 private void dispatchTriggerEvent(int whichHandle) {
750 vibrate(VIBRATE_LONG);
751 if (mOnTriggerListener != null) {
752 mOnTriggerListener.onTrigger(this, whichHandle);
753 }
754 }
755
756 /**
757 * Sets the current grabbed state, and dispatches a grabbed state change
758 * event to our listener.
759 */
760 private void setGrabbedState(int newState) {
761 if (newState != mGrabbedState) {
762 mGrabbedState = newState;
763 if (mOnTriggerListener != null) {
764 mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
765 }
766 }
767 }
768
Jim Miller24ccf3c2009-11-10 21:37:45 -0800769 private void log(String msg) {
770 Log.d(LOG_TAG, msg);
771 }
772}