Merge change 40 into donut

* changes:
  Make Launcher more forgiving when dragging desktop items.
This commit is contained in:
Android (Google) Code Review
2009-04-10 16:18:20 -07:00
8 changed files with 273 additions and 78 deletions
+1
View File
@@ -24,4 +24,5 @@
<color name="delete_color_filter">#A5FF0000</color>
<color name="appwidget_error_color">#fccc</color>
<color name="snag_callout_color">#f444</color>
</resources>
+58 -50
View File
@@ -174,7 +174,7 @@ public class CellLayout extends ViewGroup {
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
final boolean[][] occupied = mOccupied;
findOccupiedCells(xCount, yCount, occupied);
findOccupiedCells(xCount, yCount, occupied, null);
cellInfo.cell = null;
cellInfo.cellX = cellXY[0];
@@ -215,7 +215,7 @@ public class CellLayout extends ViewGroup {
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
final boolean[][] occupied = mOccupied;
findOccupiedCells(xCount, yCount, occupied);
findOccupiedCells(xCount, yCount, occupied, null);
findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
@@ -315,7 +315,7 @@ public class CellLayout extends ViewGroup {
return true;
}
CellInfo findAllVacantCells(boolean[] occupiedCells) {
CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
final boolean portrait = mPortrait;
final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
@@ -329,7 +329,7 @@ public class CellLayout extends ViewGroup {
}
}
} else {
findOccupiedCells(xCount, yCount, occupied);
findOccupiedCells(xCount, yCount, occupied, ignoreView);
}
CellInfo cellInfo = new CellInfo();
@@ -527,64 +527,72 @@ public class CellLayout extends ViewGroup {
super.setChildrenDrawnWithCacheEnabled(enabled);
}
boolean acceptChildDrop(int x, int y, int cellHSpan, int cellVSpan, View cell) {
int[] cellXY = mCellXY;
pointToCellRounded(x, y, cellXY);
int cellX = cellXY[0];
int cellY = cellXY[1];
return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
}
/**
* Finds the first View intersecting with the specified cell. If the cell is outside
* of the layout, this is returned.
*
* @param cellX The X location of the cell to test.
* @param cellY The Y location of the cell to test.
* @param cellHSpan The horizontal span of the cell to test.
* @param cellVSpan The vertical span of the cell to test.
* @param ignoreCell View to ignore during the test.
*
* @return Returns the first View intersecting with the specified cell, this if the cell
* lies outside of this layout's grid or null if no View was found.
* Find a vacant area that will fit the given bounds nearest the requested
* cell location. Uses Euclidean distance to score multiple vacant areas.
*
* @param cellX The X location of the desired location.
* @param cellY The Y location of the desired location.
* @param spanX Horizontal span of the object.
* @param spanY Vertical span of the object.
* @param vacantCells Pre-computed set of vacant cells to search.
* @param recycle Previously returned value to possibly recycle.
* @return The X, Y cell of a vacant area that can contain this object,
* nearest the requested location.
*/
View findCell(int cellX, int cellY, int cellHSpan, int cellVSpan, View ignoreCell) {
if (cellX < 0 || cellX + cellHSpan > (mPortrait ? mShortAxisCells : mLongAxisCells) ||
cellY < 0 || cellY + cellVSpan > (mPortrait ? mLongAxisCells : mShortAxisCells)) {
return this;
int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
CellInfo vacantCells, int[] recycle) {
// Keep track of best-scoring drop area
final int[] bestXY = recycle != null ? recycle : new int[2];
final int[] cellXY = mCellXY;
double bestDistance = Double.MAX_VALUE;
// Bail early if vacant cells aren't valid
if (!vacantCells.valid) {
return null;
}
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View view = getChildAt(i);
if (view == ignoreCell) {
// Look across all vacant cells for best fit
final int size = vacantCells.vacantCells.size();
for (int i = 0; i < size; i++) {
final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
// Reject if vacant cell isn't our exact size
if (cell.spanX != spanX || cell.spanY != spanY) {
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (cellX < lp.cellX + lp.cellHSpan && lp.cellX < cellX + cellHSpan &&
cellY < lp.cellY + lp.cellVSpan && lp.cellY < cellY + cellVSpan) {
return view;
// Score is center distance from requested pixel
cellToPoint(cell.cellX, cell.cellY, cellXY);
double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
Math.pow(cellXY[1] - pixelY, 2));
if (distance <= bestDistance) {
bestDistance = distance;
bestXY[0] = cell.cellX;
bestXY[1] = cell.cellY;
}
}
return null;
// Return null if no suitable location found
if (bestDistance < Double.MAX_VALUE) {
return bestXY;
} else {
return null;
}
}
/**
* Drop a child at the specified position
*
* @param child The child that is being dropped
* @param cellX The child's new x location
* @param cellY The child's new y location
* @param targetXY Destination area to move to
*/
void onDropChild(View child, int cellX, int cellY) {
int[] cellXY = mCellXY;
pointToCellRounded(cellX, cellY, cellXY);
void onDropChild(View child, int[] targetXY) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.cellX = cellXY[0];
lp.cellY = cellXY[1];
lp.cellX = targetXY[0];
lp.cellY = targetXY[1];
lp.isDragging = false;
mDragRect.setEmpty();
child.requestLayout();
@@ -688,7 +696,7 @@ public class CellLayout extends ViewGroup {
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
final boolean[][] occupied = mOccupied;
findOccupiedCells(xCount, yCount, occupied);
findOccupiedCells(xCount, yCount, occupied, null);
return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
}
@@ -723,7 +731,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
final boolean[][] occupied = mOccupied;
findOccupiedCells(xCount, yCount, occupied);
findOccupiedCells(xCount, yCount, occupied, null);
final boolean[] flat = new boolean[xCount * yCount];
for (int y = 0; y < yCount; y++) {
@@ -735,7 +743,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
return flat;
}
private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
for (int x = 0; x < xCount; x++) {
for (int y = 0; y < yCount; y++) {
occupied[x][y] = false;
@@ -745,7 +753,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child instanceof Folder) {
if (child instanceof Folder || child.equals(ignoreView)) {
continue;
}
LayoutParams lp = (LayoutParams) child.getLayoutParams();
+5
View File
@@ -19,6 +19,7 @@ package com.android.launcher;
import android.widget.ImageView;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.TranslateAnimation;
@@ -77,6 +78,10 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
Object dragInfo) {
return true;
}
public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
return null;
}
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
final ItemInfo item = (ItemInfo) dragInfo;
+80 -4
View File
@@ -113,8 +113,25 @@ public class DragLayer extends FrameLayout implements DragController {
private DropTarget mLastDropTarget;
private final Paint mTrashPaint = new Paint();
private final Paint mEstimatedPaint = new Paint();
private Paint mDragPaint;
/**
* If true, draw a "snag" showing where the object currently being dragged
* would end up if dropped from current location.
*/
private static final boolean DRAW_TARGET_SNAG = false;
private Rect mEstimatedRect = new Rect();
private float[] mDragCenter = new float[2];
private float[] mEstimatedCenter = new float[2];
private boolean mDrawEstimated = false;
private int mTriggerWidth = -1;
private int mTriggerHeight = -1;
private static final int DISTANCE_DRAW_SNAG = 20;
private static final int ANIMATION_STATE_STARTING = 1;
private static final int ANIMATION_STATE_RUNNING = 2;
private static final int ANIMATION_STATE_DONE = 3;
@@ -141,6 +158,13 @@ public class DragLayer extends FrameLayout implements DragController {
final int srcColor = context.getResources().getColor(R.color.delete_color_filter);
mTrashPaint.setColorFilter(new PorterDuffColorFilter(srcColor, PorterDuff.Mode.SRC_ATOP));
// Make estimated paint area in gray
int snagColor = context.getResources().getColor(R.color.snag_callout_color);
mEstimatedPaint.setColor(snagColor);
mEstimatedPaint.setStrokeWidth(3);
mEstimatedPaint.setAntiAlias(true);
}
public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
@@ -177,6 +201,9 @@ public class DragLayer extends FrameLayout implements DragController {
int width = viewBitmap.getWidth();
int height = viewBitmap.getHeight();
mTriggerWidth = width * 2 / 3;
mTriggerHeight = height * 2 / 3;
Matrix scale = new Matrix();
float scaleFactor = v.getWidth();
scaleFactor = (scaleFactor + DRAG_SCALE) /scaleFactor;
@@ -252,6 +279,13 @@ public class DragLayer extends FrameLayout implements DragController {
break;
}
} else {
// Only draw estimate drop "snag" when requested
if (DRAW_TARGET_SNAG && mDrawEstimated) {
canvas.drawLine(mDragCenter[0], mDragCenter[1], mEstimatedCenter[0], mEstimatedCenter[1], mEstimatedPaint);
canvas.drawCircle(mEstimatedCenter[0], mEstimatedCenter[1], 8, mEstimatedPaint);
}
// Draw actual icon being dragged
canvas.drawBitmap(mDragBitmap,
mScrollX + mLastMotionX - mTouchOffsetX - mBitmapOffsetX,
mScrollY + mLastMotionY - mTouchOffsetY - mBitmapOffsetY, mDragPaint);
@@ -355,8 +389,16 @@ public class DragLayer extends FrameLayout implements DragController {
left = (int) (scrollX + x - touchX - offsetX);
top = (int) (scrollY + y - touchY - offsetY);
// Invalidate current icon position
rect.union(left - 1, top - 1, left + width + 1, top + height + 1);
invalidate(rect);
mDragCenter[0] = rect.centerX();
mDragCenter[1] = rect.centerY();
// Invalidate any old estimated location
if (DRAW_TARGET_SNAG && mDrawEstimated) {
rect.union(mEstimatedRect);
}
final int[] coordinates = mDropCoordinates;
DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
@@ -378,6 +420,33 @@ public class DragLayer extends FrameLayout implements DragController {
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
}
}
// Render estimated drop "snag" only outside of width
mDrawEstimated = false;
if (DRAW_TARGET_SNAG && dropTarget != null) {
Rect foundEstimate = dropTarget.estimateDropLocation(mDragSource,
(int) (scrollX + mLastMotionX), (int) (scrollY + mLastMotionY),
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo, mEstimatedRect);
if (foundEstimate != null) {
mEstimatedCenter[0] = foundEstimate.centerX();
mEstimatedCenter[1] = foundEstimate.centerY();
int deltaX = (int) Math.abs(mEstimatedCenter[0] - mDragCenter[0]);
int deltaY = (int) Math.abs(mEstimatedCenter[1] - mDragCenter[1]);
if (deltaX > mTriggerWidth || deltaY > mTriggerHeight) {
mDrawEstimated = true;
}
}
}
// Include new estimated area in invalidated rectangle
if (DRAW_TARGET_SNAG && mDrawEstimated) {
rect.union(mEstimatedRect);
}
invalidate(rect);
mLastDropTarget = dropTarget;
boolean inDragRegion = false;
@@ -478,9 +547,15 @@ public class DragLayer extends FrameLayout implements DragController {
}
if (target == null) {
if (child instanceof DropTarget) {
dropCoordinates[0] = x;
dropCoordinates[1] = y;
return (DropTarget) child;
// Only consider this child if they will accept
DropTarget childTarget = (DropTarget) child;
if (childTarget.acceptDrop(mDragSource, x, y, 0, 0, mDragInfo)) {
dropCoordinates[0] = x;
dropCoordinates[1] = y;
return (DropTarget) child;
} else {
return null;
}
}
} else {
return target;
@@ -531,6 +606,7 @@ public class DragLayer extends FrameLayout implements DragController {
public void run() {
if (mDragScroller != null) {
mDrawEstimated = false;
if (mDirection == SCROLL_LEFT) {
mDragScroller.scrollLeft();
} else {
+30 -8
View File
@@ -16,6 +16,8 @@
package com.android.launcher;
import android.graphics.Rect;
/**
* Interface defining an object that can receive a drag.
*
@@ -42,18 +44,38 @@ public interface DropTarget {
void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);
/**
* Indicates whether a drop action can occur at the specified location. The method
* {@link #onDrop(DragSource, int, int, int, int, Object)} will be invoked on this
* drop target only if this method returns true.
*
* Check if a drop action can occur at, or near, the requested location.
* This may be called repeatedly during a drag, so any calls should return
* quickly.
*
* @param source DragSource where the drag started
* @param x X coordinate of the drop location
* @param y Y coordinate of the drop location
* @param xOffset Horizontal offset with the object being dragged where the original touch happened
* @param yOffset Vertical offset with the object being dragged where the original touch happened
* @param xOffset Horizontal offset with the object being dragged where the
* original touch happened
* @param yOffset Vertical offset with the object being dragged where the
* original touch happened
* @param dragInfo Data associated with the object being dragged
*
* return True if the drop is accepted, false otherwise.
* @return True if the drop will be accepted, false otherwise.
*/
boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);
/**
* Estimate the surface area where this object would land if dropped at the
* given location.
*
* @param source DragSource where the drag started
* @param x X coordinate of the drop location
* @param y Y coordinate of the drop location
* @param xOffset Horizontal offset with the object being dragged where the
* original touch happened
* @param yOffset Vertical offset with the object being dragged where the
* original touch happened
* @param dragInfo Data associated with the object being dragged
* @param recycle {@link Rect} object to be possibly recycled.
* @return Estimated area that would be occupied if object was dropped at
* the given location. Should return null if no estimate is found,
* or if this target doesn't provide estimations.
*/
Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle);
}
+5
View File
@@ -18,6 +18,7 @@ package com.android.launcher;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -69,6 +70,10 @@ public class FolderIcon extends BubbleTextView implements DropTarget {
&& item.container != mInfo.id;
}
public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
return null;
}
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
final ApplicationInfo item = (ApplicationInfo) dragInfo;
// TODO: update open folder that is looking at this data
+5
View File
@@ -1,6 +1,7 @@
package com.android.launcher;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,6 +34,10 @@ public class UserFolder extends Folder implements DropTarget {
return (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && item.container != mInfo.id;
}
public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
return null;
}
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
final ApplicationInfo item = (ApplicationInfo) dragInfo;
+89 -16
View File
@@ -48,7 +48,7 @@ import java.util.ArrayList;
*/
public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
private static final int INVALID_SCREEN = -1;
/**
* The velocity at which a fling gesture will cause us to snap to the next screen
*/
@@ -75,6 +75,11 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
* CellInfo for the cell that is currently being dragged
*/
private CellLayout.CellInfo mDragInfo;
/**
* Target drop area calculated during last acceptDrop call.
*/
private int[] mTargetCell = null;
private float mLastMotionX;
private float mLastMotionY;
@@ -88,8 +93,14 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
private Launcher mLauncher;
private DragController mDragger;
/**
* Cache of vacant cells, used during drag events and invalidated as needed.
*/
private CellLayout.CellInfo mVacantCache = null;
private int[] mTempCell = new int[2];
private int[] mTempEstimate = new int[2];
private boolean mAllowLongPress;
private boolean mLocked;
@@ -363,7 +374,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
if (group != null) {
return group.findAllVacantCells(occupied);
return group.findAllVacantCells(occupied, null);
}
return null;
}
@@ -890,7 +901,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
final CellLayout cellLayout = (CellLayout) getChildAt(mCurrentScreen);
final CellLayout cellLayout = getCurrentDropLayout();
if (source != this) {
onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
} else {
@@ -902,7 +913,9 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
originalCellLayout.removeView(cell);
cellLayout.addView(cell);
}
cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
mTargetCell = estimateDropCell(source, x - xOffset, y - yOffset,
mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
cellLayout.onDropChild(cell, mTargetCell);
final ItemInfo info = (ItemInfo)cell.getTag();
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
@@ -914,6 +927,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
Object dragInfo) {
mVacantCache = null;
}
public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
@@ -922,6 +936,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
Object dragInfo) {
mVacantCache = null;
}
private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
@@ -955,7 +970,8 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
cellLayout.addView(view, insertAtFirst ? 0 : -1);
view.setOnLongClickListener(mLongClickListener);
cellLayout.onDropChild(view, x, y);
mTargetCell = estimateDropCell(null, x, y, 1, 1, view, cellLayout, mTargetCell);
cellLayout.onDropChild(view, mTargetCell);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
final LauncherModel model = Launcher.getModel();
@@ -963,18 +979,73 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
}
public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
Object dragInfo) {
final CellLayout.CellInfo cellInfo = mDragInfo;
int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
/**
* Return the current {@link CellLayout}, correctly picking the destination
* screen while a scroll is in progress.
*/
private CellLayout getCurrentDropLayout() {
int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
return (CellLayout) getChildAt(index);
}
/**
* {@inheritDoc}
*/
public boolean acceptDrop(DragSource source, int x, int y,
int xOffset, int yOffset, Object dragInfo) {
// Workspaces accept everything
return true;
}
/**
* {@inheritDoc}
*/
public Rect estimateDropLocation(DragSource source, int x, int y,
int xOffset, int yOffset, Object dragInfo, Rect recycle) {
final CellLayout layout = getCurrentDropLayout();
final CellLayout.CellInfo cellInfo = mDragInfo;
final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
final View ignoreView = cellInfo == null ? null : cellInfo.cell;
final Rect location = recycle != null ? recycle : new Rect();
// Find drop cell and convert into rectangle
int[] dropCell = estimateDropCell(source, x - xOffset, y - yOffset,
spanX, spanY, ignoreView, layout, mTempCell);
if (dropCell == null) {
return null;
}
layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
location.left = mTempEstimate[0];
location.top = mTempEstimate[1];
layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
location.right = mTempEstimate[0];
location.bottom = mTempEstimate[1];
return location;
}
/**
* Calculate the nearest cell where the given object would be dropped.
*/
private int[] estimateDropCell(DragSource source, int pixelX, int pixelY,
int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
// Create vacant cell cache if none exists
if (mVacantCache == null) {
mVacantCache = layout.findAllVacantCells(null, ignoreView);
}
// Find the best target drop location
return layout.findNearestVacantArea(pixelX, pixelY,
spanX, spanY, mVacantCache, recycle);
}
void setLauncher(Launcher launcher) {
mLauncher = launcher;
}
@@ -1002,12 +1073,14 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
public void scrollLeft() {
mVacantCache = null;
if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
snapToScreen(mCurrentScreen - 1);
}
}
public void scrollRight() {
mVacantCache = null;
if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
mScroller.isFinished()) {
snapToScreen(mCurrentScreen + 1);