/*
* Copyright 2011 Google Inc.
*
* 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.
*/
/**
* ATTENTION: Consider using the 'ViewPager' widget, available in the
* Android Compatibility Package, r3:
*
* http://developer.android.com/sdk/compatibility-library.html
*/
package com.google.android.apps.iosched.ui.widget;
import com.google.android.apps.iosched.util.MotionEventUtils;
import com.google.android.apps.iosched.util.ReflectionUtils;
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;
import java.util.ArrayList;
/**
* A {@link android.view.ViewGroup} that shows one child at a time, allowing the user to swipe
* horizontally to page between other child views. Based on <code>Workspace.java</code> in the
* <code>Launcher.git</code> AOSP project.
*
* An improved version of this UI widget named 'ViewPager' is now available in the
* <a href="http://developer.android.com/sdk/compatibility-library.html">Android Compatibility
* Package, r3</a>.
*/
public class Workspace extends ViewGroup {
private static final String TAG = "Workspace";
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 user needs to drag at least this much for it to be considered a fling gesture. This
* reduces the chance of a random twitch sending the user to the next screen.
*/
// TODO: refactor
private static final int MIN_LENGTH_FOR_FLING = 100;
private int mDefaultScreen;
private boolean mFirstLayout = true;
private boolean mHasLaidOut = false;
private int mCurrentScreen;
private int mNextScreen = INVALID_SCREEN;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
/**
* X position of the active pointer when it was first pressed down.
*/
private float mDownMotionX;
/**
* Y position of the active pointer when it was first pressed down.
*/
private float mDownMotionY;
/**
* This view's X scroll offset when the active pointer was first pressed down.
*/
private int mDownScrollX;
private final static int TOUCH_STATE_REST = 0;
private final static int TOUCH_STATE_SCROLLING = 1;
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 Drawable mSeparatorDrawable;
private OnScreenChangeListener mOnScreenChangeListener;
private OnScrollListener mOnScrollListener;
private boolean mLocked;
private int mDeferredScreenChange = -1;
private boolean mDeferredScreenChangeFast = false;
private boolean mDeferredNotify = false;
private boolean mIgnoreChildFocusRequests;
private boolean mIsVerbose = false;
public interface OnScreenChangeListener {
void onScreenChanged(View newScreen, int newScreenIndex);
void onScreenChanging(View newScreen, int newScreenIndex);
}
public interface OnScrollListener {
void onScroll(float screenFraction);
}
/**
* Used to inflate the com.google.android.ext.workspace.Workspace from XML.
*
* @param context The application's context.
* @param attrs The attributes set containing the com.google.android.ext.workspace.Workspace's
* customization values.
*/
public Workspace(Context context, AttributeSet attrs) {
super(context, attrs);
mDefaultScreen = 0;
mLocked = false;
setHapticFeedbackEnabled(false);
initWorkspace();
mIsVerbose = Log.isLoggable(TAG, Log.VERBOSE);
}
/**
* Initializes various states for this workspace.
*/
private void initWorkspace() {
mScroller = new Scroller(getContext());
mCurrentScreen = mDefaultScreen;
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mPagingTouchSlop = ReflectionUtils.callWithDefault(configuration,
"getScaledPagingTouchSlop", mTouchSlop * 2);
}
/**
* Returns the index of the currently displayed screen.
*/
int getCurrentScreen() {
return mCurrentScreen;
}
/**
* Returns the number of screens currently contained in this Workspace.
*/
int getScreenCount() {
int childCount = getChildCount();
if (mSeparatorDrawable != null) {
return (childCount + 1) / 2;
}
return childCount;
}
View getScreenAt(int index) {
if (mSeparatorDrawable == null) {
return getChildAt(index);
}
return getChildAt(index * 2);
}
int getScrollWidth() {
int w = getWidth();
if (mSeparatorDrawable != null) {
w += mSeparatorDrawable.getIntrinsicWidth();
}
return w;
}
void handleScreenChangeCompletion(int currentScreen) {
mCurrentScreen = currentScreen;
View screen = getScreenAt(mCurrentScreen);
//screen.requestFocus();
try {
ReflectionUtils.tryInvoke(screen, "dispatchDisplayHint",
new Class[]{int.class}, View.VISIBLE);
invalidate();
} catch (NullPointerException e) {
Log.e(TAG, "Caught NullPointerException", e);
}
notifyScreenChangeListener(mCurrentScreen, true);
}
void notifyScreenChangeListener(int whichScreen, boolean changeComplete) {
if (mOnScreenChangeListener != null) {
if (changeComplete)
mOnScreenChangeListener.onScreenChanged(getScreenAt(whichScreen), whichScreen);
else
mOnScreenChangeListener.onScreenChanging(getScreenAt(whichScreen), whichScreen);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(getCurrentScreenFraction());
}
}
/**
* Registers the specified listener on each screen contained in this workspace.
*
* @param listener The listener used to respond to long clicks.
*/
@Override
public void setOnLongClickListener(OnLongClickListener listener) {
mLongClickListener = listener;
final int count = getScreenCount();
for (int i = 0; i < count; i++) {
getScreenAt(i).setOnLongClickListener(listener);
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(getCurrentScreenFraction());
}
postInval