android左右两个滑动组件,Android: 仿Launcher Workspace左右滑动控件

package com.mylauncher;

import Android.app.WallpaperManager;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Rect;

import android.graphics.drawable.Drawable;

import android.os.IBinder;

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.Interpolator;

import android.widget.Scroller;

/**

* The workspace is a wide area with a wallpaper and a finite number of screens. Each

* screen contains a number of icons, folders or widgets the user can interact with.

* A workspace is meant to be used with a fixed width only.

*/

public class Workspace extends ViewGroup {

@SuppressWarnings({"UnusedDeclaration"})

private static final StringTAG="MyLauncher.Workspace";

private static final intINVALID_SCREEN= -1;

/**

* The velocity at which a fling gesture will cause us to snap to the next screen

*/

private static final intSNAP_VELOCITY=600;

private final WallpaperManager mWallpaperManager;

private int mDefaultScreen;

private booleanmFirstLayout=true;

private int mCurrentScreen;

private intmNextScreen=INVALID_SCREEN;

private Scroller mScroller;

private VelocityTracker mVelocityTracker;

/**

* Target drop area calculated during last acceptDrop call.

*/

private int[]mTargetCell=null;

private float mLastMotionX;

private float mLastMotionY;

private final static intTOUCH_STATE_REST=0;

private final static intTOUCH_STATE_SCROLLING=1;

private intmTouchState=TOUCH_STATE_REST;

private OnLongClickListener mLongClickListener;

private int[]mTempCell=newint[2];

private int[]mTempEstimate=newint[2];

private booleanmAllowLongPress=true;

private int mTouchSlop;

private int mMaximumVelocity;

private static final intINVALID_POINTER= -1;

private intmActivePointerId=INVALID_POINTER;

private Drawable mPreviousIndicator;

private Drawable mNextIndicator;

private static final floatNANOTIME_DIV=1000000000.0f;

private static final floatSMOOTHING_SPEED=0.75f;

private static final floatSMOOTHING_CONSTANT= (float) (0.016 / Math.log(SMOOTHING_SPEED));

private float mSmoothingTime;

private float mTouchX;

private WorkspaceOvershootInterpolator mScrollInterpolator;

private static final floatBASELINE_FLING_VELOCITY=2500.f;

private static final floatFLING_VELOCITY_INFLUENCE=0.4f;

private static class WorkspaceOvershootInterpolator implements Interpolator {

private static final floatDEFAULT_TENSION=1.3f;

private float mTension;

public WorkspaceOvershootInterpolator() {

mTension=DEFAULT_TENSION;

}

public void setDistance(int distance) {

mTension=distance>0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;

}

public void disableSettle() {

mTension=0.f;

}

public float getInterpolation(float t) {

// _o(t) = t * t * ((tension + 1) * t + tension)

// o(t) = _o(t - 1) + 1

t-=1.0f;

return t * t * ((mTension + 1) * t + mTension) + 1.0f;

}

}

/**

* Used to inflate the Workspace from XML.

*

* @param context The application's context.

* @param attrs The attribtues set containing the Workspace's customization values.

*/

public Workspace(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

/**

* Used to inflate the Workspace from XML.

*

* @param context The application's context.

* @param attrs The attribtues set containing the Workspace's customization values.

* @param defStyle Unused.

*/

public Workspace(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

mWallpaperManager= WallpaperManager.getInstance(context);

TypedArraya=context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);

mDefaultScreen= a.getInt(R.styleable.Workspace_defaultScreen, 1);

a.recycle();

setHapticFeedbackEnabled(false);

initWorkspace();

}

/**

* Initializes various states for this workspace.

*/

private void initWorkspace() {

Contextcontext=getContext();

mScrollInterpolator=newWorkspaceOvershootInterpolator();

mScroller=newScroller(context, mScrollInterpolator);

mCurrentScreen=mDefaultScreen;

final ViewConfigurationconfiguration=ViewConfiguration.get(getContext());

mTouchSlop=configuration.getScaledTouchSlop();

mMaximumVelocity=configuration.getScaledMaximumFlingVelocity();

}

boolean isDefaultScreenShowing() {

returnmCurrentScreen== mDefaultScreen;

}

/**

* Returns the index of the currently displayed screen.

*

* @return The index of the currently displayed screen.

*/

int getCurrentScreen() {

return mCurrentScreen;

}

/**

* Sets the current screen.

*

* @param currentScreen

*/

void setCurrentScreen(int currentScreen) {

if (!mScroller.isFinished()) mScroller.abortAnimation();

mCurrentScreen=Math.max(0, Math.min(currentScreen, getChildCount() - 1));

//mPreviousIndicator.setLevel(mCurrentScreen);

//mNextIndicator.setLevel(mCurrentScreen);

scrollTo(mCurrentScreen * getWidth(), 0);

invalidate();

}

/**

* 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 intcount=getChildCount();

for (inti=0; i

getChildAt(i).setOnLongClickListener(l);

}

}

private void updateWallpaperOffset(int scrollRange) {

IBindertoken=getWindowToken();

if (token != null) {

mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );

mWallpaperManager.setWallpaperOffsets(getWindowToken(),

Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);

}

}

@Override

public void scrollTo(int x, int y) {

super.scrollTo(x, y);

mTouchX=x;

mSmoothingTime=System.nanoTime() / NANOTIME_DIV;

}

@Override

public void computeScroll() {

if (mScroller.computeScrollOffset()) {

mTouchX=mScrollX= mScroller.getCurrX();

mSmoothingTime=System.nanoTime() / NANOTIME_DIV;

mScrollY=mScroller.getCurrY();

postInvalidate();

} else if (mNextScreen != INVALID_SCREEN) {

mCurrentScreen=Math.max(0, Math.min(mNextScreen, getChildCount() - 1));

//mPreviousIndicator.setLevel(mCurrentScreen);

//mNextIndicator.setLevel(mCurrentScreen);

mNextScreen=INVALID_SCREEN;

//clearChildrenCache();

} else if (mTouchState== TOUCH_STATE_SCROLLING) {

final floatnow=System.nanoTime() / NANOTIME_DIV;

final floate= (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);

final floatdx=mTouchX- mScrollX;

mScrollX += dx * e;

mSmoothingTime=now;

// Keep generating points as long as we're more than 1px away from the target

if (dx>1.f || dx

postInvalidate();

}

}

}

@Override

protected void dispatchDraw(Canvas canvas) {

booleanrestore=false;

intrestoreCount=0;

// ViewGroup.dispatchDraw() supports many features we don't need:

// clip to padding, layout animation, animation listener, disappearing

// children, etc. The following implementation attempts to fast-track

// the drawing dispatch by drawing only what we know needs to be drawn.

booleanfastDraw=mTouchState!= TOUCH_STATE_SCROLLING &&mNextScreen== INVALID_SCREEN;

// If we are not scrolling or flinging, draw only the current screen

if (fastDraw) {

drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());

} else {

final longdrawingTime=getDrawingTime();

final floatscrollPos= (float) mScrollX / getWidth();

final intleftScreen= (int) scrollPos;

final intrightScreen=leftScreen+ 1;

if (leftScreen>= 0) {

drawChild(canvas, getChildAt(leftScreen), drawingTime);

}

if (scrollPos != leftScreen && rightScreen

drawChild(canvas, getChildAt(rightScreen), drawingTime);

}

}

if (restore) {

canvas.restoreToCount(restoreCount);

}

}

protected void onAttachedToWindow() {

super.onAttachedToWindow();

computeScroll();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

final intwidth=MeasureSpec.getSize(widthMeasureSpec);

final intwidthMode=MeasureSpec.getMode(widthMeasureSpec);

if (widthMode != MeasureSpec.EXACTLY) {

throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");

}

final intheightMode=MeasureSpec.getMode(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 intcount=getChildCount();

for (inti=0; i

getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);

}

if (mFirstLayout) {

setHorizontalScrollBarEnabled(false);

scrollTo(mCurrentScreen * width, 0);

setHorizontalScrollBarEnabled(true);

updateWallpaperOffset(width * (getChildCount() - 1));

mFirstLayout=false;

}

}

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

intchildLeft=0;

final intcount=getChildCount();

for (inti=0; i

final Viewchild=getChildAt(i);

if (child.getVisibility() != View.GONE) {

final intchildchildWidth= child.getMeasuredWidth();

child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());

childLeft += childWidth;

}

}

}

@Override

public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {

intscreen=indexOfChild(child);

if (screen != mCurrentScreen || !mScroller.isFinished()) {

snapToScreen(screen);

return true;

}

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()

snapToScreen(getCurrentScreen() + 1);

return true;

}

}

return super.dispatchUnhandledMove(focused, direction);

}

@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 intaction=ev.getAction();

Log.d(TAG, "onInterceptTouchEvent, action: " + action + " mTouchState: " + mTouchState);

if ((action== MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {

Log.d(TAG, "onInterceptTouchEvent, MotionEvent.ACTION_MOVE mTouchState: " + mTouchState);

return true;

}

acquireVelocityTrackerAndAddMovement(ev);

switch (action & MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_MOVE: {

Log.d(TAG, "onInterceptTouchEvent.ACTION_MOVE.................");

/*

*mIsBeingDragged== false, otherwise the shortcut would have caught it. Check

* whether the user has moved far enough from his original down touch.

*/

/*

* Locally do absolute value. mLastMotionX is set to the y value

* of the down event.

*/

final intpointerIndex=ev.findPointerIndex(mActivePointerId);

final floatx=ev.getX(pointerIndex);

final floaty=ev.getY(pointerIndex);

final intxDiff= (int) Math.abs(x - mLastMotionX);

final intyDiff= (int) Math.abs(y - mLastMotionY);

final inttouchSlop=mTouchSlop;

booleanxMoved=xDiff>touchSlop;

booleanyMoved=yDiff>touchSlop;

if (xMoved || yMoved) {

if (xMoved) {

Log.d(TAG, "onInterceptTouchEvent.ACTION_MOVE.TOUCH_STATE_SCROLLING................");

// 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);

}

// 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 ViewcurrentScreen=getChildAt(mCurrentScreen);

currentScreen.cancelLongPress();

}

}

break;

}

case MotionEvent.ACTION_DOWN: {

Log.d(TAG, "onInterceptTouchEvent.ACTION_DOWN.................");

final floatx=ev.getX();

final floaty=ev.getY();

// Remember location of down touch

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.

*/

Log.d(TAG, "mScroller.isFinished()................." + mScroller.isFinished());

mTouchState=mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;

break;

}

case MotionEvent.ACTION_CANCEL:

case MotionEvent.ACTION_UP:

Log.d(TAG, "onInterceptTouchEvent.ACTION_UP.................");

// Release the drag

//clearChildrenCache();

mTouchState=TOUCH_STATE_REST;

mActivePointerId=INVALID_POINTER;

mAllowLongPress=false;

releaseVelocityTracker();

break;

case MotionEvent.ACTION_POINTER_UP:

Log.d(TAG, "onInterceptTouchEvent.ACTION_POINTER_UP.................");

onSecondaryPointerUp(ev);

break;

}

/*

* The only time we want to intercept motion events is if we are in the

* drag mode.

*/

Log.d(TAG, "onInterceptTouchEvent.mTouchState != TOUCH_STATE_REST................." + (mTouchState != TOUCH_STATE_REST));

return mTouchState != TOUCH_STATE_REST;

}

private void onSecondaryPointerUp(MotionEvent ev) {

final intpointerIndex= (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)>>

MotionEvent.ACTION_POINTER_INDEX_SHIFT;

final intpointerId=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 intnewPointerIndex=pointerIndex== 0 ? 1 : 0;

mLastMotionX=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.

*/

@Override

public void focusableViewAvailable(View focused) {

Viewcurrent=getChildAt(mCurrentScreen);

Viewv=focused;

while (true) {

if (v== current) {

super.focusableViewAvailable(focused);

return;

}

if (v== this) {

return;

}

ViewParentparent=v.getParent();

if (parent instanceof View) {

v= (View)v.getParent();

} else {

return;

}

}

}

@Override

public boolean onTouchEvent(MotionEvent ev) {

acquireVelocityTrackerAndAddMovement(ev);

final intaction=ev.getAction();

switch (action & MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_DOWN:

Log.d(TAG, "onTouchEvent.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();

mActivePointerId=ev.getPointerId(0);

if (mTouchState== TOUCH_STATE_SCROLLING) {

//enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);

}

break;

case MotionEvent.ACTION_MOVE:

Log.d(TAG, "onTouchEvent.ACTION_MOVE.................");

if (mTouchState== TOUCH_STATE_SCROLLING) {

Log.d(TAG, "onTouchEvent.ACTION_MOVE.TOUCH_STATE_SCROLLING................");

// Scroll to follow the motion event

final intpointerIndex=ev.findPointerIndex(mActivePointerId);

final floatx=ev.getX(pointerIndex);

final floatdeltaX=mLastMotionX- x;

mLastMotionX=x;

if (deltaX<0) {

if (mTouchX>0) {

mTouchX += Math.max(-mTouchX, deltaX);

mSmoothingTime=System.nanoTime() / NANOTIME_DIV;

invalidate();

}

} else if (deltaX>0) {

final floatavailableToScroll=getChildAt(getChildCount() - 1).getRight() -

mTouchX - getWidth();

if (availableToScroll>0) {

mTouchX += Math.min(availableToScroll, deltaX);

mSmoothingTime=System.nanoTime() / NANOTIME_DIV;

invalidate();

}

} else {

awakenScrollBars();

}

}

break;

case MotionEvent.ACTION_UP:

Log.d(TAG, "onTouchEvent.ACTION_UP.................");

if (mTouchState== TOUCH_STATE_SCROLLING) {

final VelocityTrackervelocityTracker=mVelocityTracker;

velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

final intvelocityX= (int) velocityTracker.getXVelocity(mActivePointerId);

final intscreenWidth=getWidth();

final intwhichScreen= (getScrollX() + (screenWidth / 2)) / screenWidth;

final floatscrolledPos= (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 intbound=scrolledPos

mCurrentScreen - 1 : mCurrentScreen;

snapToScreen(Math.min(whichScreen, bound), velocityX, true);

} else if (velocityX

// Fling hard enough to move right

// Don't fling across more than one screen at a time.

final intbound=scrolledPos>whichScreen ?

mCurrentScreen + 1 : mCurrentScreen;

snapToScreen(Math.max(whichScreen, bound), velocityX, true);

} else {

snapToScreen(whichScreen, 0, true);

}

}

mTouchState=TOUCH_STATE_REST;

mActivePointerId=INVALID_POINTER;

releaseVelocityTracker();

break;

case MotionEvent.ACTION_CANCEL:

if (mTouchState== TOUCH_STATE_SCROLLING) {

final intscreenWidth=getWidth();

final intwhichScreen= (getScrollX() + (screenWidth / 2)) / screenWidth;

snapToScreen(whichScreen, 0, true);

}

mTouchState=TOUCH_STATE_REST;

mActivePointerId=INVALID_POINTER;

releaseVelocityTracker();

break;

case MotionEvent.ACTION_POINTER_UP:

Log.d(TAG, "onTouchEvent.ACTION_POINTER_UP.................");

onSecondaryPointerUp(ev);

break;

}

return true;

}

private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {

if (mVelocityTracker== null) {

mVelocityTracker= VelocityTracker.obtain();

}

mVelocityTracker.addMovement(ev);

}

private void releaseVelocityTracker() {

if (mVelocityTracker != null) {

mVelocityTracker.recycle();

mVelocityTracker=null;

}

}

void snapToScreen(int whichScreen) {

snapToScreen(whichScreen, 0, false);

}

private void snapToScreen(int whichScreen, int velocity, boolean settle) {

//if (!mScroller.isFinished()) return;

whichScreen=Math.max(0, Math.min(whichScreen, getChildCount() - 1));

//enableChildrenCache(mCurrentScreen, whichScreen);

mNextScreen=whichScreen;

//mPreviousIndicator.setLevel(mNextScreen);

//mNextIndicator.setLevel(mNextScreen);

ViewfocusedChild=getFocusedChild();

if (focusedChild != null && whichScreen != mCurrentScreen &&

focusedChild== getChildAt(mCurrentScreen)) {

focusedChild.clearFocus();

}

final intscreenDelta=Math.max(1, Math.abs(whichScreen - mCurrentScreen));

final intnewX=whichScreen* getWidth();

final intdelta=newX- mScrollX;

intduration= (screenDelta + 1) * 100;

if (!mScroller.isFinished()) {

mScroller.abortAnimation();

}

if (settle) {

mScrollInterpolator.setDistance(screenDelta);

} else {

mScrollInterpolator.disableSettle();

}

velocity=Math.abs(velocity);

if (velocity>0) {

duration += (duration / (velocity / BASELINE_FLING_VELOCITY))

* FLING_VELOCITY_INFLUENCE;

} else {

duration += 100;

}

awakenScrollBars(duration);

mScroller.startScroll(mScrollX, 0, delta, 0, duration);

invalidate();

}

@Override

protected Parcelable onSaveInstanceState() {

final SavedStatestate=newSavedState(super.onSaveInstanceState());

state.currentScreen=mCurrentScreen;

return state;

}

@Override

protected void onRestoreInstanceState(Parcelable state) {

SavedStatesavedState= (SavedState) state;

super.onRestoreInstanceState(savedState.getSuperState());

if (savedState.currentScreen != -1) {

mCurrentScreen=savedState.currentScreen;

}

}

public void scrollLeft() {

Log.d(TAG, "scrollLeft.................");

if (mScroller.isFinished()) {

if (mCurrentScreen>0) snapToScreen(mCurrentScreen - 1);

} else {

if (mNextScreen>0) snapToScreen(mNextScreen - 1);

}

}

public void scrollRight() {

Log.d(TAG, "scrollRight.................");

if (mScroller.isFinished()) {

if (mCurrentScreen

} else {

if (mNextScreen

}

}

public int getScreenForView(View v) {

intresult= -1;

if (v != null) {

ViewParentvvp= v.getParent();

intcount=getChildCount();

for (inti=0; 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;

}

/**

* Set true to allow long-press events to be triggered, usually checked by

* {@link Launcher} to accept or block dpad-initiated long-presses.

*/

public void setAllowLongPress(boolean allowLongPress) {

mAllowLongPress=allowLongPress;

}

void moveToDefaultScreen(boolean animate) {

if (animate) {

snapToScreen(mDefaultScreen);

} else {

setCurrentScreen(mDefaultScreen);

}

getChildAt(mDefaultScreen).requestFocus();

}

//    void setIndicators(Drawable previous, Drawable next) {

//mPreviousIndicator=previous;

//mNextIndicator=next;

//        previous.setLevel(mCurrentScreen);

//        next.setLevel(mCurrentScreen);

//    }

public static class SavedState extends BaseSavedState {

intcurrentScreen= -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.CreatorCREATOR=

newParcelable.Creator() {

public SavedState createFromParcel(Parcel in) {

return new SavedState(in);

}

public SavedState[] newArray(int size) {

return new SavedState[size];

}

};

}

}package com.mylauncher;

import android.app.WallpaperManager;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Rect;

import android.graphics.drawable.Drawable;

import android.os.IBinder;

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.Interpolator;

import android.widget.Scroller;

/**

* The workspace is a wide area with a wallpaper and a finite number of screens. Each

* screen contains a number of icons, folders or widgets the user can interact with.

* A workspace is meant to be used with a fixed width only.

*/

public class Workspace extends ViewGroup {

@SuppressWarnings({"UnusedDeclaration"})

private static final String TAG = "MyLauncher.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 = 600;

private final WallpaperManager mWallpaperManager;

private int mDefaultScreen;

private boolean mFirstLayout = true;

private int mCurrentScreen;

private int mNextScreen = INVALID_SCREEN;

private Scroller mScroller;

private VelocityTracker mVelocityTracker;

/**

* Target drop area calculated during last acceptDrop call.

*/

private int[] mTargetCell = null;

private float mLastMotionX;

private float mLastMotionY;

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 int[] mTempCell = new int[2];

private int[] mTempEstimate = new int[2];

private boolean mAllowLongPress = true;

private int mTouchSlop;

private int mMaximumVelocity;

private static final int INVALID_POINTER = -1;

private int mActivePointerId = INVALID_POINTER;

private Drawable mPreviousIndicator;

private Drawable mNextIndicator;

private static final float NANOTIME_DIV = 1000000000.0f;

private static final float SMOOTHING_SPEED = 0.75f;

private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));

private float mSmoothingTime;

private float mTouchX;

private WorkspaceOvershootInterpolator mScrollInterpolator;

private static final float BASELINE_FLING_VELOCITY = 2500.f;

private static final float FLING_VELOCITY_INFLUENCE = 0.4f;

private static class WorkspaceOvershootInterpolator implements Interpolator {

private static final float DEFAULT_TENSION = 1.3f;

private float mTension;

public WorkspaceOvershootInterpolator() {

mTension = DEFAULT_TENSION;

}

public void setDistance(int distance) {

mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;

}

public void disableSettle() {

mTension = 0.f;

}

public float getInterpolation(float t) {

// _o(t) = t * t * ((tension + 1) * t + tension)

// o(t) = _o(t - 1) + 1

t -= 1.0f;

return t * t * ((mTension + 1) * t + mTension) + 1.0f;

}

}

/**

* Used to inflate the Workspace from XML.

*

* @param context The application's context.

* @param attrs The attribtues set containing the Workspace's customization values.

*/

public Workspace(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

/**

* Used to inflate the Workspace from XML.

*

* @param context The application's context.

* @param attrs The attribtues set containing the Workspace's customization values.

* @param defStyle Unused.

*/

public Workspace(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

mWallpaperManager = WallpaperManager.getInstance(context);

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);

mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);

a.recycle();

setHapticFeedbackEnabled(false);

initWorkspace();

}

/**

* Initializes various states for this workspace.

*/

private void initWorkspace() {

Context context = getContext();

mScrollInterpolator = new WorkspaceOvershootInterpolator();

mScroller = new Scroller(context, mScrollInterpolator);

mCurrentScreen = mDefaultScreen;

final ViewConfiguration configuration = ViewConfiguration.get(getContext());

mTouchSlop = configuration.getScaledTouchSlop();

mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

}

boolean isDefaultScreenShowing() {

return mCurrentScreen == mDefaultScreen;

}

/**

* Returns the index of the currently displayed screen.

*

* @return The index of the currently displayed screen.

*/

int getCurrentScreen() {

return mCurrentScreen;

}

/**

* Sets the current screen.

*

* @param currentScreen

*/

void setCurrentScreen(int currentScreen) {

if (!mScroller.isFinished()) mScroller.abortAnimation();

mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));

//mPreviousIndicator.setLevel(mCurrentScreen);

//mNextIndicator.setLevel(mCurrentScreen);

scrollTo(mCurrentScreen * getWidth(), 0);

invalidate();

}

/**

* 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 = getChildCount();

for (int i = 0; i < count; i++) {

getChildAt(i).setOnLongClickListener(l);

}

}

private void updateWallpaperOffset(int scrollRange) {

IBinder token = getWindowToken();

if (token != null) {

mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );

mWallpaperManager.setWallpaperOffsets(getWindowToken(),

Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);

}

}

@Override

public void scrollTo(int x, int y) {

super.scrollTo(x, y);

mTouchX = x;

mSmoothingTime = System.nanoTime() / NANOTIME_DIV;

}

@Override

public void computeScroll() {

if (mScroller.computeScrollOffset()) {

mTouchX = mScrollX = mScroller.getCurrX();

mSmoothingTime = System.nanoTime() / NANOTIME_DIV;

mScrollY = mScroller.getCurrY();

postInvalidate();

} else if (mNextScreen != INVALID_SCREEN) {

mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));

//mPreviousIndicator.setLevel(mCurrentScreen);

//mNextIndicator.setLevel(mCurrentScreen);

mNextScreen = INVALID_SCREEN;

//clearChildrenCache();

} 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 - mScrollX;

mScrollX += dx * e;

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();

}

}

}

@Override

protected void dispatchDraw(Canvas canvas) {

boolean restore = false;

int restoreCount = 0;

// ViewGroup.dispatchDraw() supports many features we don't need:

// clip to padding, layout animation, animation listener, disappearing

// children, etc. The following implementation attempts to fast-track

// the drawing dispatch by drawing only what we know needs to be drawn.

boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;

// If we are not scrolling or flinging, draw only the current screen

if (fastDraw) {

drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());

} else {

final long drawingTime = getDrawingTime();

final float scrollPos = (float) mScrollX / getWidth();

final int leftScreen = (int) scrollPos;

final int rightScreen = leftScreen + 1;

if (leftScreen >= 0) {

drawChild(canvas, getChildAt(leftScreen), drawingTime);

}

if (scrollPos != leftScreen && rightScreen < getChildCount()) {

drawChild(canvas, getChildAt(rightScreen), drawingTime);

}

}

if (restore) {

canvas.restoreToCount(restoreCount);

}

}

protected void onAttachedToWindow() {

super.onAttachedToWindow();

computeScroll();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

final int width = MeasureSpec.getSize(widthMeasureSpec);

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

if (widthMode != MeasureSpec.EXACTLY) {

throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");

}

final int heightMode = MeasureSpec.getMode(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 count = getChildCount();

for (int i = 0; i < count; i++) {

getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);

}

if (mFirstLayout) {

setHorizontalScrollBarEnabled(false);

scrollTo(mCurrentScreen * width, 0);

setHorizontalScrollBarEnabled(true);

updateWallpaperOffset(width * (getChildCount() - 1));

mFirstLayout = false;

}

}

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

int childLeft = 0;

final int count = getChildCount();

for (int i = 0; i < count; 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;

}

}

}

@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

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() < getChildCount() - 1) {

snapToScreen(getCurrentScreen() + 1);

return true;

}

}

return super.dispatchUnhandledMove(focused, direction);

}

@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();

Log.d(TAG, "onInterceptTouchEvent, action: " + action + " mTouchState: " + mTouchState);

if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {

Log.d(TAG, "onInterceptTouchEvent, MotionEvent.ACTION_MOVE mTouchState: " + mTouchState);

return true;

}

acquireVelocityTrackerAndAddMovement(ev);

switch (action & MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_MOVE: {

Log.d(TAG, "onInterceptTouchEvent.ACTION_MOVE.................");

/*

* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check

* whether the user has moved far enough from his original down touch.

*/

/*

* 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 xMoved = xDiff > touchSlop;

boolean yMoved = yDiff > touchSlop;

if (xMoved || yMoved) {

if (xMoved) {

Log.d(TAG, "onInterceptTouchEvent.ACTION_MOVE.TOUCH_STATE_SCROLLING................");

// 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);

}

// 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 = getChildAt(mCurrentScreen);

currentScreen.cancelLongPress();

}

}

break;

}

case MotionEvent.ACTION_DOWN: {

Log.d(TAG, "onInterceptTouchEvent.ACTION_DOWN.................");

final float x = ev.getX();

final float y = ev.getY();

// Remember location of down touch

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.

*/

Log.d(TAG, "mScroller.isFinished()................." + mScroller.isFinished());

mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;

break;

}

case MotionEvent.ACTION_CANCEL:

case MotionEvent.ACTION_UP:

Log.d(TAG, "onInterceptTouchEvent.ACTION_UP.................");

// Release the drag

//clearChildrenCache();

mTouchState = TOUCH_STATE_REST;

mActivePointerId = INVALID_POINTER;

mAllowLongPress = false;

releaseVelocityTracker();

break;

case MotionEvent.ACTION_POINTER_UP:

Log.d(TAG, "onInterceptTouchEvent.ACTION_POINTER_UP.................");

onSecondaryPointerUp(ev);

break;

}

/*

* The only time we want to intercept motion events is if we are in the

* drag mode.

*/

Log.d(TAG, "onInterceptTouchEvent.mTouchState != TOUCH_STATE_REST................." + (mTouchState != TOUCH_STATE_REST));

return mTouchState != TOUCH_STATE_REST;

}

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);

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.

*/

@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;

}

}

}

@Override

public boolean onTouchEvent(MotionEvent ev) {

acquireVelocityTrackerAndAddMovement(ev);

final int action = ev.getAction();

switch (action & MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_DOWN:

Log.d(TAG, "onTouchEvent.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();

mActivePointerId = ev.getPointerId(0);

if (mTouchState == TOUCH_STATE_SCROLLING) {

//enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);

}

break;

case MotionEvent.ACTION_MOVE:

Log.d(TAG, "onTouchEvent.ACTION_MOVE.................");

if (mTouchState == TOUCH_STATE_SCROLLING) {

Log.d(TAG, "onTouchEvent.ACTION_MOVE.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;

mLastMotionX = x;

if (deltaX < 0) {

if (mTouchX > 0) {

mTouchX += Math.max(-mTouchX, deltaX);

mSmoothingTime = System.nanoTime() / NANOTIME_DIV;

invalidate();

}

} else if (deltaX > 0) {

final float availableToScroll = getChildAt(getChildCount() - 1).getRight() -

mTouchX - getWidth();

if (availableToScroll > 0) {

mTouchX += Math.min(availableToScroll, deltaX);

mSmoothingTime = System.nanoTime() / NANOTIME_DIV;

invalidate();

}

} else {

awakenScrollBars();

}

}

break;

case MotionEvent.ACTION_UP:

Log.d(TAG, "onTouchEvent.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 < getChildCount() - 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);

}

}

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();

break;

case MotionEvent.ACTION_POINTER_UP:

Log.d(TAG, "onTouchEvent.ACTION_POINTER_UP.................");

onSecondaryPointerUp(ev);

break;

}

return true;

}

private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {

if (mVelocityTracker == null) {

mVelocityTracker = VelocityTracker.obtain();

}

mVelocityTracker.addMovement(ev);

}

private void releaseVelocityTracker() {

if (mVelocityTracker != null) {

mVelocityTracker.recycle();

mVelocityTracker = null;

}

}

void snapToScreen(int whichScreen) {

snapToScreen(whichScreen, 0, false);

}

private void snapToScreen(int whichScreen, int velocity, boolean settle) {

//if (!mScroller.isFinished()) return;

whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));

//enableChildrenCache(mCurrentScreen, whichScreen);

mNextScreen = whichScreen;

//mPreviousIndicator.setLevel(mNextScreen);

//mNextIndicator.setLevel(mNextScreen);

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 - mScrollX;

int duration = (screenDelta + 1) * 100;

if (!mScroller.isFinished()) {

mScroller.abortAnimation();

}

if (settle) {

mScrollInterpolator.setDistance(screenDelta);

} else {

mScrollInterpolator.disableSettle();

}

velocity = Math.abs(velocity);

if (velocity > 0) {

duration += (duration / (velocity / BASELINE_FLING_VELOCITY))

* FLING_VELOCITY_INFLUENCE;

} else {

duration += 100;

}

awakenScrollBars(duration);

mScroller.startScroll(mScrollX, 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() {

Log.d(TAG, "scrollLeft.................");

if (mScroller.isFinished()) {

if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);

} else {

if (mNextScreen > 0) snapToScreen(mNextScreen - 1);

}

}

public void scrollRight() {

Log.d(TAG, "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;

}

/**

* Set true to allow long-press events to be triggered, usually checked by

* {@link Launcher} to accept or block dpad-initiated long-presses.

*/

public void setAllowLongPress(boolean allowLongPress) {

mAllowLongPress = allowLongPress;

}

void moveToDefaultScreen(boolean animate) {

if (animate) {

snapToScreen(mDefaultScreen);

} else {

setCurrentScreen(mDefaultScreen);

}

getChildAt(mDefaultScreen).requestFocus();

}

// void setIndicators(Drawable previous, Drawable next) {

// mPreviousIndicator = previous;

// mNextIndicator = next;

// previous.setLevel(mCurrentScreen);

// next.setLevel(mCurrentScreen);

// }

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 CREATOR =

new Parcelable.Creator() {

public SavedState createFromParcel(Parcel in) {

return new SavedState(in);

}

public SavedState[] newArray(int size) {

return new SavedState[size];

}

};

}

}

package com.mylauncher;

import android.content.Context;

import android.util.AttributeSet;

import android.view.View;

import android.widget.LinearLayout;

import android.view.MotionEvent;

import android.util.Log;

public class CellLayout extends LinearLayout {

private static final StringTAG="MyLauncher.CellLayout";

public CellLayout(Context context, AttributeSet attrs) {

super(context, attrs);

// TODO Auto-generated constructor stub

}

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

Log.d(TAG, "onInterceptTouchEvent,action=" + ev.getAction());

return true;

}

@Override

public boolean onTouchEvent(MotionEvent ev) {

Log.d(TAG, "onTouchEvent,action=" + ev.getAction());

return true;

}

}0b1331709591d260c1c78e86d0c51c18.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值