最近写应用,多次用到里多屏滑动,参考了下一些资料,写里一个类,可以通用。目前用起来没什么问题,先存下来。
package com.android.systemui.recent.gk;
import android.content.Context;
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.animation.Interpolator;
import android.widget.Scroller;
import android.widget.TextView;
public class ScreenView extends ViewGroup {
protected int mVisibleRange = 1;
private static final int MAX_VISIBLE_RANGE = 4;
private static final int TOUCH_STATE_RESET = 0;
private static final int TOUCH_STATE_SCROLLING = 1;
private static final int INVALIDATE_SCREEN = -1;
private int mTouchState = TOUCH_STATE_RESET;
private float mTouchDownX;
private float mTouchDownY;
private float mLastMotionX;
private float mTouchSlop;
private int mCurrentScreen;
private int mNextScreen;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
private static final int OVER_SCROLL_BOUNDS = 0;
private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.3f;
private static final float RETURN_TO_ORIGINAL_SCREEN_THRESHOLD = 0.33f;
private static final int MIN_LENGTH_FOR_FLING = 25;
private static final int FLING_THRESHOLD_VELOCITY = 200;
private static final int MIN_SNAP_VELOCITY_BASE = 200;
private static final int SCREEN_SNAP_DURATION = 200;
private OvershootInterpolator mScrollInterpolator;
private int mFlingThresholdVelocity;
private int mMinSnapVelocity;
private int mMaximumVelocity;
private final float mBaseLineFlingVelocity = 2500.0f;
private final float mFlingVelocityInfluence = 0.4f;
private boolean mTouchMoveHorizion = false;
private double mTouchMoveOrientationSlop;
private int mScreenCount = 0;
private int mScreenWidth;
private int mMaxChildWidth;
private static final float mOverScrollRatio = 0.3f;
private static final String TAG = "ScreenView jalen";
private int mOverScrollBounds = 0;
public ScreenView(Context context) {
super(context);
init(context);
}
public ScreenView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ScreenView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
}
private void init(Context context){
ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
mMaximumVelocity = config.getScaledMaximumFlingVelocity();
mMinSnapVelocity = MIN_SNAP_VELOCITY_BASE * 2;
mFlingThresholdVelocity = FLING_THRESHOLD_VELOCITY * 2;
mScrollInterpolator = new OvershootInterpolator();
mScroller = new Scroller(context, mScrollInterpolator);
mTouchMoveOrientationSlop = getTouchMoveOrientationSlop();
}
private double getTouchMoveOrientationSlop(){
return Math.tan(45 * Math.PI / 180);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
int childCount = getChildCount();
int childGap;
if(childCount < mVisibleRange){
childGap = (mScreenWidth - childCount * mMaxChildWidth) / (childCount + 1);
}else{
childGap = (mScreenWidth - mVisibleRange * mMaxChildWidth) / (mVisibleRange + 1);
}
int left = getPaddingLeft() + childGap;
int top = getPaddingTop();
boolean isNewScreen;
for(int i=0; i<childCount; i++){
View v = getChildAt(i);
if(v.getVisibility() == GONE) continue;
v.layout(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
isNewScreen = ((i + 1) % mVisibleRange == 0);
if(isNewScreen){
int screenIndex = (i + mVisibleRange - 1) / mVisibleRange;
left = screenIndex * getWidth() + getPaddingLeft() + childGap;
}else{
left += v.getMeasuredWidth() + childGap;
}
}
Log.i(TAG,"onLayout childCount="+childCount + ", childGap="+childGap);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int childCount = getChildCount();
for(int i=0; i<childCount; i++){
View v = getChildAt(i);
measureChild(v, widthMeasureSpec, heightMeasureSpec);
mMaxChildWidth = Math.max(mMaxChildWidth, v.getMeasuredWidth());
mScreenWidth = View.MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
mVisibleRange = Math.min(MAX_VISIBLE_RANGE, Math.max(1, mScreenWidth / mMaxChildWidth));
}
Log.i(TAG,"onMeasure childCount="+childCount + ", mMaxChildWidth="+mMaxChildWidth
+", mScreenWidth="+mScreenWidth
+", mVisibleRange="+mVisibleRange);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void addView(View child, int index, LayoutParams params) {
// TODO Auto-generated method stub
super.addView(child, index, params);
mScreenCount += 1;
}
@Override
public void removeAllViews() {
// TODO Auto-generated method stub
super.removeAllViews();
mScreenCount = 0;
}
@Override
public void removeViewsInLayout(int start, int count) {
// TODO Auto-generated method stub
super.removeViewsInLayout(start, count);
mScreenCount -= 1;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
aquireVelocityTrackerAndAddMotion(ev);
switch(ev.getActionMasked()){
case MotionEvent.ACTION_DOWN:
mLastMotionX = mTouchDownX = ev.getX();
mTouchDownY = ev.getY();
mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESET : TOUCH_STATE_SCROLLING;
mTouchMoveHorizion = (mTouchState == TOUCH_STATE_SCROLLING);
break;
case MotionEvent.ACTION_MOVE:
if(mTouchState != TOUCH_STATE_SCROLLING){
float xDiff = Math.abs(ev.getX() - mTouchDownX);
float yDiff = Math.abs(ev.getY() - mTouchDownY);
if(xDiff > mTouchSlop){
if(yDiff == 0 || (xDiff / yDiff > mTouchMoveOrientationSlop)){
mTouchMoveHorizion = true;
}
if(mTouchMoveHorizion){
mTouchState = TOUCH_STATE_SCROLLING;
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
releaseVelocityTracker();
mTouchState = TOUCH_STATE_RESET;
break;
}
Log.i(TAG, " onInterceptTouchEvent "+ev.getActionMasked()+" return="+(mTouchState == TOUCH_STATE_SCROLLING));
return mTouchState == TOUCH_STATE_SCROLLING;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
aquireVelocityTrackerAndAddMotion(event);
switch(event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
mLastMotionX = mTouchDownX = event.getX();
mTouchDownY = event.getY();
mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESET : TOUCH_STATE_SCROLLING;
mTouchMoveHorizion = (mTouchState == TOUCH_STATE_SCROLLING);
break;
case MotionEvent.ACTION_MOVE:
if(mTouchState == TOUCH_STATE_SCROLLING){
float deltaX = mLastMotionX - event.getX();
mLastMotionX = event.getX();
float nextScrollX = getScrollX() + deltaX;
if(nextScrollX < (getScreenCount() - 1) * getWidth() + OVER_SCROLL_BOUNDS
&& nextScrollX > -OVER_SCROLL_BOUNDS){
scrollBy((int)deltaX, 0);
}
}else{
float xDiff = Math.abs(event.getX() - mTouchDownX);
float yDiff = Math.abs(event.getY() - mTouchDownY);
if(xDiff > mTouchSlop){
if(yDiff == 0 || (xDiff / yDiff > mTouchMoveOrientationSlop)){
mTouchMoveHorizion = true;
}
if(mTouchMoveHorizion){
mTouchState = TOUCH_STATE_SCROLLING;
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if(mTouchState == TOUCH_STATE_SCROLLING){
final int screenWidth = getWidth();
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final int velocityX = (int) mVelocityTracker.getXVelocity();
int delataX = (int)(event.getX() - mTouchDownX);
boolean isSignificationMove = Math.abs(delataX) > screenWidth * SIGNIFICANT_MOVE_THRESHOLD;
boolean isFling = (Math.abs(velocityX) > mFlingThresholdVelocity)
&& (Math.abs(delataX) > MIN_LENGTH_FOR_FLING);
boolean returnToOriginalScreen = (Math.signum(velocityX) != Math.signum(delataX))
&& (Math.abs(delataX) > screenWidth * RETURN_TO_ORIGINAL_SCREEN_THRESHOLD)
&& isFling;
int finalScreen;
if(((isSignificationMove && delataX > 0 && !isFling) || (isFling && velocityX > 0))
&& mCurrentScreen > 0){
finalScreen = returnToOriginalScreen ? mCurrentScreen : mCurrentScreen - 1;
snapToScreen(finalScreen, velocityX);
}else if(((isSignificationMove && delataX < 0 && ! isFling) || (isFling && velocityX < 0))
&& mCurrentScreen < getScreenCount() - 1){
finalScreen = returnToOriginalScreen ? mCurrentScreen : mCurrentScreen + 1;
snapToScreen(finalScreen, velocityX);
}else{
finalScreen = (getScrollX() + screenWidth / 2) / screenWidth;
snapToScreen(finalScreen, velocityX);
}
mTouchState = TOUCH_STATE_RESET;
}
releaseVelocityTracker();
break;
}
return true;
}
@Override
public void computeScroll() {
// TODO Auto-generated method stub
//super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}else if(mNextScreen != INVALIDATE_SCREEN){
mCurrentScreen = Math.max(0, Math.min(mNextScreen, getScreenCount() - 1));
scrollTo(mCurrentScreen * getWidth(), 0);
mNextScreen = INVALIDATE_SCREEN;
}
}
private void snapToScreen(int whichScreen, int velocity){
whichScreen = Math.max(0, Math.min(whichScreen, getScreenCount() - 1));
int screenWidth = getWidth();
final int screenDelta = Math.abs(mCurrentScreen - whichScreen);
final int newX = whichScreen * screenWidth;
final int deltaX = newX - getScrollX();
int duration = 0;
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
velocity = Math.max(mMinSnapVelocity, Math.abs(velocity));
int tmpDuration = Math.abs(deltaX) * SCREEN_SNAP_DURATION / screenWidth;
if(velocity > 0){
tmpDuration += tmpDuration / (velocity / mBaseLineFlingVelocity) * mFlingVelocityInfluence;
}
duration = Math.max(SCREEN_SNAP_DURATION, tmpDuration);
if(screenDelta <= 1) duration = Math.min(duration, SCREEN_SNAP_DURATION * 2);
boolean changeScreen = whichScreen != mCurrentScreen;
View focusedView = getFocusedChild();
if(changeScreen && focusedView != null && focusedView == getChildAt(mCurrentScreen)){
clearChildFocus(focusedView);
}
Log.i(TAG,"snapToScreen "
+", screenDelta="+screenDelta
+", newX="+newX
+", deltaX="+deltaX
+", whichScreen="+whichScreen
+", velocity="+velocity
+", duration="+duration);
mNextScreen = whichScreen;
mScroller.startScroll(getScrollX(), 0, deltaX, 0, duration);
invalidate();
}
public void snapToScreen(int screenId){
if(screenId < 0 || screenId > getScreenCount() - 1){
return;
}
snapToScreen(screenId, 0);
}
private void aquireVelocityTrackerAndAddMotion(MotionEvent ev){
if(mVelocityTracker == null){
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
}
private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
public int getScreenCount(){
//return mScreenCount;
return (int)((getChildCount() + (mVisibleRange - 1)) / mVisibleRange);
}
public static class OvershootInterpolator implements Interpolator {
private static final float DEFAULT_TENSION = 1.3f;
private float mTension;
public OvershootInterpolator() {
mTension = 0.0f;//DEFAULT_TENSION;
}
public void setDistance(int distance) {
mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
}
public void setTension(float tension){
mTension = 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;
}
}
}