update ui layout
[freespace.git] / weibosrc / com / weibo / view / Workspace.java
blobba7999c7a7541b776d92afe7663a580f90da57f8
1 /*
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;
36 /**
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;
46 /**
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;
62 /**
63 * CellInfo for the cell that is currently being dragged
66 /**
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;
81 /**
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() {
121 mTension = 0.f;
124 public float getInterpolation(float t) {
125 // _o(t) = t * t * ((tension + 1) * t + tension)
126 // o(t) = _o(t - 1) + 1
127 t -= 1.0f;
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);
152 mDefaultScreen = 2;
154 setHapticFeedbackEnabled(false);
155 initWorkspace();
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;
192 @Override
193 public void scrollTo(int x, int y) {
194 super.scrollTo(x, y);
195 mTouchX = x;
196 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
199 @Override
200 public void computeScroll() {
201 if (mScroller.computeScrollOffset()) {
202 mTouchX = mScrollX = mScroller.getCurrX();
203 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
204 mScrollY = mScroller.getCurrY();
205 postInvalidate();
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;
215 mScrollX += dx * e;
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) {
220 postInvalidate();
227 protected void onAttachedToWindow() {
228 super.onAttachedToWindow();
229 computeScroll();
232 @Override
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);
253 if (mFirstLayout) {
254 setHorizontalScrollBarEnabled(false);
255 scrollTo(mCurrentScreen * width, 0);
256 setHorizontalScrollBarEnabled(true);
257 mFirstLayout = false;
261 @Override
262 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
263 int childLeft = 0;
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;
275 @Override
276 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
277 int screen = indexOfChild(child);
278 if (screen != mCurrentScreen || !mScroller.isFinished()) {
280 return true;
282 return false;
286 @Override
287 public boolean dispatchUnhandledMove(View focused, int direction) {
288 if (direction == View.FOCUS_LEFT) {
289 if (getCurrentScreen() > 0) {
290 snapToScreen(getCurrentScreen() - 1);
291 return true;
293 } else if (direction == View.FOCUS_RIGHT) {
294 if (getCurrentScreen() < getChildCount() - 1) {
295 snapToScreen(getCurrentScreen() + 1);
296 return true;
299 return super.dispatchUnhandledMove(focused, direction);
303 @Override
304 public boolean dispatchTouchEvent(MotionEvent ev) {
305 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
308 return super.dispatchTouchEvent(ev);
311 @Override
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
318 * scrolling there.
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
324 * motion.
326 final int action = ev.getAction();
327 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
328 return true;
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
345 * of the down event.
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) {
359 if (xMoved) {
360 // Scroll if the user moved far enough along the X axis
361 mTouchState = TOUCH_STATE_SCROLLING;
362 mLastMotionX = x;
363 mTouchX = mScrollX;
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
372 // everything
373 final View currentScreen = getChildAt(mCurrentScreen);
374 currentScreen.cancelLongPress();
377 break;
380 case MotionEvent.ACTION_DOWN: {
381 final float x = ev.getX();
382 final float y = ev.getY();
383 // Remember location of down touch
384 mLastMotionX = x;
385 mLastMotionY = y;
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
392 * being flinged.
394 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
395 break;
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);
411 // }
414 mTouchState = TOUCH_STATE_REST;
415 mActivePointerId = INVALID_POINTER;
416 mAllowLongPress = false;
418 if (mVelocityTracker != null) {
419 mVelocityTracker.recycle();
420 mVelocityTracker = null;
423 break;
425 case MotionEvent.ACTION_POINTER_UP:
426 onSecondaryPointerUp(ev);
427 break;
431 * The only time we want to intercept motion events is if we are in the
432 * drag mode.
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.
464 @Override
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.
468 // }
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);
494 break;
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;
501 mLastMotionX = x;
503 if (deltaX < 0) {
504 if (mTouchX > 0) {
505 mTouchX += Math.max(-mTouchX, deltaX);
506 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
507 invalidate();
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;
515 invalidate();
517 } else {
518 awakenScrollBars();
521 mTouchState = TOUCH_STATE_SCROLLING;
522 break;
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);
545 } else {
546 snapToScreen(whichScreen, 0, true);
549 if (mVelocityTracker != null) {
550 mVelocityTracker.recycle();
551 mVelocityTracker = null;
554 mTouchState = TOUCH_STATE_REST;
555 mActivePointerId = INVALID_POINTER;
556 break;
557 case MotionEvent.ACTION_CANCEL:
558 mTouchState = TOUCH_STATE_REST;
559 mActivePointerId = INVALID_POINTER;
560 break;
561 case MotionEvent.ACTION_POINTER_UP:
562 onSecondaryPointerUp(ev);
563 break;
566 return true;
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);
601 if (velocity > 0) {
602 duration += (duration / (velocity / BASELINE_FLING_VELOCITY))
603 * FLING_VELOCITY_INFLUENCE;
604 } else {
605 duration += 100;
608 awakenScrollBars(duration);
609 mScroller.startScroll(mScrollX, 0, delta, 0, duration);
610 invalidate();
616 * {@inheritDoc}
618 public void scrollLeft() {
619 if (mScroller.isFinished()) {
620 if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);
621 } else {
622 if (mNextScreen > 0) snapToScreen(mNextScreen - 1);
626 public void scrollRight() {
627 if (mScroller.isFinished()) {
628 if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1);
629 } else {
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);
640 mTitle = title;
641 mTitle.setText(mTitles[mCurrentScreen]);
644 public int getPage(int currentScreen){
645 return mPages[currentScreen];