/*
* Copyright (C) 2008 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.aviary.android.feather.widget.wp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PorterDuff.Mode;
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.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.Adapter;
import android.widget.LinearLayout;
import android.widget.Scroller;
import com.aviary.android.feather.R;
// TODO: Auto-generated Javadoc
/**
* The Class Workspace.
*/
public class Workspace extends ViewGroup {
/** The Constant INVALID_SCREEN. */
private static final int INVALID_SCREEN = -1;
/** The Constant OVER_SCROLL_NEVER. */
public static final int OVER_SCROLL_NEVER = 0;
/** The Constant OVER_SCROLL_ALWAYS. */
public static final int OVER_SCROLL_ALWAYS = 1;
/** The Constant OVER_SCROLL_IF_CONTENT_SCROLLS. */
public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 2;
/** The velocity at which a fling gesture will cause us to snap to the next screen. */
private static final int SNAP_VELOCITY = 600;
private int mPreviousScreen = INVALID_SCREEN;
/** The m default screen. */
private int mDefaultScreen = 0;
/** The m padding bottom. */
private int mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom;
/** The m current screen. */
private int mCurrentScreen = INVALID_SCREEN;
/** The m next screen. */
private int mNextScreen = INVALID_SCREEN;
/** The m old selected position. */
private int mOldSelectedPosition = INVALID_SCREEN;
/** The m scroller. */
private Scroller mScroller;
/** The m velocity tracker. */
private VelocityTracker mVelocityTracker;
/** The m last motion x. */
private float mLastMotionX;
/** The m last motion x2. */
private float mLastMotionX2;
/** The m last motion y. */
private float mLastMotionY;
/** The Constant TOUCH_STATE_REST. */
private final static int TOUCH_STATE_REST = 0;
/** The Constant TOUCH_STATE_SCROLLING. */
private final static int TOUCH_STATE_SCROLLING = 1;
/** The m touch state. */
private int mTouchState = TOUCH_STATE_REST;
/** The m allow long press. */
private boolean mAllowLongPress = true;
/** The m touch slop. */
private int mTouchSlop;
/** The m maximum velocity. */
private int mMaximumVelocity;
/** The Constant INVALID_POINTER. */
private static final int INVALID_POINTER = -1;
/** The m active pointer id. */
private int mActivePointerId = INVALID_POINTER;
/** The m indicator. */
private WorkspaceIndicator mIndicator;
/** The Constant NANOTIME_DIV. */
private static final float NANOTIME_DIV = 1000000000.0f;
/** The Constant SMOOTHING_SPEED. */
private static final float SMOOTHING_SPEED = 0.75f;
/** The Constant SMOOTHING_CONSTANT. */
private static final float SMOOTHING_CONSTANT = (float) ( 0.016 / Math.log( SMOOTHING_SPEED ) );
/** The Constant BASELINE_FLING_VELOCITY. */
private static final float BASELINE_FLING_VELOCITY = 2500.f;
/** The Constant FLING_VELOCITY_INFLUENCE. */
private static final float FLING_VELOCITY_INFLUENCE = 0.4f;
/** The m smoothing time. */
private float mSmoothingTime;
/** The m touch x. */
private float mTouchX;
/** The m scroll interpolator. */
private Interpolator mScrollInterpolator;
/** The m adapter. */
protected Adapter mAdapter;
/** The m observer. */
protected DataSetObserver mObserver;
/** The m data changed. */
protected boolean mDataChanged;
/** The m first position. */
protected int mFirstPosition;
/** The m item count. */
protected int mItemCount = 0;
/** The m item type count. */
protected int mItemTypeCount = 1;
/** The m recycler. */
private List<Queue<View>> mRecycleBin;
/** The m height measure spec. */
private int mHeightMeasureSpec;
/** The m width measure spec. */
private int mWidthMeasureSpec;
/** The m edge glow left. */
private EdgeGlow mEdgeGlowLeft;
/** The m edge glow right. */
private EdgeGlow mEdgeGlowRight;
/** The m over scroll mode. */
private int mOverScrollMode;
/** The m allow child selection. */
private boolean mAllowChildSelection = true;
private boolean mCacheEnabled = false;
/**
* The listener interface for receiving onPageChange events. The class that is interested in processing a onPageChange event
* implements this interface, and the object created with that class is registered with a component using the component's
* <code>addOnPageChangeListener<code> method. When
* the onPageChange event occurs, that object's appropriate
* method is invoked.
*
* @see OnPageChangeEvent
*/
public interface OnPageChangeListener {
/**
* On page changed.
*
* @param which
* the which
*/
void onPageChanged( int which, int old );
}
/** The m on page change listener. */
private OnPageChangeListener mOnPageChangeListener;
/**
* Sets the on page change listener.
*
* @param listener
* the new on page change listener
*/
public void setOnPageChangeListener( OnPageChangeListener listener ) {
mOnPageChangeListener = listener;
}
/**
* Instantiates a new workspace.
*
* @param context
* the context
* @param attrs
* the attrs
*/
public Workspace( Context context, AttributeSet attrs ) {
this( context, attrs, 0 );
initWorkspace( context, attrs, 0 );
}
/**
* Instantiates a new workspace.
*
* @param context
* the context
* @param attrs
* the attrs
* @param defStyle
* the def style
*/
public Workspace( Context context, AttributeSet attrs, int defStyle ) {
super( context, attrs, defStyle );
initWorkspace( context, attrs, defStyle );
}
/**
* Inits the workspace.
*
* @param context
* the context
* @param attrs
* the attrs
* @param defStyle
* the def style
*/
private void initWorkspace( Context context, AttributeSet attrs, int defStyle ) {
TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.Workspace, defStyle, 0 );
mDefaultScreen = a.getInt( R.styleable.Workspace_defaultScreen, 0 );
a.recycle();
setHapticFeedbackEnabled( false );
mScrollInterpolator = new DecelerateInterpolator( 1.0f );
mScroller = new Scroller( context, mScrollInterpolator );
// mCurrentScreen = mDefaultScreen;
mPreviousScreen = INVALID_SCREEN;
final ViewConfiguration configuration = ViewConfiguration.get( getContext() );
mTouchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mPaddingTop = getPaddingTop();
mPaddingBottom = getPaddingBottom();
mPaddingLeft = getPaddingLeft();
mPaddingRight = getPaddingRight();
int overscrollMode = a.getInt( R.styleable.Workspace_overscroll, 0 );
setOverScroll( overscrollMode );
}
/**
* Sets the over scroll.
*
* @param mode
* the new over scroll
*/
public void setOverScroll( int mode ) {
if ( mode != OVER_SCROLL_NEVER ) {
if ( mEdgeGlowLeft == null ) {
final Resources res = getContext().getResources();
final Drawable edge = res.getDrawable( R.drawable.feather_overscroll_edge );
final Drawable glow = res.getDrawable( R.drawable.feather_overscroll_glow );
mEdgeGlowLeft = new EdgeGlow( edge, glow );
mEdgeGlowRight = new EdgeGlow( edge, glow );
mEdgeGlowLeft.setColorFilter( 0xFF454545, Mode.MULTIPLY );
}
} else {
mEdgeGlowLeft = null;
mEdgeGlowRight = null;
}
mOverScrollMode = mode;
}
/**
* Gets the over scroll.
*
* @return the over scroll
*/
public int getOverScroll() {
return mOverScrollMode;
}
/**
* Sets the allow child selection.
*
* @param value
* the new allow child selection
*/
public void setAllowChildSelection( boolean value ) {
mAllowChildSelection = value;
}
/**
* Gets the adapter.
*
* @return the adapter
*/
public Adapter getAdapter() {
return mAdapter;
}
/**
* Sets the adapter.
*
* @param adapter
* the new adapter
*/
public void setAdapter( Adapter adapter ) {
if ( mAdapter != null ) {
mAdapter.unregisterDataSetObserver( mObserver );
mAdapter = null;
}
mAdapter = adapter;
resetList();
if ( mAdapter != null ) {
mObserver = new WorkspaceDataSetObserver();
mAdapter.registerDataSetObserver( mObserver );
mItemTypeCount = adapter.getViewTypeCount();
mItemCount = adapter.getCount();
mRecycleBin = Collections.synchronizedList( new ArrayList<Queue<View>>() );
for ( int i = 0; i < mItemTypeCount; i++ ) {
mRecycleBin.add( new LinkedList<View>() );
}
} else {
mItemCount = 0;
}
mDataChanged = true;
requestLayout();
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#addView(android.view.View, int, android.view.ViewGroup.LayoutParams)
*/
@Override
public void addView( View child, int index, LayoutParams params ) {
if ( !( child instanceof CellLayout ) ) {
throw new IllegalArgumentException( "A Workspace can only have CellLayout children." );
}
super.addView( child, index, params );
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#addView(android.view.View)
*/
@Override
public void addView( View child ) {
if ( !( child instanceof CellLayout ) ) {
throw new IllegalArgumentException( "A Workspace can only have CellLayout children." );
}
super.addView( child );
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#addView(android.view.View, int)
*/
@Override
public void addView( View child, int index ) {
if ( !( child instanceof CellLayout ) ) {
throw new IllegalArgumentException( "A Workspace can only have CellLayout children." );
}
super.addView( child, index );
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#addView(android.view.View, int, int)
*/
@Override
public void addView( View child, int width, int height ) {
if ( !( child instanceof CellLayout ) ) {
throw new IllegalArgumentException( "A Workspace can only have CellLayout children." );
}
super.addView( child, width, height );
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#addView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
@Override
public void addView( View child, LayoutParams params ) {
if ( !( child instanceof CellLayout ) ) {
throw new IllegalArgumentException( "A Workspace can only have CellLayout children." );
}
super.addView( child, params );
}
/**
* Checks if is default screen showing.
*
* @return true, if is default screen showing
*/
boolean isDefaultScreenShowing() {
return mCurrentScreen == mDefaultScreen;
}
/**
* Returns the index of the currently displayed screen.
*
* @return The index of the currently displayed screen.
*/
public int getCurrentScreen() {
return mCurrentScreen;
}
/**
* Gets the total pages.
*
* @return the total pages
*/
public int getTotalPages() {
return mItemCount;
}
/**
* Sets the current screen.
*
* @param currentScreen
* the new current screen
*/
void setCurrentScreen( int currentScreen ) {
if ( !mScroller.isFinished() ) mScroller.abortAnimation();
mCurrentScreen = Math.max( 0, Math.min( currentScreen, mItemCount - 1 ) );
if ( mIndicator != null ) mIndicator.setLevel( mCurrentScreen, mItemCount );
scrollTo( mCurrentScreen * getWidth(), 0 );
invalidate();
}
/*
* (non-Javadoc)
*
* @see android.view.View#scrollTo(int, int)
*/
@Override
public void scrollTo( int x, int y ) {
super.scrollTo( x, y );
mTouchX = x;
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
}
/*
* (non-Javadoc)
*
* @see android.view.View#computeScroll()
*/
@Override
public void computeScroll() {
if ( mScroller.computeScrollOffset() ) {
mTouchX = mScroller.getCurrX();
float mScrollX = mTouchX;
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
float mScrollY = mScroller.getCurrY();
scrollTo( (int) mScrollX, (int) mScrollY );
postInvalidate();
} else if ( mNextScreen != INVALID_SCREEN ) {
int which = Math.max( 0, Math.min( mNextScreen, mItemCount - 1 ) );
onFinishedAnimation( which );
} else if ( mTouchState == TOUCH_STATE_SCROLLING ) {
final float now = System.nanoTime() / NANOTIME_DIV;
final float e = (float) Math.exp( ( now - mSmoothingTime ) / SMOOTHING_CONSTANT );
final float dx = mTouchX - getScrollX();
float mScrollX = getScrollX() + ( dx * e );
scrollTo( (int) mScrollX, 0 );
mSmoothingTime = now;
// Keep generating points as long as we're more than 1px away from the target
if ( dx > 1.f || dx < -1.f ) {
postInvalidate();
}
}
}
/** The m old selected child. */
private View mOldSelectedChild;
/**
* On finished animation.
*
* @param newScreen
* the new screen
*/
private void onFinishedAnimation( int newScreen ) {
final int previousScreen = mCurrentScreen;
final boolean toLeft = newScreen > mCurrentScreen;
final boolean toRight = newScreen < mCurrentScreen;
final boolean changed = newScreen != mCurrentScreen;
mCurrentScreen = newScreen;
if ( mIndicator != null ) mIndicator.setLevel( mCurrentScreen, mItemCount );
setNextSelectedPositionInt( INVALID_SCREEN );
fillToGalleryRight();
fillToGalleryLeft();
if ( toLeft ) {
detachOffScreenChildren( true );
} else if ( toRight ) {
detachOffScreenChildren( false );
}
if ( changed || mItemCount == 1 || true ) {
View child = getChildAt( mCurrentScreen - mFirstPosition );
if ( null != child ) {
if ( mAllowChildSelection ) {
if ( null != mOldSelectedChild ) {
mOldSelectedChild.setSelected( false );
mOldSelectedChild = null;
}
child.setSelected( true );
mOldSelectedChild = child;
}
// int index = indexOfChild( child ) + mFirstPosition;
child.requestFocus();
}
}
clearChildrenCache();
if ( mOnPageChangeListener != null && newScreen != mPreviousScreen ) {
post( new Runnable() {
@Override
public void run() {
if( null != mOnPageChangeListener )
mOnPageChangeListener.onPageChanged( mCurrentScreen, previousScreen );
}
} );
}
postUpdateIndicator( mCurrentScreen, mItemCount );
mPreviousScreen = newScreen;
}
/**
* Detach off screen children.
*
* @param toLeft
* the to left
*/
private void detachOffScreenChildren( boolean toLeft ) {
int numChildren = getChildCount();
int start = 0;
int count = 0;
int viewType;
if ( toLeft ) {
final int galleryLeft = mPaddingLeft + getScreenScrollPositionX( mCurrentScreen - 1 );
for ( int i = 0; i < numChildren; i++ ) {
final View child = getChildAt( i );
if ( child.getRight() > galleryLeft ) {
break;
} else {
count++;
viewType = mAdapter.getItemViewType( i + mFirstPosition );
mRecycleBin.get( viewType ).offer( child );
}
}
} else {
final int galleryRight = getTotalWidth() + getScreenScrollPositionX( mCurrentScreen + 1 );
for ( int i = numChildren - 1; i >= 0; i-- ) {
final View child = getChildAt( i );
if ( child.getLeft() < galleryRight ) {
break;
} else {
start = i;
count++;
viewType = mAdapter.getItemViewType( i + mFirstPosition );
mRecycleBin.get( viewType ).offer( child );
}
}
}
detachViewsFromParent( start, count );
if ( toLeft && count > 0 ) {
mFirstPosition += count;
}
}
private Matrix mEdgeMatrix = new Matrix();
/**
* Draw edges.
*
* @param canvas
* the canvas
*/
private void drawEdges( Canvas canvas ) {
if ( mEdgeGlowLeft != null ) {
if ( !mEdgeGlowLeft.isFinished() ) {
final int restoreCount = canvas.save();
final int height = getHeight();
mEdgeMatrix.reset();
mEdgeMatrix.postRotate( -90 );
mEdgeMatrix.postTranslate( 0, height );
canvas.concat( mEdgeMatrix );
mEdgeGlowLeft.setSize( height, height / 5 );
if ( mEdgeGlowLeft.draw( canvas ) ) {
invalidate();
}
canvas.restoreToCount( restoreCount );
}
if ( !mEdgeGlowRight.isFinished() ) {
final int restoreCount = canvas.save();
final int width = getWidth();
final int height = getHeight();
mEdgeMatrix.reset();
mEdgeMatrix.postRotate( 90 );
mEdgeMatrix.postTranslate( getScrollX() + width, 0 );
canvas.concat( mEdgeMatrix );
mEdgeGlowRight.setSize( height, height / 5 );
if ( mEdgeGlowRight.draw( canvas ) ) {
invalidate();
}
canvas.restoreToCount( restoreCount );
}
}
}
@Override
protected void dispatchDraw( Canvas canvas ) {
boolean restore = false;
int restoreCount = 0;
if ( mItemCount < 1 ) return;
if ( mCurrentScreen < 0 ) return;
boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
// If we are not scrolling or flinging, draw only the current screen
if ( fastDraw ) {
try {
drawChild( canvas, getChildAt( mCurrentScreen - mFirstPosition ), getDrawingTime() );
} catch ( RuntimeException e ) {
e.printStackTrace();
}
} else {
final long drawingTime = getDrawingTime();
final float scrollPos = (float) getScrollX() / getTotalWidth();
final int leftScreen = (int) scrollPos;
final int rightScreen = leftScreen + 1;
if ( leftScreen >= 0 ) {
try {
drawChild( canvas, getChildAt( leftScreen - mFirstPosition ), drawingTime );
} catch ( RuntimeException e ) {
e.printStackTrace();
}
}
if ( scrollPos != leftScreen && rightScreen < mItemCount ) {
try {
drawChild( canvas, getChildAt( rightScreen - mFirstPosition ), drawingTime );
} catch ( RuntimeException e ) {
e.printStackTrace();
}
}
}
// let's draw the edges only if we have more than 1 page
if ( mEdgeGlowLeft != null && mItemCount > 1 ) {
drawEdges( canvas );
}
if ( restore ) {
canvas.restoreToCount( restoreCount );
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
}
@Override
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) {
super.onMeasure( widthMeasureSpec, heightMeasureSpec );
mWidthMeasureSpec = widthMeasureSpec;
mHeightMeasureSpec = heightMeasureSpec;
final int widthMode = MeasureSpec.getMode( widthMeasureSpec );
final int heightMode = MeasureSpec.getMode( heightMeasureSpec );
if ( widthMode != MeasureSpec.EXACTLY ) {
// throw new IllegalStateException( "Workspace can only be used in EXACTLY mode." );
}
if ( heightMode != MeasureSpec.EXACTLY ) {
// throw new IllegalStateException( "Workspace can only be used in EXACTLY mode." );
}
}
/**
* Handle data changed.
*/
private void handleDataChanged() {
if ( mItemCount > 0 ) {
setNextSelectedPositionInt( 0 );
} else {
mCurrentScreen = INVALID_SCREEN;
mPreviousScreen = INVALID_SCREEN;
setNextSelectedPositionInt( INVALID_SCREEN );
}
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#onLayout(boolean, int, int, int, int)
*/
@Override
protected void onLayout( boolean changed, int left, int top, int right, int bottom ) {
Log.i( VIEW_LOG_TAG, "onLayout. changed=" + changed + ", width=" + (right-left) + ", mDataChanged=" + mDataChanged );
if ( changed || mDataChanged ) {
handleDataChanged();
}
if ( mItemCount < 1 ) {
return;
}
final int width = right - left;
if ( mDataChanged ) {
if ( mCurrentScreen > INVALID_SCREEN )
scrollTo( mCurrentScreen * width, 0 );
else
scrollTo( 0, 0 );
}
if ( mNextScreen > INVALID_SCREEN ) {
setSelectedPositionInt( mNextScreen );
}
if ( mDataChanged ) {
mFirstPosition = mDefaultScreen;
int childrenLeft = mPaddingLeft;
int childrenWidth = ( getRight() - getLeft() ) - ( mPaddingLeft + mPaddingRight );
View sel = makeAndAddView( mCurrentScreen, 0, 0, true );
int selectedOffset = childrenLeft + ( childrenWidth / 2 ) - ( sel.getWidth() / 2 );
sel.offsetLeftAndRight( selectedOffset );
fillToGalleryRight();
fillToGalleryLeft();
checkSelectionChanged();
}
mDataChanged = false;
setNextSelectedPositionInt( mCurrentScreen );
if ( changed && !mDataChanged ) {
layoutChildren();
}
}
/**
* Check selection changed.
*/
void checkSelectionChanged() {
if ( ( mCurrentScreen != mOldSelectedPosition ) ) {
// selectionChanged();
mOldSelectedPosition = mCurrentScreen;
}
}
/**
* Make and add view.
*
* @param position
* the position
* @param offset
* the offset
* @param x
* the x
* @param fromLeft
* the from left
* @return the view
*/
private View makeAndAddView( int position, int offset, int x, boolean fromLeft ) {
View child;
if ( !mDataChanged ) {
int viewType = mAdapter.getItemViewType( position );
child = mRecycleBin.get( viewType ).poll();
if ( child != null ) {
child = mAdapter.getView( position, child, this );
setUpChild( child, offset, x, fromLeft );
return child;
}
}
// Nothing found in the recycler -- ask the adapter for a view
child = mAdapter.getView( position, null, this );
// Position the view
setUpChild( child, offset, x, fromLeft );
return child;
}
/**
* Sets the up child.
*
* @param child
* the child
* @param offset
* the offset
* @param x
* the x
* @param fromLeft
* the from left
*/
private void setUpChild( View child, int offset, int x, boolean fromLeft ) {
// Respect layout params that are already in the view. Otherwise
// make some up...
LayoutParams lp = child.getLayoutParams();
if ( lp == null ) {
lp = (LayoutParams) generateDefaultLayoutParams();
}
addViewInLayout( child, fromLeft ? -1 : 0, lp );
if ( mAllowChildSelection ) {
// final boolean wantfocus = offset == 0;
// child.setSelected( wantfocus );
// if( wantfocus ){
// child.requestFocus();
// }
}
// Get measure specs
int childHeightSpec = ViewGroup.getChildMeasureSpec( mHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height );
int childWidthSpec = ViewGroup.getChildMeasureSpec( mWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width );
// Measure child
child.measure( childWidthSpec, childHeightSpec );
int childLeft;
int childRight;
// Position vertically based on gravity setting
int childTop = calculateTop( child, true );
int childBottom = childTop + child.getMeasuredHeight();
int width = child.getMeasuredWidth();
if ( fromLeft ) {
childLeft = x;
childRight = childLeft + width;
} else {
childLeft = x - width;
childRight = x;
}
child.layout( childLeft, childTop, childRight, childBottom );
}
private void layoutChildren() {
int total = getTotalPages();
int x = mPaddingLeft;
for ( int i = 0; i < total; i++ ) {
View child = getScreenAt( i );
if( null == child ) continue;
LayoutParams lp = child.getLayoutParams();
// Get measure specs
int childHeightSpec = ViewGroup.getChildMeasureSpec( mHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height );
int childWidthSpec = ViewGroup.getChildMeasureSpec( mWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width );
// Measure child
child.measure( childWidthSpec, childHeightSpec );
int childLeft;
int childRight;
// Position vertically based on gravity setting
int childTop = calculateTop( child, true );
int childBottom = childTop + child.getMeasuredHeight();
int width = child.getMeasuredWidth();
childLeft = x;
childRight = childLeft + width;
child.layout( childLeft, childTop, childRight, childBottom );
x = childRight;
}
}
/**
* Calculate top.
*
* @param child
* the child
* @param duringLayout
* the during layout
* @return the int
*/
private int calculateTop( View child, boolean duringLayout ) {
return mPaddingTop;
}
/**
* Gets the total width.
*
* @return the total width
*/
private int getTotalWidth() {
return getWidth();
}
/**
* Gets the screen scroll position x.
*
* @param screen
* the screen
* @return the screen scroll position x
*/
private int getScreenScrollPositionX( int screen ) {
return ( screen * getTotalWidth() );
}
/**
* Fill to gallery right.
*/
private void fillToGalleryRight() {
int itemSpacing = 0;
int galleryRight = getScreenScrollPositionX( mCurrentScreen + 2 );
int numChildren = getChildCount();
int numItems = mItemCount;
// Set state for initial iteration
View prevIterationView = getChildAt( numChildren - 1 );
int curPosition;
int curLeftEdge;
if ( prevIterationView != null ) {
curPosition = mFirstPosition + numChildren;
curLeftEdge = prevIterationView.getRight() + itemSpacing;
} else {
mFirstPosition = curPosition = mItemCount - 1;
curLeftEdge = mPaddingLeft;
}
while ( curLeftEdge < galleryRight && curPosition < numItems ) {
prevIterationView = makeAndAddView( curPosition, curPosition - mCurrentScreen, curLeftEdge, true );
// Set state for next iteration
curLeftEdge = prevIterationView.getRight() + itemSpacing;
curPosition++;
}
}
/**
* Fill to gallery left.
*/
private void fillToGalleryLeft() {
int itemSpacing = 0;
int galleryLeft = getScreenScrollPositionX( mCurrentScreen - 1 );
// Set state for initial iteration
View prevIterationView = getChildAt( 0 );
int curPosition;
int curRightEdge;
if ( prevIterationView != null ) {
curPosition = mFirstPosition - 1;
curRightEdge = prevIterationView.getLeft() - itemSpacing;
} else {
// No children available!
curPosition = 0;
curRightEdge = getRight() - getLeft() - mPaddingRight;
}
while ( curRightEdge > galleryLeft && curPosition >= 0 ) {
prevIterationView = makeAndAddView( curPosition, curPosition - mCurrentScreen, curRightEdge, false );
// Remember some state
mFirstPosition = curPosition;
// Set state for next iteration
curRightEdge = prevIterationView.getLeft() - itemSpacing;
curPosition--;
}
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#generateDefaultLayoutParams()
*/
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT );
}
/**
* Reset list.
*/
void resetList() {
while ( getChildCount() > 0 ) {
View view = getChildAt( 0 );
detachViewFromParent( view );
removeDetachedView( view, false );
}
emptyRecycler();
mOldSelectedPosition = INVALID_SCREEN;
setSelectedPositionInt( INVALID_SCREEN );
setNextSelectedPositionInt( INVALID_SCREEN );
mPreviousScreen = INVALID_SCREEN;
postInvalidate();
}
private void emptyRecycler() {
if ( null != mRecycleBin ) {
while ( mRecycleBin.size() > 0 ) {
Queue<View> recycler = mRecycleBin.remove( 0 );
recycler.clear();
}
mRecycleBin.clear();
}
}
/**
* Sets the next selected position int.
*
* @param screen
* the new next selected position int
*/
private void setNextSelectedPositionInt( int screen ) {
mNextScreen = screen;
}
/**
* Sets the selected position int.
*
* @param screen
* the new selected position int
*/
private void setSelectedPositionInt( int screen ) {
mCurrentScreen = screen;
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean)
*/
@Override
public boolean requestChildRectangleOnScreen( View child, Rect rectangle, boolean immediate ) {
int screen = indexOfChild( child ) + mFirstPosition;
if ( screen != mCurrentScreen || !mScroller.isFinished() ) {
snapToScreen( screen );
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#onRequestFocusInDescendants(int, android.graphics.Rect)
*/
@Override
protected boolean onRequestFocusInDescendants( int direction, Rect previouslyFocusedRect ) {
if ( mItemCount < 1 ) return false;
if ( isEnabled() ) {
int focusableScreen;
if ( mNextScreen != INVALID_SCREEN ) {
focusableScreen = mNextScreen;
} else {
focusableScreen = mCurrentScreen;
}
if ( focusableScreen != INVALID_SCREEN ) {
View child = getChildAt( focusableScreen );
if ( null != child ) child.requestFocus( direction, previouslyFocusedRect );
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#dispatchUnhandledMove(android.view.View, int)
*/
@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() < mItemCount - 1 ) {
snapToScreen( getCurrentScreen() + 1 );
return true;
}
}
return super.dispatchUnhandledMove( focused, direction );
}
/*
* (non-Javadoc)
*
* @see android.view.View#setEnabled(boolean)
*/
@Override
public void setEnabled( boolean enabled ) {
super.setEnabled( enabled );
for ( int i = 0; i < getChildCount(); i++ ) {
getChildAt( i ).setEnabled( enabled );
}
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#addFocusables(java.util.ArrayList, int, int)
*/
@Override
public void addFocusables( ArrayList<View> views, int direction, int focusableMode ) {
if ( isEnabled() ) {
View child = getChildAt( mCurrentScreen );
if ( null != child ) {
child.addFocusables( views, direction );
}
if ( direction == View.FOCUS_LEFT ) {
if ( mCurrentScreen > 0 ) {
child = getChildAt( mCurrentScreen - 1 );
if ( null != child ) {
child.addFocusables( views, direction );
}
}
} else if ( direction == View.FOCUS_RIGHT ) {
if ( mCurrentScreen < mItemCount - 1 ) {
child = getChildAt( mCurrentScreen + 1 );
if ( null != child ) {
child.addFocusables( views, direction );
}
}
}
}
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)
*/
@Override
public boolean onInterceptTouchEvent( MotionEvent ev ) {
final int action = ev.getAction();
if ( !isEnabled() ) {
return false; // We don't want the events. Let them fall through to the all apps view.
}
if ( ( action == MotionEvent.ACTION_MOVE ) && ( mTouchState != TOUCH_STATE_REST ) ) {
return true;
}
acquireVelocityTrackerAndAddMovement( ev );
switch ( action & MotionEvent.ACTION_MASK ) {
case MotionEvent.ACTION_MOVE: {
/*
* Locally do absolute value. mLastMotionX is set to the y value of the down event.
*/
final int pointerIndex = ev.findPointerIndex( mActivePointerId );
if ( pointerIndex < 0 ) {
// invalid pointer
return true;
}
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 xMoved = xDiff > touchSlop;
boolean yMoved = yDiff > touchSlop;
mLastMotionX2 = x;
if ( xMoved || yMoved ) {
if ( xMoved ) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_SCROLLING;
mLastMotionX = x;
mTouchX = getScrollX();
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
enableChildrenCache( mCurrentScreen - 1, mCurrentScreen + 1 );
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
// Remember location of down touch
mLastMotionX = x;
mLastMotionX2 = x;
mLastMotionY = y;
mActivePointerId = ev.getPointerId( 0 );
mAllowLongPress = true;
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// Release the drag
clearChildrenCache();
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
mAllowLongPress = false;
releaseVelocityTracker();
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;
}
/**
* On secondary pointer up.
*
* @param ev
* the ev
*/
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 = ev.getX( newPointerIndex );
mLastMotionX2 = ev.getX( newPointerIndex );
mLastMotionY = ev.getY( newPointerIndex );
mActivePointerId = ev.getPointerId( newPointerIndex );
if ( mVelocityTracker != null ) {
mVelocityTracker.clear();
}
}
}
/**
* 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.
*
* @param focused
* the focused
*/
@Override
public void focusableViewAvailable( View focused ) {
View current = getChildAt( 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;
}
}
}
/**
* Enable children cache.
*
* @param fromScreen
* the from screen
* @param toScreen
* the to screen
*/
public void enableChildrenCache( int fromScreen, int toScreen ) {
if ( !mCacheEnabled ) return;
if ( fromScreen > toScreen ) {
final int temp = fromScreen;
fromScreen = toScreen;
toScreen = temp;
}
final int count = getChildCount();
fromScreen = Math.max( fromScreen, 0 );
toScreen = Math.min( toScreen, count - 1 );
for ( int i = fromScreen; i <= toScreen; i++ ) {
final CellLayout layout = (CellLayout) getChildAt( i );
layout.setChildrenDrawnWithCacheEnabled( true );
layout.setChildrenDrawingCacheEnabled( true );
}
}
/**
* Clear children cache.
*/
public void clearChildrenCache() {
if ( !mCacheEnabled ) return;
final int count = getChildCount();
for ( int i = 0; i < count; i++ ) {
final CellLayout layout = (CellLayout) getChildAt( i );
layout.setChildrenDrawnWithCacheEnabled( false );
layout.setChildrenDrawingCacheEnabled( false );
}
}
public void setCacheEnabled( boolean value ) {
mCacheEnabled = value;
}
/*
* (non-Javadoc)
*
* @see android.view.View#onTouchEvent(android.view.MotionEvent)
*/
@Override
public boolean onTouchEvent( MotionEvent ev ) {
final int action = ev.getAction();
if ( !isEnabled() ) {
if ( !mScroller.isFinished() ) {
mScroller.abortAnimation();
}
snapToScreen( mCurrentScreen );
return false; // We don't want the events. Let them fall through to the all apps view.
}
acquireVelocityTrackerAndAddMovement( ev );
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
mLastMotionX = ev.getX();
mLastMotionX2 = ev.getX();
mActivePointerId = ev.getPointerId( 0 );
if ( mTouchState == TOUCH_STATE_SCROLLING ) {
enableChildrenCache( mCurrentScreen - 1, mCurrentScreen + 1 );
}
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 float deltaX = mLastMotionX - x;
final float deltaX2 = mLastMotionX2 - x;
final int mode = mOverScrollMode;
mLastMotionX = x;
if ( deltaX < 0 ) {
mTouchX += deltaX;
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
if ( mTouchX < 0 && mode != OVER_SCROLL_NEVER ) {
mTouchX = mLastMotionX = 0;
// mLastMotionX = x;
if ( mEdgeGlowLeft != null && deltaX2 < 0 ) {
float overscroll = ( (float) -deltaX2 * 1.5f ) / getWidth();
mEdgeGlowLeft.onPull( overscroll );
if ( !mEdgeGlowRight.isFinished() ) {
mEdgeGlowRight.onRelease();
}
}
}
invalidate();
} else if ( deltaX > 0 ) {
final int totalWidth = getScreenScrollPositionX( mItemCount - 1 );
final float availableToScroll = getScreenScrollPositionX( mItemCount ) - mTouchX;
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
mTouchX += Math.min( availableToScroll, deltaX );
if ( availableToScroll <= getWidth() && mode != OVER_SCROLL_NEVER ) {
mTouchX = mLastMotionX = totalWidth;
// mLastMotionX = x;
if ( mEdgeGlowLeft != null && deltaX2 > 0 ) {
float overscroll = ( (float) deltaX2 * 1.5f ) / getWidth();
mEdgeGlowRight.onPull( overscroll );
if ( !mEdgeGlowLeft.isFinished() ) {
mEdgeGlowLeft.onRelease();
}
}
}
invalidate();
} else {
awakenScrollBars();
}
}
break;
case MotionEvent.ACTION_UP:
if ( mTouchState == TOUCH_STATE_SCROLLING ) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity( 1000, mMaximumVelocity );
final int velocityX = (int) velocityTracker.getXVelocity( mActivePointerId );
final int screenWidth = getWidth();
final int whichScreen = ( getScrollX() + ( screenWidth / 2 ) ) / screenWidth;
final float scrolledPos = (float) getScrollX() / screenWidth;
if ( velocityX > SNAP_VELOCITY && mCurrentScreen > 0 ) {
// Fling hard enough to move left.
// Don't fling across more than one screen at a time.
final int bound = scrolledPos < whichScreen ? mCurrentScreen - 1 : mCurrentScreen;
snapToScreen( Math.min( whichScreen, bound ), velocityX, true );
} else if ( velocityX < -SNAP_VELOCITY && mCurrentScreen < mItemCount - 1 ) {
// Fling hard enough to move right
// Don't fling across more than one screen at a time.
final int bound = scrolledPos > whichScreen ? mCurrentScreen + 1 : mCurrentScreen;
snapToScreen( Math.max( whichScreen, bound ), velocityX, true );
} else {
snapToScreen( whichScreen, 0, true );
}
if ( mEdgeGlowLeft != null ) {
mEdgeGlowLeft.onRelease();
mEdgeGlowRight.onRelease();
}
}
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
releaseVelocityTracker();
break;
case MotionEvent.ACTION_CANCEL:
if ( mTouchState == TOUCH_STATE_SCROLLING ) {
final int screenWidth = getWidth();
final int whichScreen = ( getScrollX() + ( screenWidth / 2 ) ) / screenWidth;
snapToScreen( whichScreen, 0, true );
}
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
releaseVelocityTracker();
if ( mEdgeGlowLeft != null ) {
mEdgeGlowLeft.onRelease();
mEdgeGlowRight.onRelease();
}
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp( ev );
break;
}
return true;
}
/**
* Acquire velocity tracker and add movement.
*
* @param ev
* the ev
*/
private void acquireVelocityTrackerAndAddMovement( MotionEvent ev ) {
if ( mVelocityTracker == null ) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement( ev );
}
/**
* Release velocity tracker.
*/
private void releaseVelocityTracker() {
if ( mVelocityTracker != null ) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* Snap to screen.
*
* @param whichScreen
* the which screen
*/
void snapToScreen( int whichScreen ) {
snapToScreen( whichScreen, 0, false );
}
/**
* Snap to screen.
*
* @param whichScreen
* the which screen
* @param velocity
* the velocity
* @param settle
* the settle
*/
private void snapToScreen( int whichScreen, int velocity, boolean settle ) {
whichScreen = Math.max( 0, Math.min( whichScreen, mItemCount - 1 ) );
enableChildrenCache( mCurrentScreen, whichScreen );
setNextSelectedPositionInt( whichScreen );
View focusedChild = getFocusedChild();
if ( focusedChild != null && whichScreen != mCurrentScreen && focusedChild == getChildAt( mCurrentScreen ) ) {
focusedChild.clearFocus();
}
final int screenDelta = Math.max( 1, Math.abs( whichScreen - mCurrentScreen ) );
final int newX = whichScreen * getWidth();
final int delta = newX - getScrollX();
int duration = ( screenDelta + 1 ) * 100;
if ( !mScroller.isFinished() ) {
mScroller.abortAnimation();
}
/*
* if ( mScrollInterpolator instanceof WorkspaceOvershootInterpolator ) { if ( settle ) { ( (WorkspaceOvershootInterpolator)
* mScrollInterpolator ).setDistance( screenDelta ); } else { ( (WorkspaceOvershootInterpolator) mScrollInterpolator
* ).disableSettle(); } }
*/
velocity = Math.abs( velocity );
if ( velocity > 0 ) {
duration += ( duration / ( velocity / BASELINE_FLING_VELOCITY ) ) * FLING_VELOCITY_INFLUENCE;
} else {
duration += 100;
}
mScroller.startScroll( getScrollX(), 0, delta, 0, duration );
int mode = getOverScroll();
if ( delta != 0 && ( mode == OVER_SCROLL_IF_CONTENT_SCROLLS ) ) {
edgeReached( whichScreen, delta, velocity );
}
invalidate();
}
private void postUpdateIndicator( final int screen, final int count ) {
getHandler().post( new Runnable() {
@Override
public void run() {
if ( mIndicator != null ) mIndicator.setLevel( screen, count );
}
} );
}
/**
* Edge reached.
*
* @param whichscreen
* the whichscreen
* @param delta
* the delta
* @param vel
* the vel
*/
void edgeReached( int whichscreen, int delta, int vel ) {
if ( whichscreen == 0 || whichscreen == ( mItemCount - 1 ) ) {
if ( delta < 0 ) {
mEdgeGlowLeft.onAbsorb( vel );
} else {
mEdgeGlowRight.onAbsorb( vel );
}
}
}
/*
* (non-Javadoc)
*
* @see android.view.View#onSaveInstanceState()
*/
@Override
protected Parcelable onSaveInstanceState() {
final SavedState state = new SavedState( super.onSaveInstanceState() );
state.currentScreen = mCurrentScreen;
return state;
}
/*
* (non-Javadoc)
*
* @see android.view.View#onRestoreInstanceState(android.os.Parcelable)
*/
@Override
protected void onRestoreInstanceState( Parcelable state ) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState( savedState.getSuperState() );
if ( savedState.currentScreen != -1 ) {
mCurrentScreen = savedState.currentScreen;
}
}
/**
* Scroll left.
*/
public void scrollLeft() {
if ( mScroller.isFinished() ) {
if ( mCurrentScreen > 0 ) snapToScreen( mCurrentScreen - 1 );
} else {
if ( mNextScreen > 0 ) snapToScreen( mNextScreen - 1 );
}
}
/**
* Scroll right.
*/
public void scrollRight() {
if ( mScroller.isFinished() ) {
if ( mCurrentScreen < mItemCount - 1 ) snapToScreen( mCurrentScreen + 1 );
} else {
if ( mNextScreen < mItemCount - 1 ) snapToScreen( mNextScreen + 1 );
}
}
/**
* Gets the screen for view.
*
* @param v
* the v
* @return the screen for view
*/
public int getScreenForView( View v ) {
int result = -1;
if ( v != null ) {
ViewParent vp = v.getParent();
int count = mItemCount;
for ( int i = 0; i < count; i++ ) {
if ( vp == getChildAt( i ) ) {
return i;
}
}
}
return result;
}
/**
* Gets the view for tag.
*
* @param tag
* the tag
* @return the view for tag
*/
public View getViewForTag( Object tag ) {
int screenCount = mItemCount;
for ( int screen = 0; screen < screenCount; screen++ ) {
CellLayout currentScreen = ( (CellLayout) getChildAt( screen ) );
int count = currentScreen.getChildCount();
for ( int i = 0; i < count; i++ ) {
View child = currentScreen.getChildAt( i );
if ( child.getTag() == tag ) {
return child;
}
}
}
return null;
}
/**
* Allow long press.
*
* @return True is long presses are still allowed for the current touch
*/
public boolean allowLongPress() {
return mAllowLongPress;
}
/**
* Set true to allow long-press events to be triggered, usually checked by {@link Launcher} to accept or block dpad-initiated
* long-presses.
*
* @param allowLongPress
* the new allow long press
*/
public void setAllowLongPress( boolean allowLongPress ) {
mAllowLongPress = allowLongPress;
}
/**
* Move to default screen.
*
* @param animate
* the animate
*/
void moveToDefaultScreen( boolean animate ) {
if ( animate ) {
snapToScreen( mDefaultScreen );
} else {
setCurrentScreen( mDefaultScreen );
}
getChildAt( mDefaultScreen ).requestFocus();
}
/**
* Sets the indicator.
*
* @param indicator
* the new indicator
*/
public void setIndicator( WorkspaceIndicator indicator ) {
mIndicator = indicator;
mIndicator.setLevel( mCurrentScreen, mItemCount );
}
/**
* The Class SavedState.
*/
public static class SavedState extends BaseSavedState {
/** The current screen. */
int currentScreen = -1;
/**
* Instantiates a new saved state.
*
* @param superState
* the super state
*/
SavedState( Parcelable superState ) {
super( superState );
}
/**
* Instantiates a new saved state.
*
* @param in
* the in
*/
private SavedState( Parcel in ) {
super( in );
currentScreen = in.readInt();
}
/*
* (non-Javadoc)
*
* @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int)
*/
@Override
public void writeToParcel( Parcel out, int flags ) {
super.writeToParcel( out, flags );
out.writeInt( currentScreen );
}
/** The Constant CREATOR. */
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel( Parcel in ) {
return new SavedState( in );
}
@Override
public SavedState[] newArray( int size ) {
return new SavedState[size];
}
};
}
/**
* An asynchronous update interface for receiving notifications about WorkspaceDataSet information as the WorkspaceDataSet is
* constructed.
*/
class WorkspaceDataSetObserver extends DataSetObserver {
/*
* (non-Javadoc)
*
* @see android.database.DataSetObserver#onChanged()
*/
@Override
public void onChanged() {
super.onChanged();
}
/*
* (non-Javadoc)
*
* @see android.database.DataSetObserver#onInvalidated()
*/
@Override
public void onInvalidated() {
super.onInvalidated();
}
}
/**
* The Class RecycleBin.
*/
class RecycleBin {
/** The array. */
protected View[][] array;
/** The start. */
protected int start[];
/** The end. */
protected int end[];
/** The max size. */
protected int maxSize;
/** The full. */
protected boolean full[];
/**
* Instantiates a new recycle bin.
*
* @param typeCount
* the type count
* @param size
* the size
*/
public RecycleBin( int typeCount, int size ) {
maxSize = size;
array = new View[typeCount][size];
start = new int[typeCount];
end = new int[typeCount];
full = new boolean[typeCount];
}
/**
* Checks if is empty.
*
* @param type
* the type
* @return true, if is empty
*/
public boolean isEmpty( int type ) {
return ( ( start[type] == end[type] ) && !full[type] );
}
/**
* Adds the.
*
* @param type
* the type
* @param o
* the o
*/
public void add( int type, View o ) {
if ( !full[type] ) array[type][start[type] = ( ++start[type] % array[type].length )] = o;
if ( start[type] == end[type] ) full[type] = true;
}
/**
* Removes the.
*
* @param type
* the type
* @return the view
*/
public View remove( int type ) {
if ( full[type] ) {
full[type] = false;
} else if ( isEmpty( type ) ) return null;
return array[type][end[type] = ( ++end[type] % array[type].length )];
}
/**
* Clear.
*/
void clear() {
int typeCount = array.length;
for ( int i = 0; i < typeCount; i++ ) {
while ( !isEmpty( i ) ) {
final View view = remove( i );
if ( view != null ) {
removeDetachedView( view, true );
}
}
}
array = new View[typeCount][maxSize];
start = new int[typeCount];
end = new int[typeCount];
full = new boolean[typeCount];
}
}
/**
* Gets the screen at.
*
* @param screen
* the screen
* @return the screen at
*/
public View getScreenAt( int screen ) {
return getChildAt( screen - mFirstPosition );
}
}