Creating generic Workspace/CellLayout for paged views in Launcher.
Adding SimpleWorkspace, SimpleCellLayout and an AllApps implementation of the SimpleWorkspace. Making SimpleWorkspace support content with smaller dimensions than the workspace dimensions itself. Temporary change to AllApps tabs styling until we get new assets for tabs in general. Change-Id: Ibe3c56603223853d232816b6695e4ddd757857ba
This commit is contained in:
@@ -0,0 +1,788 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.Scroller;
|
||||
|
||||
/**
|
||||
* An abstraction of the original Workspace which supports browsing through a
|
||||
* sequential list of "pages" (or PagedViewCellLayouts).
|
||||
*/
|
||||
public abstract class PagedView extends ViewGroup {
|
||||
private static final String TAG = "PagedView";
|
||||
private static final int INVALID_SCREEN = -1;
|
||||
|
||||
// the velocity at which a fling gesture will cause us to snap to the next screen
|
||||
private static final int SNAP_VELOCITY = 500;
|
||||
|
||||
// the min drag distance for a fling to register, to prevent random screen shifts
|
||||
private static final int MIN_LENGTH_FOR_FLING = 50;
|
||||
|
||||
private boolean mFirstLayout = true;
|
||||
|
||||
private int mCurrentScreen;
|
||||
private int mNextScreen = INVALID_SCREEN;
|
||||
private Scroller mScroller;
|
||||
private VelocityTracker mVelocityTracker;
|
||||
|
||||
private float mDownMotionX;
|
||||
private float mLastMotionX;
|
||||
private float mLastMotionY;
|
||||
|
||||
private final static int TOUCH_STATE_REST = 0;
|
||||
private final static int TOUCH_STATE_SCROLLING = 1;
|
||||
private final static int TOUCH_STATE_PREV_PAGE = 2;
|
||||
private final static int TOUCH_STATE_NEXT_PAGE = 3;
|
||||
|
||||
private int mTouchState = TOUCH_STATE_REST;
|
||||
|
||||
private OnLongClickListener mLongClickListener;
|
||||
|
||||
private boolean mAllowLongPress = true;
|
||||
|
||||
private int mTouchSlop;
|
||||
private int mPagingTouchSlop;
|
||||
private int mMaximumVelocity;
|
||||
|
||||
private static final int INVALID_POINTER = -1;
|
||||
|
||||
private int mActivePointerId = INVALID_POINTER;
|
||||
|
||||
private ScreenSwitchListener mScreenSwitchListener;
|
||||
|
||||
private boolean mDimmedPagesDirty;
|
||||
|
||||
public interface ScreenSwitchListener {
|
||||
void onScreenSwitch(View newScreen, int newScreenIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context The application's context.
|
||||
*/
|
||||
public PagedView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PagedView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public PagedView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
setHapticFeedbackEnabled(false);
|
||||
initWorkspace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes various states for this workspace.
|
||||
*/
|
||||
private void initWorkspace() {
|
||||
mScroller = new Scroller(getContext());
|
||||
mCurrentScreen = 0;
|
||||
|
||||
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
|
||||
mTouchSlop = configuration.getScaledTouchSlop();
|
||||
mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
|
||||
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
}
|
||||
|
||||
public void setScreenSwitchListener(ScreenSwitchListener screenSwitchListener) {
|
||||
mScreenSwitchListener = screenSwitchListener;
|
||||
if (mScreenSwitchListener != null) {
|
||||
mScreenSwitchListener.onScreenSwitch(getScreenAt(mCurrentScreen), mCurrentScreen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the currently displayed screen.
|
||||
*
|
||||
* @return The index of the currently displayed screen.
|
||||
*/
|
||||
int getCurrentScreen() {
|
||||
return mCurrentScreen;
|
||||
}
|
||||
|
||||
int getScreenCount() {
|
||||
return getChildCount();
|
||||
}
|
||||
|
||||
View getScreenAt(int index) {
|
||||
return getChildAt(index);
|
||||
}
|
||||
|
||||
int getScrollWidth() {
|
||||
return getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current screen.
|
||||
*
|
||||
* @param currentScreen
|
||||
*/
|
||||
void setCurrentScreen(int currentScreen) {
|
||||
if (!mScroller.isFinished()) mScroller.abortAnimation();
|
||||
if (getChildCount() == 0) return;
|
||||
|
||||
mCurrentScreen = Math.max(0, Math.min(currentScreen, getScreenCount() - 1));
|
||||
scrollTo(getChildOffset(mCurrentScreen) - getRelativeChildOffset(mCurrentScreen), 0);
|
||||
invalidate();
|
||||
notifyScreenSwitchListener();
|
||||
}
|
||||
|
||||
private void notifyScreenSwitchListener() {
|
||||
if (mScreenSwitchListener != null) {
|
||||
mScreenSwitchListener.onScreenSwitch(getScreenAt(mCurrentScreen), mCurrentScreen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the specified listener on each screen contained in this workspace.
|
||||
*
|
||||
* @param l The listener used to respond to long clicks.
|
||||
*/
|
||||
@Override
|
||||
public void setOnLongClickListener(OnLongClickListener l) {
|
||||
mLongClickListener = l;
|
||||
final int count = getScreenCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
getScreenAt(i).setOnLongClickListener(l);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
|
||||
postInvalidate();
|
||||
} else if (mNextScreen != INVALID_SCREEN) {
|
||||
mCurrentScreen = Math.max(0, Math.min(mNextScreen, getScreenCount() - 1));
|
||||
notifyScreenSwitchListener();
|
||||
mNextScreen = INVALID_SCREEN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
if (widthMode != MeasureSpec.EXACTLY) {
|
||||
throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
|
||||
}
|
||||
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
if (heightMode != MeasureSpec.EXACTLY) {
|
||||
throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
|
||||
}
|
||||
|
||||
// The children are given the same width and height as the workspace
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
setMeasuredDimension(widthSize, heightSize);
|
||||
|
||||
if (mFirstLayout) {
|
||||
setHorizontalScrollBarEnabled(false);
|
||||
scrollTo(mCurrentScreen * widthSize, 0);
|
||||
setHorizontalScrollBarEnabled(true);
|
||||
mFirstLayout = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
final int childCount = getChildCount();
|
||||
int childLeft = 0;
|
||||
if (childCount > 0) {
|
||||
childLeft = (getMeasuredWidth() - getChildAt(0).getMeasuredWidth()) / 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child.getVisibility() != View.GONE) {
|
||||
final int childWidth = child.getMeasuredWidth();
|
||||
child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
|
||||
childLeft += childWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void invalidateDimmedPages() {
|
||||
mDimmedPagesDirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
if (mDimmedPagesDirty || (mTouchState == TOUCH_STATE_SCROLLING) ||
|
||||
!mScroller.isFinished()) {
|
||||
int screenCenter = mScrollX + (getMeasuredWidth() / 2);
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; ++i) {
|
||||
PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
|
||||
int childWidth = layout.getMeasuredWidth();
|
||||
int halfChildWidth = (childWidth / 2);
|
||||
int childCenter = getChildOffset(i) + halfChildWidth;
|
||||
int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
|
||||
float dimAlpha = 0.0f;
|
||||
if (distanceFromScreenCenter < halfChildWidth) {
|
||||
dimAlpha = 0.0f;
|
||||
} else if (distanceFromScreenCenter > childWidth) {
|
||||
dimAlpha = 1.0f;
|
||||
} else {
|
||||
dimAlpha = (float) (distanceFromScreenCenter - halfChildWidth) / halfChildWidth;
|
||||
dimAlpha = (dimAlpha * dimAlpha);
|
||||
}
|
||||
layout.setDimmedBitmapAlpha(Math.max(0.0f, Math.min(1.0f, dimAlpha)));
|
||||
}
|
||||
}
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
|
||||
int screen = indexOfChild(child);
|
||||
if (screen != mCurrentScreen || !mScroller.isFinished()) {
|
||||
snapToScreen(screen);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
|
||||
int focusableScreen;
|
||||
if (mNextScreen != INVALID_SCREEN) {
|
||||
focusableScreen = mNextScreen;
|
||||
} else {
|
||||
focusableScreen = mCurrentScreen;
|
||||
}
|
||||
View v = getScreenAt(focusableScreen);
|
||||
if (v != null) {
|
||||
v.requestFocus(direction, previouslyFocusedRect);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchUnhandledMove(View focused, int direction) {
|
||||
if (direction == View.FOCUS_LEFT) {
|
||||
if (getCurrentScreen() > 0) {
|
||||
snapToScreen(getCurrentScreen() - 1);
|
||||
return true;
|
||||
}
|
||||
} else if (direction == View.FOCUS_RIGHT) {
|
||||
if (getCurrentScreen() < getScreenCount() - 1) {
|
||||
snapToScreen(getCurrentScreen() + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.dispatchUnhandledMove(focused, direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
|
||||
if (mCurrentScreen >= 0 && mCurrentScreen < getScreenCount()) {
|
||||
getScreenAt(mCurrentScreen).addFocusables(views, direction);
|
||||
}
|
||||
if (direction == View.FOCUS_LEFT) {
|
||||
if (mCurrentScreen > 0) {
|
||||
getScreenAt(mCurrentScreen - 1).addFocusables(views, direction);
|
||||
}
|
||||
} else if (direction == View.FOCUS_RIGHT){
|
||||
if (mCurrentScreen < getScreenCount() - 1) {
|
||||
getScreenAt(mCurrentScreen + 1).addFocusables(views, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If one of our descendant views decides that it could be focused now, only
|
||||
* pass that along if it's on the current screen.
|
||||
*
|
||||
* This happens when live folders requery, and if they're off screen, they
|
||||
* end up calling requestFocus, which pulls it on screen.
|
||||
*/
|
||||
@Override
|
||||
public void focusableViewAvailable(View focused) {
|
||||
View current = getScreenAt(mCurrentScreen);
|
||||
View v = focused;
|
||||
while (true) {
|
||||
if (v == current) {
|
||||
super.focusableViewAvailable(focused);
|
||||
return;
|
||||
}
|
||||
if (v == this) {
|
||||
return;
|
||||
}
|
||||
ViewParent parent = v.getParent();
|
||||
if (parent instanceof View) {
|
||||
v = (View)v.getParent();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
if (disallowIntercept) {
|
||||
// We need to make sure to cancel our long press if
|
||||
// a scrollable widget takes over touch events
|
||||
final View currentScreen = getChildAt(mCurrentScreen);
|
||||
currentScreen.cancelLongPress();
|
||||
}
|
||||
super.requestDisallowInterceptTouchEvent(disallowIntercept);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
/*
|
||||
* This method JUST determines whether we want to intercept the motion.
|
||||
* If we return true, onTouchEvent will be called and we do the actual
|
||||
* scrolling there.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Shortcut the most recurring case: the user is in the dragging
|
||||
* state and he is moving his finger. We want to intercept this
|
||||
* motion.
|
||||
*/
|
||||
final int action = ev.getAction();
|
||||
if ((action == MotionEvent.ACTION_MOVE) &&
|
||||
(mTouchState == TOUCH_STATE_SCROLLING)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
switch (action & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
/*
|
||||
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
|
||||
* whether the user has moved far enough from his original down touch.
|
||||
*/
|
||||
determineScrollingStart(ev);
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
final float x = ev.getX();
|
||||
final float y = ev.getY();
|
||||
// Remember location of down touch
|
||||
mDownMotionX = x;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
mAllowLongPress = true;
|
||||
|
||||
/*
|
||||
* If being flinged and user touches the screen, initiate drag;
|
||||
* otherwise don't. mScroller.isFinished should be false when
|
||||
* being flinged.
|
||||
*/
|
||||
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
|
||||
|
||||
// check if this can be the beginning of a tap on the side of the screens
|
||||
// to scroll the current page
|
||||
if ((mTouchState != TOUCH_STATE_PREV_PAGE) &&
|
||||
(mTouchState != TOUCH_STATE_NEXT_PAGE)) {
|
||||
if (getChildCount() > 0) {
|
||||
int relativeChildLeft = getChildOffset(0);
|
||||
int relativeChildRight = relativeChildLeft + getChildAt(0).getMeasuredWidth();
|
||||
if (x < relativeChildLeft) {
|
||||
mTouchState = TOUCH_STATE_PREV_PAGE;
|
||||
} else if (x > relativeChildRight) {
|
||||
mTouchState = TOUCH_STATE_NEXT_PAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
// Release the drag
|
||||
mTouchState = TOUCH_STATE_REST;
|
||||
mAllowLongPress = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onSecondaryPointerUp(ev);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* The only time we want to intercept motion events is if we are in the
|
||||
* drag mode.
|
||||
*/
|
||||
return mTouchState != TOUCH_STATE_REST;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determines if we should change the touch state to start scrolling after the
|
||||
* user moves their touch point too far.
|
||||
*/
|
||||
private void determineScrollingStart(MotionEvent ev) {
|
||||
/*
|
||||
* Locally do absolute value. mLastMotionX is set to the y value
|
||||
* of the down event.
|
||||
*/
|
||||
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final int xDiff = (int) Math.abs(x - mLastMotionX);
|
||||
final int yDiff = (int) Math.abs(y - mLastMotionY);
|
||||
|
||||
final int touchSlop = mTouchSlop;
|
||||
boolean xPaged = xDiff > mPagingTouchSlop;
|
||||
boolean xMoved = xDiff > touchSlop;
|
||||
boolean yMoved = yDiff > touchSlop;
|
||||
|
||||
if (xMoved || yMoved) {
|
||||
if (xPaged) {
|
||||
// Scroll if the user moved far enough along the X axis
|
||||
mTouchState = TOUCH_STATE_SCROLLING;
|
||||
mLastMotionX = x;
|
||||
}
|
||||
// Either way, cancel any pending longpress
|
||||
if (mAllowLongPress) {
|
||||
mAllowLongPress = false;
|
||||
// Try canceling the long press. It could also have been scheduled
|
||||
// by a distant descendant, so use the mAllowLongPress flag to block
|
||||
// everything
|
||||
final View currentScreen = getScreenAt(mCurrentScreen);
|
||||
currentScreen.cancelLongPress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (mVelocityTracker == null) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
final int action = ev.getAction();
|
||||
|
||||
switch (action & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
/*
|
||||
* If being flinged and user touches, stop the fling. isFinished
|
||||
* will be false if being flinged.
|
||||
*/
|
||||
if (!mScroller.isFinished()) {
|
||||
mScroller.abortAnimation();
|
||||
}
|
||||
|
||||
// Remember where the motion event started
|
||||
mDownMotionX = mLastMotionX = ev.getX();
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (mTouchState == TOUCH_STATE_SCROLLING) {
|
||||
// Scroll to follow the motion event
|
||||
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final int deltaX = (int) (mLastMotionX - x);
|
||||
mLastMotionX = x;
|
||||
|
||||
int sx = getScrollX();
|
||||
if (deltaX < 0) {
|
||||
if (sx > 0) {
|
||||
scrollBy(Math.max(-sx, deltaX), 0);
|
||||
}
|
||||
} else if (deltaX > 0) {
|
||||
final int lastChildIndex = getChildCount() - 1;
|
||||
final int availableToScroll = getChildOffset(lastChildIndex) -
|
||||
getRelativeChildOffset(lastChildIndex) - sx;
|
||||
if (availableToScroll > 0) {
|
||||
scrollBy(Math.min(availableToScroll, deltaX), 0);
|
||||
}
|
||||
} else {
|
||||
awakenScrollBars();
|
||||
}
|
||||
} else if ((mTouchState == TOUCH_STATE_PREV_PAGE) ||
|
||||
(mTouchState == TOUCH_STATE_NEXT_PAGE)) {
|
||||
determineScrollingStart(ev);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (mTouchState == TOUCH_STATE_SCROLLING) {
|
||||
final int activePointerId = mActivePointerId;
|
||||
final int pointerIndex = ev.findPointerIndex(activePointerId);
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final VelocityTracker velocityTracker = mVelocityTracker;
|
||||
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
|
||||
boolean isfling = Math.abs(mDownMotionX - x) > MIN_LENGTH_FOR_FLING;
|
||||
|
||||
if (isfling && velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
|
||||
snapToScreen(mCurrentScreen - 1);
|
||||
} else if (isfling && velocityX < -SNAP_VELOCITY &&
|
||||
mCurrentScreen < getChildCount() - 1) {
|
||||
snapToScreen(mCurrentScreen + 1);
|
||||
} else {
|
||||
snapToDestination();
|
||||
}
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
} else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
|
||||
// at this point we have not moved beyond the touch slop
|
||||
// (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
|
||||
// we can just page
|
||||
int nextScreen = Math.max(0, mCurrentScreen - 1);
|
||||
if (nextScreen != mCurrentScreen) {
|
||||
snapToScreen(nextScreen);
|
||||
} else {
|
||||
snapToDestination();
|
||||
}
|
||||
} else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
|
||||
// at this point we have not moved beyond the touch slop
|
||||
// (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
|
||||
// we can just page
|
||||
int nextScreen = Math.min(getChildCount() - 1, mCurrentScreen + 1);
|
||||
if (nextScreen != mCurrentScreen) {
|
||||
snapToScreen(nextScreen);
|
||||
} else {
|
||||
snapToDestination();
|
||||
}
|
||||
}
|
||||
mTouchState = TOUCH_STATE_REST;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mTouchState = TOUCH_STATE_REST;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onSecondaryPointerUp(ev);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onSecondaryPointerUp(MotionEvent ev) {
|
||||
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
|
||||
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
final int pointerId = ev.getPointerId(pointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
// This was our active pointer going up. Choose a new
|
||||
// active pointer and adjust accordingly.
|
||||
// TODO: Make this decision more intelligent.
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
|
||||
mLastMotionY = ev.getY(newPointerIndex);
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestChildFocus(View child, View focused) {
|
||||
super.requestChildFocus(child, focused);
|
||||
int screen = indexOfChild(child);
|
||||
if (screen >= 0 && !isInTouchMode()) {
|
||||
snapToScreen(screen);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getRelativeChildOffset(int index) {
|
||||
return (getMeasuredWidth() - getChildAt(index).getMeasuredWidth()) / 2;
|
||||
}
|
||||
|
||||
protected int getChildOffset(int index) {
|
||||
if (getChildCount() == 0)
|
||||
return 0;
|
||||
|
||||
int offset = getRelativeChildOffset(0);
|
||||
for (int i = 0; i < index; ++i) {
|
||||
offset += getChildAt(i).getMeasuredWidth();
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
protected void snapToDestination() {
|
||||
int minDistanceFromScreenCenter = getMeasuredWidth();
|
||||
int minDistanceFromScreenCenterIndex = -1;
|
||||
int screenCenter = mScrollX + (getMeasuredWidth() / 2);
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; ++i) {
|
||||
PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
|
||||
int childWidth = layout.getMeasuredWidth();
|
||||
int halfChildWidth = (childWidth / 2);
|
||||
int childCenter = getChildOffset(i) + halfChildWidth;
|
||||
int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
|
||||
if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
|
||||
minDistanceFromScreenCenter = distanceFromScreenCenter;
|
||||
minDistanceFromScreenCenterIndex = i;
|
||||
}
|
||||
}
|
||||
snapToScreen(minDistanceFromScreenCenterIndex, 1000);
|
||||
}
|
||||
|
||||
void snapToScreen(int whichScreen) {
|
||||
snapToScreen(whichScreen, 1000);
|
||||
}
|
||||
|
||||
void snapToScreen(int whichScreen, int duration) {
|
||||
whichScreen = Math.max(0, Math.min(whichScreen, getScreenCount() - 1));
|
||||
|
||||
mNextScreen = whichScreen;
|
||||
|
||||
int newX = getChildOffset(whichScreen) - getRelativeChildOffset(whichScreen);
|
||||
final int sX = getScrollX();
|
||||
final int delta = newX - sX;
|
||||
awakenScrollBars(duration);
|
||||
if (duration == 0) {
|
||||
duration = Math.abs(delta);
|
||||
}
|
||||
|
||||
if (!mScroller.isFinished()) mScroller.abortAnimation();
|
||||
mScroller.startScroll(sX, 0, delta, 0, duration);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable onSaveInstanceState() {
|
||||
final SavedState state = new SavedState(super.onSaveInstanceState());
|
||||
state.currentScreen = mCurrentScreen;
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Parcelable state) {
|
||||
SavedState savedState = (SavedState) state;
|
||||
super.onRestoreInstanceState(savedState.getSuperState());
|
||||
if (savedState.currentScreen != -1) {
|
||||
mCurrentScreen = savedState.currentScreen;
|
||||
}
|
||||
}
|
||||
|
||||
public void scrollLeft() {
|
||||
if (mScroller.isFinished()) {
|
||||
if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);
|
||||
} else {
|
||||
if (mNextScreen > 0) snapToScreen(mNextScreen - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void scrollRight() {
|
||||
if (mScroller.isFinished()) {
|
||||
if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1);
|
||||
} else {
|
||||
if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int getScreenForView(View v) {
|
||||
int result = -1;
|
||||
if (v != null) {
|
||||
ViewParent vp = v.getParent();
|
||||
int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (vp == getChildAt(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True is long presses are still allowed for the current touch
|
||||
*/
|
||||
public boolean allowLongPress() {
|
||||
return mAllowLongPress;
|
||||
}
|
||||
|
||||
public static class SavedState extends BaseSavedState {
|
||||
int currentScreen = -1;
|
||||
|
||||
SavedState(Parcelable superState) {
|
||||
super(superState);
|
||||
}
|
||||
|
||||
private SavedState(Parcel in) {
|
||||
super(in);
|
||||
currentScreen = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
super.writeToParcel(out, flags);
|
||||
out.writeInt(currentScreen);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SavedState> CREATOR =
|
||||
new Parcelable.Creator<SavedState>() {
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public abstract void syncPages();
|
||||
public abstract void syncPageItems(int page);
|
||||
public void invalidatePageData() {
|
||||
syncPages();
|
||||
for (int i = 0; i < getChildCount(); ++i) {
|
||||
syncPageItems(i);
|
||||
}
|
||||
invalidateDimmedPages();
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user