View滑动体系

1 View滑动主要类

(1)MotionEvent


当用户触摸屏幕时,将创建一个MontionEvent对象。MotionEvent包含了关于发生触摸的位置和时间的信息,以及触摸事件的其他细节


获取MontionEvent对象的方法有:

1.重载Activity中的onTouchEvent(MotionEvent event)方法;
2.View对象调用View.setOnTouchListener接口实现onTouch(View v, MotionEvent event)方法;


获得MontionEvent对象后,可以通过以下常用方法进一步获取触控事件的具体信息:
event.getAction() //获取触控动作比如ACTION_DOWN

event.getRawx(),event.getRawY(),event.getX(),event.getY()


事件类型

  /**
     * 手指按下时
     */
    public static final int ACTION_DOWN = 0;

 /**
     * 移动操作时
     */
    public static final int ACTION_MOVE = 2;

  /**
     * 手指放开时
     */
    public static final int ACTION_UP = 1;



(2)ViewConfiguration

包含了方法和标准的常量用来设置UI的超时、大小和距离 


其中TouchSlop参数是系统能识别的最小滑动距离,在ViewConfiguration有定义。通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取TouchSlop

public static ViewConfiguration get(Context context) {
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        final int density = (int) (100.0f * metrics.density);

        ViewConfiguration configuration = sConfigurations.get(density);
        if (configuration == null) {
            configuration = new ViewConfiguration(context);
            sConfigurations.put(density, configuration);
        }

        return configuration;
    }
static final SparseArray<ViewConfiguration> sConfigurations =
            new SparseArray<ViewConfiguration>(2);
 public int getScaledTouchSlop() {
        return mTouchSlop;
    }
ViewConfiguration 实例存储在sConfiguration(SparseArray)中。
 private ViewConfiguration(Context context) {
        .....
        mTouchSlop = res.getDimensionPixelSize(
                com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
       .....
    }
    <dimen name="config_viewConfigurationTouchSlop">8dp</dimen>
可以看出最小滑动距离是8dp.


(3)VelocityTracker
速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向。


首先,在View的onTouchEvent(MotionEvent  event)方法中追踪当前点击事件的速度

VelocityTracker verTracker = VelocityTracker.obtain();

verTracker .addMovement(event);

static public VelocityTracker obtain() {
        VelocityTracker instance = sPool.acquire();
        return (instance != null) ? instance : new VelocityTracker(null);
    }
这里使用了同步资源池

private static final SynchronizedPool<VelocityTracker> sPool =
            new SynchronizedPool<VelocityTracker>(2);
     

 (3.1)SynchronizedPool(Pools内部类)

 

 public static class SynchronizedPool<T> extends SimplePool<T> {
        private final Object mLock = new Object();

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SynchronizedPool(int maxPoolSize) {
            super(maxPoolSize);
        }

        @Override
        public T acquire() {
            synchronized (mLock) {
                return super.acquire();
            }
        }

        @Override
        public boolean release(T element) {
            synchronized (mLock) {
                return super.release(element);
            }
        }
    }
这里就是用数组保存了资源,使用acquire和release来完成资源的管理

public static class SimplePool<T> implements Pool<T> {
        private final Object[] mPool;

        private int mPoolSize;

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SimplePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            mPool = new Object[maxPoolSize];
        }

        @Override
        @SuppressWarnings("unchecked")
        public T acquire() {
            if (mPoolSize > 0) {
                final int lastPooledIndex = mPoolSize - 1;
                T instance = (T) mPool[lastPooledIndex];
                mPool[lastPooledIndex] = null;
                mPoolSize--;
                return instance;
            }
            return null;
        }

        @Override
        public boolean release(T instance) {
            if (isInPool(instance)) {
                throw new IllegalStateException("Already in the pool!");
            }
            if (mPoolSize < mPool.length) {
                mPool[mPoolSize] = instance;
                mPoolSize++;
                return true;
            }
            return false;
        }

        private boolean isInPool(T instance) {
            for (int i = 0; i < mPoolSize; i++) {
                if (mPool[i] == instance) {
                    return true;
                }
            }
            return false;
        }
    }


回到VelocityTracker中,我们获取了VelocityTracker实例,接着追踪事件,再来看看addMovement(event)

public void addMovement(MotionEvent event) {
        if (event == null) {
            throw new IllegalArgumentException("event must not be null");
        }
        nativeAddMovement(mPtr, event);
    }
已经到Native层了

接着,我们想知道当前的滑动速度,在获取速度之前,必须先计算速度computeCurrentVelocity

verTracker .computeCurrentVelocity(1000);

现在我们可以获取速度了

int xVelocity=(int)verTracker .getXVelocity();
int yVelocity=(int)verTracker .geYVelocity();

public void computeCurrentVelocity(int units) {
        nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
    }

最后,我们要要清除回收内存

verTracker .clear();

verTracker .recycle();

    public void clear() {
        nativeClear(mPtr);
    }
 public void recycle() {
        if (mStrategy == null) {
            clear();
            sPool.release(this);
        }
    }


(4)GestureDetector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为

public class ThirdActivity extends Activity implements View.OnClickListener,GestureDetector.OnDoubleTapListener,GestureDetector.OnGestureListener {
    private GestureDetector mGesture;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
        mGesture=new GestureDetector(this);
     mGesture.setIsLongpressEnabled(true);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean consume=mGesture.onTouchEvent(event);
        return consume;
    }
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        return false;
    }
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }
}

(5)Scroller

见3 弹性滑动


2 View滑动的类型

(一)通过View本身的scrollTo / scrollBy

使用方法举例:mButton.scrollTo(0,50);

(1)View

先看View中scrollTo 的源码和scrollBy的源码

public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }
scrollBy也是调用了scrollTo方法。scrollTo中有两个重要参数,mScrollX和mScrollY,可以分别由view.getScrollX(),  view.getScrollY()获取到

mScrollX的值 = (View的左边缘) - (View内容的左边缘),mScrollY  =  (View的上边缘)  -(View内容上边缘),单位为像素。

(二)使用动画


(1)View动画

View动画是对View的影像在操作,并不能改变View的位置参数,包括宽/高。 所以单击动画的影像,无法触发点击事件,单击原来的位置,却可以。

通过java代码:

scale = (TextView) findViewById(R.id.scale);

ScaleAnimation sAnima = new ScaleAnimation(0, 5, 0, 5);

sAnima.setDuration(2000);

scale.startAnimation(sAnima); 


通过XML

<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:shareInterpolator="false">  
    <scale  
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"  
        android:fromXScale="1.0"  
        android:toXScale="1.4"  
        android:fromYScale="1.0"  
        android:toYScale="0.6"  
        android:pivotX="50%"  
        android:pivotY="50%"  
        android:fillAfter="false"  
        android:duration="1000" />  
    <set  
        android:interpolator="@android:anim/accelerate_interpolator"  
        android:startOffset="1000">  
        <scale  
            android:fromXScale="1.4"  
            android:toXScale="0.0"  
            android:fromYScale="0.6"  
            android:toYScale="0.0"  
            android:pivotX="50%"  
            android:pivotY="50%"  
            android:duration="400" />  
        <rotate  
            android:fromDegrees="0"  
            android:toDegrees="60"  
            android:toYScale="0.0"  
            android:pivotX="50%"  
            android:pivotY="50%"  
            android:duration="400" />  
    </set>  
</set>  
 img = (ImageView)findViewById(R.id.xmlAnimImg);

Animation anim = AnimationUtils.loadAnimation(AnimaXmlActivity.this, R.anim.myanim);  
  img.startAnimation(anim);

(2)属性动画

属性动画可以解决动画属性点击无效的问题

ObjectAnimator
         .ofFloat(view, "rotationX", 0.0F, 360.0F)
         .setDuration(500)
         .start();


(三)改变布局参数

通过改变布局参数让看起来在动

jump = (Button) findViewById(R.id.jump);
        LinearLayout.LayoutParams param=(LinearLayout.LayoutParams)jump.getLayoutParams();
        param.weight=80;
        param.height=80;
        jump.setLayoutParams(param);


(四)总结

1.scrollTo/scrollBy只能滑动内容,不能滑动View本身

2.View动画因为改变的是影像,所以用于不需要和用户交互的控件

3.布局适用于具有交互性的View,


3 弹性滑动

(一)使用Scroller

public class HorizontalScrollViewEx extends ViewGroup {
    ......
    public HorizontalScrollViewEx(Context context) {
        super(context);
        init();
    }

    public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public HorizontalScrollViewEx(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mScroller = new Scroller(getContext());
        mVelocityTracker = VelocityTracker.obtain();
    }
    ......
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            scrollBy(-deltaX, 0);
            break;
        }
        case MotionEvent.ACTION_UP: {
            int scrollX = getScrollX();
            int scrollToChildIndex = scrollX / mChildWidth;
            mVelocityTracker.computeCurrentVelocity(1000);
            float xVelocity = mVelocityTracker.getXVelocity();
            if (Math.abs(xVelocity) >= 50) {
                mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
            } else {
                mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
            }
            mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
            int dx = mChildIndex * mChildWidth - scrollX;
            smoothScrollBy(dx, 0);
            mVelocityTracker.clear();
            break;
        }
        default:
            break;
        }

        mLastX = x;
        mLastY = y;
        return true;
    }
    .......
    private void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mVelocityTracker.recycle();
        super.onDetachedFromWindow();
    }
}
这里我们调用了mScroller.startScroll(getScrollX(), 0, dx, 0, 500),来看看startScroll的源码

(1)Scroller

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;
        mFinished = false;
        mDuration = duration;
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        mDeltaX = dx;
        mDeltaY = dy;
        mDurationReciprocal = 1.0f / (float) mDuration;
    }
所以仅仅调用startScroll是无法滑动的,再来看看invalidate,invalidate会导致View重绘,在View的draw方法中又会去调用computeScroll方法,再来看看我们对computeScroll方法的复写


(2)View

@Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }
重点在computeScrollOffset之中


(3)Scroller

public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                float x = timePassed * mDurationReciprocal;
    
                if (mInterpolator == null)
                    x = viscousFluid(x); 
                else
                    x = mInterpolator.getInterpolation(x);
    
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
             ......
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }
computeScrollOffset这个方法根据时间流逝的百分比算mScrollX和mScrollY,然后我们获取到mScrollX和mScrollY并进行重绘postInvalidate

 if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }

再来看看postInvalidate,postInvalidate最终还是调用view的invalidate进行重绘,所以这里就变成了一个递归,直到(mScroller.computeScrollOffset()返回false的时候结束


(二)通过属性动画

ValueAnimator animator=ValueAnimator.ofInt(0,1).setDuration(1000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction=animation.getAnimatedFraction();
                jump.scrollTo((int)(2*fraction),0);
            }
        });
通过AnimatorUpdateListener监听动画的每一帧到来时间来获取动画完成的比例,在根据动画完成的比例计算当前View要滑动的距离

(三) 延时策略

@Override
    public void onClick(View v) {
        if (v == mButton1) {
            mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO, DELAYED_TIME);
        }
    }
private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MESSAGE_SCROLL_TO: {
                mCount++;
                if (mCount <= FRAME_COUNT) {
                    float fraction = mCount / (float) FRAME_COUNT;
                    int scrollX = (int) (fraction * 100);
                    mButton1.scrollTo(scrollX, 0);
                    mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO, DELAYED_TIME);
                }
                break;
            }
            default:
                break;
            }
        };
    };
通过延时发送消息,自己造一个百分比出来,然后通过scrollTo完成弹性滑动。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值