2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com
.weibo
.view
;
19 import android
.content
.Context
;
20 import android
.graphics
.Rect
;
21 import android
.graphics
.drawable
.Drawable
;
22 import android
.util
.AttributeSet
;
23 import android
.util
.Log
;
24 import android
.view
.MotionEvent
;
25 import android
.view
.VelocityTracker
;
26 import android
.view
.View
;
27 import android
.view
.ViewConfiguration
;
28 import android
.view
.ViewGroup
;
29 import android
.view
.animation
.Interpolator
;
30 import android
.widget
.Scroller
;
31 import android
.widget
.TextView
;
33 import com
.weibo
.sina
.TimeLineView
;
34 import com
.weibo
.sina
.R
;
37 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
38 * screen contains a number of icons, folders or widgets the user can interact with.
39 * A workspace is meant to be used with a fixed width only.
41 public class Workspace
extends ViewGroup
{
42 @SuppressWarnings({"UnusedDeclaration"})
43 private static final String TAG
= "TimeLineView.Workspace";
44 private static final int INVALID_SCREEN
= -1;
47 * The velocity at which a fling gesture will cause us to snap to the next screen
49 private static final int SNAP_VELOCITY
= 600;
50 private Drawable mPreviousIndicator
;
51 private Drawable mNextIndicator
;
53 private int mDefaultScreen
;
55 private boolean mFirstLayout
= true;
57 private int mCurrentScreen
;
58 private int mNextScreen
= INVALID_SCREEN
;
59 private Scroller mScroller
;
60 private VelocityTracker mVelocityTracker
;
61 private TextView mTitle
;
63 * CellInfo for the cell that is currently being dragged
67 * Target drop area calculated during last acceptDrop call.
70 private float mLastMotionX
;
71 private float mLastMotionY
;
73 private final static int TOUCH_STATE_REST
= 0;
74 private final static int TOUCH_STATE_SCROLLING
= 1;
76 private int mTouchState
= TOUCH_STATE_REST
;
79 public TimeLineView mLauncher
;
82 * Cache of vacant cells, used during drag events and invalidated as needed.
86 private boolean mAllowLongPress
= true;
88 private int mTouchSlop
;
89 private int mMaximumVelocity
;
91 private static final int INVALID_POINTER
= -1;
93 private int mActivePointerId
= INVALID_POINTER
;
95 private static final float NANOTIME_DIV
= 1000000000.0f
;
96 private static final float SMOOTHING_SPEED
= 0.75f
;
97 private static final float SMOOTHING_CONSTANT
= (float) (0.016 / Math
.log(SMOOTHING_SPEED
));
98 private float mSmoothingTime
;
99 private float mTouchX
;
101 private int[] mPages
;
102 private int[] mTitles
;
103 private WorkspaceOvershootInterpolator mScrollInterpolator
;
105 private static final float BASELINE_FLING_VELOCITY
= 2500.f
;
106 private static final float FLING_VELOCITY_INFLUENCE
= 0.4f
;
108 private static class WorkspaceOvershootInterpolator
implements Interpolator
{
109 private static final float DEFAULT_TENSION
= 1.3f
;
110 private float mTension
;
112 public WorkspaceOvershootInterpolator() {
113 mTension
= DEFAULT_TENSION
;
116 public void setDistance(int distance
) {
117 mTension
= distance
> 0 ? DEFAULT_TENSION
/ distance
: DEFAULT_TENSION
;
120 public void disableSettle() {
124 public float getInterpolation(float t
) {
125 // _o(t) = t * t * ((tension + 1) * t + tension)
126 // o(t) = _o(t - 1) + 1
128 return t
* t
* ((mTension
+ 1) * t
+ mTension
) + 1.0f
;
133 * Used to inflate the Workspace from XML.
135 * @param context The application's context.
136 * @param attrs The attribtues set containing the Workspace's customization values.
138 public Workspace(Context context
, AttributeSet attrs
) {
139 this(context
, attrs
, 0);
143 * Used to inflate the Workspace from XML.
145 * @param context The application's context.
146 * @param attrs The attribtues set containing the Workspace's customization values.
147 * @param defStyle Unused.
149 public Workspace(Context context
, AttributeSet attrs
, int defStyle
) {
150 super(context
, attrs
, defStyle
);
154 setHapticFeedbackEnabled(false);
159 * Initializes various states for this workspace.
161 private void initWorkspace() {
163 mPages
=new int[]{R
.id
.page0
,R
.id
.page1
,R
.id
.page2
,R
.id
.page3
,R
.id
.page4
};
164 mTitles
= new int[]{R
.string
.my_post
,R
.string
.news
,R
.string
.my_index
,R
.string
.funny
,R
.string
.my_post
};
166 Log
.e(TAG
,"initWorkspace");
167 Context context
= getContext();
168 mScrollInterpolator
= new WorkspaceOvershootInterpolator();
169 mScroller
= new Scroller(context
, mScrollInterpolator
);
170 mCurrentScreen
= mDefaultScreen
;
172 final ViewConfiguration configuration
= ViewConfiguration
.get(getContext());
173 mTouchSlop
= configuration
.getScaledTouchSlop();
174 mMaximumVelocity
= configuration
.getScaledMaximumFlingVelocity();
179 * Returns the index of the currently displayed screen.
181 * @return The index of the currently displayed screen.
183 public int getCurrentScreen() {
184 return mCurrentScreen
;
193 public void scrollTo(int x
, int y
) {
194 super.scrollTo(x
, y
);
196 mSmoothingTime
= System
.nanoTime() / NANOTIME_DIV
;
200 public void computeScroll() {
201 if (mScroller
.computeScrollOffset()) {
202 mTouchX
= mScrollX
= mScroller
.getCurrX();
203 mSmoothingTime
= System
.nanoTime() / NANOTIME_DIV
;
204 mScrollY
= mScroller
.getCurrY();
206 } else if (mNextScreen
!= INVALID_SCREEN
) {
207 mCurrentScreen
= Math
.max(0, Math
.min(mNextScreen
, getChildCount() - 1));
208 mNextScreen
= INVALID_SCREEN
;
209 //mTitle.setText(mTitles[mCurrentScreen]);
211 } else if (mTouchState
== TOUCH_STATE_SCROLLING
) {
212 final float now
= System
.nanoTime() / NANOTIME_DIV
;
213 final float e
= (float) Math
.exp((now
- mSmoothingTime
) / SMOOTHING_CONSTANT
);
214 final float dx
= mTouchX
- mScrollX
;
216 mSmoothingTime
= now
;
218 // Keep generating points as long as we're more than 1px away from the target
219 if (dx
> 1.f
|| dx
< -1.f
) {
227 protected void onAttachedToWindow() {
228 super.onAttachedToWindow();
233 protected void onMeasure(int widthMeasureSpec
, int heightMeasureSpec
) {
234 super.onMeasure(widthMeasureSpec
, heightMeasureSpec
);
235 final int width
= MeasureSpec
.getSize(widthMeasureSpec
);
236 final int widthMode
= MeasureSpec
.getMode(widthMeasureSpec
);
237 if (widthMode
!= MeasureSpec
.EXACTLY
) {
238 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
241 final int heightMode
= MeasureSpec
.getMode(heightMeasureSpec
);
242 if (heightMode
!= MeasureSpec
.EXACTLY
) {
243 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
246 // The children are given the same width and height as the workspace
247 final int count
= getChildCount();
248 for (int i
= 0; i
< count
; i
++) {
249 getChildAt(i
).measure(widthMeasureSpec
, heightMeasureSpec
);
254 setHorizontalScrollBarEnabled(false);
255 scrollTo(mCurrentScreen
* width
, 0);
256 setHorizontalScrollBarEnabled(true);
257 mFirstLayout
= false;
262 protected void onLayout(boolean changed
, int left
, int top
, int right
, int bottom
) {
264 final int count
= getChildCount();
265 for (int i
= 0; i
< count
; i
++) {
266 final View child
= getChildAt(i
);
267 if (child
.getVisibility() != View
.GONE
) {
268 final int childWidth
= child
.getMeasuredWidth();
269 child
.layout(childLeft
, 0, childLeft
+ childWidth
, child
.getMeasuredHeight());
270 childLeft
+= childWidth
;
276 public boolean requestChildRectangleOnScreen(View child
, Rect rectangle
, boolean immediate
) {
277 int screen
= indexOfChild(child
);
278 if (screen
!= mCurrentScreen
|| !mScroller
.isFinished()) {
287 public boolean dispatchUnhandledMove(View focused
, int direction
) {
288 if (direction
== View
.FOCUS_LEFT
) {
289 if (getCurrentScreen() > 0) {
290 snapToScreen(getCurrentScreen() - 1);
293 } else if (direction
== View
.FOCUS_RIGHT
) {
294 if (getCurrentScreen() < getChildCount() - 1) {
295 snapToScreen(getCurrentScreen() + 1);
299 return super.dispatchUnhandledMove(focused
, direction
);
304 public boolean dispatchTouchEvent(MotionEvent ev
) {
305 if (ev
.getAction() == MotionEvent
.ACTION_DOWN
) {
308 return super.dispatchTouchEvent(ev
);
312 public boolean onInterceptTouchEvent(MotionEvent ev
) {
316 * This method JUST determines whether we want to intercept the motion.
317 * If we return true, onTouchEvent will be called and we do the actual
322 * Shortcut the most recurring case: the user is in the dragging
323 * state and he is moving his finger. We want to intercept this
326 final int action
= ev
.getAction();
327 if ((action
== MotionEvent
.ACTION_MOVE
) && (mTouchState
!= TOUCH_STATE_REST
)) {
331 if (mVelocityTracker
== null) {
332 mVelocityTracker
= VelocityTracker
.obtain();
334 mVelocityTracker
.addMovement(ev
);
336 switch (action
& MotionEvent
.ACTION_MASK
) {
337 case MotionEvent
.ACTION_MOVE
: {
339 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
340 * whether the user has moved far enough from his original down touch.
344 * Locally do absolute value. mLastMotionX is set to the y value
347 final int pointerIndex
= ev
.findPointerIndex(mActivePointerId
);
348 final float x
= ev
.getX(pointerIndex
);
349 final float y
= ev
.getY(pointerIndex
);
350 final int xDiff
= (int) Math
.abs(x
- mLastMotionX
);
351 final int yDiff
= (int) Math
.abs(y
- mLastMotionY
);
353 final int touchSlop
= mTouchSlop
;
354 boolean xMoved
= xDiff
> touchSlop
;
355 boolean yMoved
= yDiff
> touchSlop
;
357 if (xMoved
|| yMoved
) {
360 // Scroll if the user moved far enough along the X axis
361 mTouchState
= TOUCH_STATE_SCROLLING
;
364 mSmoothingTime
= System
.nanoTime() / NANOTIME_DIV
;
365 //enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
367 // Either way, cancel any pending longpress
368 if (mAllowLongPress
) {
369 mAllowLongPress
= false;
370 // Try canceling the long press. It could also have been scheduled
371 // by a distant descendant, so use the mAllowLongPress flag to block
373 final View currentScreen
= getChildAt(mCurrentScreen
);
374 currentScreen
.cancelLongPress();
380 case MotionEvent
.ACTION_DOWN
: {
381 final float x
= ev
.getX();
382 final float y
= ev
.getY();
383 // Remember location of down touch
386 mActivePointerId
= ev
.getPointerId(0);
387 mAllowLongPress
= true;
390 * If being flinged and user touches the screen, initiate drag;
391 * otherwise don't. mScroller.isFinished should be false when
394 mTouchState
= mScroller
.isFinished() ? TOUCH_STATE_REST
: TOUCH_STATE_SCROLLING
;
398 case MotionEvent
.ACTION_CANCEL
:
399 case MotionEvent
.ACTION_UP
:
401 if (mTouchState
!= TOUCH_STATE_SCROLLING
) {
402 //final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
403 // if (!currentScreen.lastDownOnOccupiedCell()) {
404 // getLocationOnScreen(mTempCell);
405 // // Send a tap to the wallpaper if the last down was on empty space
406 // final int pointerIndex = ev.findPointerIndex(mActivePointerId);
407 // mWallpaperManager.sendWallpaperCommand(getWindowToken(),
408 // "android.wallpaper.tap",
409 // mTempCell[0] + (int) ev.getX(pointerIndex),
410 // mTempCell[1] + (int) ev.getY(pointerIndex), 0, null);
414 mTouchState
= TOUCH_STATE_REST
;
415 mActivePointerId
= INVALID_POINTER
;
416 mAllowLongPress
= false;
418 if (mVelocityTracker
!= null) {
419 mVelocityTracker
.recycle();
420 mVelocityTracker
= null;
425 case MotionEvent
.ACTION_POINTER_UP
:
426 onSecondaryPointerUp(ev
);
431 * The only time we want to intercept motion events is if we are in the
434 return mTouchState
!= TOUCH_STATE_REST
;
437 private void onSecondaryPointerUp(MotionEvent ev
) {
438 final int pointerIndex
= (ev
.getAction() & MotionEvent
.ACTION_POINTER_INDEX_MASK
) >>
439 MotionEvent
.ACTION_POINTER_INDEX_SHIFT
;
440 final int pointerId
= ev
.getPointerId(pointerIndex
);
441 if (pointerId
== mActivePointerId
) {
442 // This was our active pointer going up. Choose a new
443 // active pointer and adjust accordingly.
444 // TODO: Make this decision more intelligent.
445 final int newPointerIndex
= pointerIndex
== 0 ?
1 : 0;
446 mLastMotionX
= ev
.getX(newPointerIndex
);
447 mLastMotionY
= ev
.getY(newPointerIndex
);
448 mActivePointerId
= ev
.getPointerId(newPointerIndex
);
449 if (mVelocityTracker
!= null) {
450 mVelocityTracker
.clear();
456 * If one of our descendant views decides that it could be focused now, only
457 * pass that along if it's on the current screen.
459 * This happens when live folders requery, and if they're off screen, they
460 * end up calling requestFocus, which pulls it on screen.
465 public boolean onTouchEvent(MotionEvent ev
) {
466 // if (mLauncher.isWorkspaceLocked()) {
467 // return false; // We don't want the events. Let them fall through to the all apps view.
471 if (mVelocityTracker
== null) {
472 mVelocityTracker
= VelocityTracker
.obtain();
474 mVelocityTracker
.addMovement(ev
);
476 final int action
= ev
.getAction();
478 switch (action
& MotionEvent
.ACTION_MASK
) {
479 case MotionEvent
.ACTION_DOWN
:
481 * If being flinged and user touches, stop the fling. isFinished
482 * will be false if being flinged.
484 if (!mScroller
.isFinished()) {
485 mScroller
.abortAnimation();
488 // Remember where the motion event started
489 mLastMotionX
= ev
.getX();
490 mActivePointerId
= ev
.getPointerId(0);
491 if (mTouchState
== TOUCH_STATE_SCROLLING
) {
492 //enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
495 case MotionEvent
.ACTION_MOVE
:
496 if (mTouchState
== TOUCH_STATE_SCROLLING
) {
497 // Scroll to follow the motion event
498 final int pointerIndex
= ev
.findPointerIndex(mActivePointerId
);
499 final float x
= ev
.getX(pointerIndex
);
500 final float deltaX
= mLastMotionX
- x
;
505 mTouchX
+= Math
.max(-mTouchX
, deltaX
);
506 mSmoothingTime
= System
.nanoTime() / NANOTIME_DIV
;
509 } else if (deltaX
> 0) {
510 final float availableToScroll
= getChildAt(getChildCount() - 1).getRight() -
511 mTouchX
- getWidth();
512 if (availableToScroll
> 0) {
513 mTouchX
+= Math
.min(availableToScroll
, deltaX
);
514 mSmoothingTime
= System
.nanoTime() / NANOTIME_DIV
;
521 mTouchState
= TOUCH_STATE_SCROLLING
;
523 case MotionEvent
.ACTION_UP
:
524 if (mTouchState
== TOUCH_STATE_SCROLLING
) {
525 final VelocityTracker velocityTracker
= mVelocityTracker
;
526 velocityTracker
.computeCurrentVelocity(1000, mMaximumVelocity
);
527 final int velocityX
= (int) velocityTracker
.getXVelocity(mActivePointerId
);
529 final int screenWidth
= getWidth();
530 final int whichScreen
= (mScrollX
+ (screenWidth
/ 2)) / screenWidth
;
531 final float scrolledPos
= (float) mScrollX
/ screenWidth
;
533 if (velocityX
> SNAP_VELOCITY
&& mCurrentScreen
> 0) {
534 // Fling hard enough to move left.
535 // Don't fling across more than one screen at a time.
536 final int bound
= scrolledPos
< whichScreen ?
537 mCurrentScreen
- 1 : mCurrentScreen
;
538 snapToScreen(Math
.min(whichScreen
, bound
), velocityX
, true);
539 } else if (velocityX
< -SNAP_VELOCITY
&& mCurrentScreen
< getChildCount() - 1) {
540 // Fling hard enough to move right
541 // Don't fling across more than one screen at a time.
542 final int bound
= scrolledPos
> whichScreen ?
543 mCurrentScreen
+ 1 : mCurrentScreen
;
544 snapToScreen(Math
.max(whichScreen
, bound
), velocityX
, true);
546 snapToScreen(whichScreen
, 0, true);
549 if (mVelocityTracker
!= null) {
550 mVelocityTracker
.recycle();
551 mVelocityTracker
= null;
554 mTouchState
= TOUCH_STATE_REST
;
555 mActivePointerId
= INVALID_POINTER
;
557 case MotionEvent
.ACTION_CANCEL
:
558 mTouchState
= TOUCH_STATE_REST
;
559 mActivePointerId
= INVALID_POINTER
;
561 case MotionEvent
.ACTION_POINTER_UP
:
562 onSecondaryPointerUp(ev
);
569 void snapToScreen(int whichScreen
) {
570 snapToScreen(whichScreen
, 0, false);
573 private void snapToScreen(int whichScreen
, int velocity
, boolean settle
) {
574 //if (!mScroller.isFinished()) return;
575 whichScreen
= Math
.max(0, Math
.min(whichScreen
, getChildCount() - 1));
578 mNextScreen
= whichScreen
;
582 // mPreviousIndicator.setLevel(mNextScreen);
583 // mNextIndicator.setLevel(mNextScreen);
584 View focusedChild
= getFocusedChild();
585 if (focusedChild
!= null && whichScreen
!= mCurrentScreen
&&
586 focusedChild
== getChildAt(mCurrentScreen
)) {
587 focusedChild
.clearFocus();
590 final int screenDelta
= Math
.max(1, Math
.abs(whichScreen
- mCurrentScreen
));
591 final int newX
= whichScreen
* getWidth();
592 final int delta
= newX
- mScrollX
;
593 int duration
= (screenDelta
+ 1) * 100;
595 if (!mScroller
.isFinished()) {
596 mScroller
.abortAnimation();
600 velocity
= Math
.abs(velocity
);
602 duration
+= (duration
/ (velocity
/ BASELINE_FLING_VELOCITY
))
603 * FLING_VELOCITY_INFLUENCE
;
608 awakenScrollBars(duration
);
609 mScroller
.startScroll(mScrollX
, 0, delta
, 0, duration
);
618 public void scrollLeft() {
619 if (mScroller
.isFinished()) {
620 if (mCurrentScreen
> 0) snapToScreen(mCurrentScreen
- 1);
622 if (mNextScreen
> 0) snapToScreen(mNextScreen
- 1);
626 public void scrollRight() {
627 if (mScroller
.isFinished()) {
628 if (mCurrentScreen
< getChildCount() -1) snapToScreen(mCurrentScreen
+ 1);
630 if (mNextScreen
< getChildCount() -1) snapToScreen(mNextScreen
+ 1);
635 public void setIndicators(Drawable previous
, Drawable next
,TextView title
) {
636 mPreviousIndicator
= previous
;
637 mNextIndicator
= next
;
638 previous
.setLevel(mCurrentScreen
);
639 next
.setLevel(mCurrentScreen
);
641 mTitle
.setText(mTitles
[mCurrentScreen
]);
644 public int getPage(int currentScreen
){
645 return mPages
[currentScreen
];