欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

手写 ViewPager,以及 Android 中的拖拽操作

程序员文章站 2022-06-26 17:59:20
14_手写 ViewPager,以及 Android 中的拖拽操作ViewGroup 的触摸反馈、ViewPagerViewGroup的触摸反馈VelocityTrackerscrollTo / scrollBy 和 computeScroll()ViewGroup 的触摸反馈、ViewPagerViewGroup的触摸反馈除了重写 onTouchEvent(),还需要重写 onInterceptTouchEvent()onInterceptTouchEvent()中,ACTION_DOWN...

ViewGroup 的触摸反馈、ViewPager

ViewGroup的触摸反馈

  • 除了重写 onTouchEvent(),还需要重写 onInterceptTouchEvent()

  • onInterceptTouchEvent()中,ACTION_DOWN 事件做的事和 onTouchEvent()基本一致或完全一致

    • 原因:ACTION_DOWN在多数手势中起到的是起始记录的作用(例如记录手指落点),而 onInterceptTouchEvent()调用后,onTouchEvent()未必会被调用,因此需要把这个记录责任转交给 onInterceptTouchEvent()。
    • 有时ACTION_DOWN事件也会在经过onInterceptTouchEvent()之后再转交给自己的 onTouchEvent()(例如当没有触摸到子View或者触摸到的子View没有消费事件时)。因此需要确认在onInterceptTouchEvent()和onTouchEvent()都被调用时,程序状态不会出问题。
  • onInterceptTouchEvent()中,ACTION_MOVE 一般的作用是确认滑动,即当用户朝某一方向滑动一段距离(touch slop)后,ViewGroup要向自己的子View和父View确认自己将消费事件。

    • 确认滑动的方式:Math.abs(event.getX() - downX) > ViewConfiguration.getXxxSlop()
    • 告知子View的方式:在onInterceptTouchEvent()中返回true,子View会收到 ACTION_CANCEL事件,并且后续事件不再发给子View
    • 告知父 View 的方式:调用 getParent().requestDisallowInterceptTouchEvent(true)。这个方法会递归通知每一级父View,让他们在后续事件中不要再尝试通过 onInterceptTouchEvent()拦截事件。这个通知仅在当前事件序列有效,即在这组事件结束后(即用户抬手后),父View会自动对后续的新事件序列启用拦截机制

VelocityTracker

  • 如果GestureDetector不能满足需求,或者觉得GestureDetector过于复杂,可以自己处理 onTouchEvent()的事件。但需要使用VelocityTracker来计算手指移动速度。

  • 使用方法:

    • 在每个事件序列开始是(即ACTION_DOWN事件到来时),通过VelocityTracker.obtain() 创建一个实例,或者使用velocityTracker.clear()把之前的某个实例重置

    • 对于每个事件(包括ACTION_DOWN事件),使用 velocityTracker.addMovement(event)把事件添加进 VelocityTracker

    • 在需要速度的时候(例如在ACTION_UP中计算是否达到fling速度),使用 velocityTracker.computeCurrentVelocity(1000, maxVelocity)来计算实时速度,并通过 getXVelocity() / getYVelocity()来获取计算出的速度

      • 方法参数中的1000是指的计算的时间长度,单位是ms。例如这里填入1000,那么 getXVelocity()返回的值就是每1000ms (即一秒)时间内手指移动的像素数

      • 第二个参数是速度上限,超过这个速度时,计算出的速度会回落到这个速度。例如这里填了200,而实时速度是300, 那么实际的返回速度将是200

        • maxVelocity 可以通过 viewConfiguration.getScaledMaximumFlingVelocity() 来获取

scrollTo / scrollBy 和 computeScroll()

  • scrollTo() / scrollBy()会设置绘制时的偏移,通常用于滑动控件设置偏移

  • scroll值表示绘制行为在控件内部内容的起始偏移(类似:我要从内容的第300个像素开始绘制),因此scrollTo()内的参数填正值时,绘制内容会向负向移动

  • scrollTo()是瞬时方法,不会自动使用动画。如果要用动画,需要配合View.computeScroll()方 法

  • computeScroll()在View重绘时被自动调用

  • 使用方式:

    // onTouchEvent()中:
    overScroller.startScroll(startX, startY, dx, dy);
    postInvalidateOnAnimation();
    
    // onTouchEvent()外:
    @Override
    public void computeScroll() {
    	if (overScroller.computeScrollOffset()) { // 计算实时位置 
    		scrollTo(overScroller.getCurrX(), overScroller.getCurrY()); // 更新界面 	
    		postInvalidateOnAnimation(); // 下一帧继续
    	}
    }
    

完整代码

public class TwoPager extends ViewGroup {
    float downX;
    float downY;
    float downScrollX;
    boolean scrolling;
    float minVelocity;
    float maxVelocity;
    OverScroller overScroller;
    ViewConfiguration viewConfiguration;
    VelocityTracker velocityTracker = VelocityTracker.obtain();

    public TwoPager(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        overScroller = new OverScroller(context);
        viewConfiguration = ViewConfiguration.get(context);
        maxVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
        minVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;
        int childTop = 0;
        int childRight = getWidth();
        int childBottom = getHeight();
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.layout(childLeft, childTop, childRight, childBottom);
            childLeft += getWidth();
            childRight += getWidth();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            velocityTracker.clear();
        }
        velocityTracker.addMovement(ev);

        boolean result = false;
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                scrolling = false;
                downX = ev.getX();
                downY = ev.getY();
                downScrollX = getScrollX();
                break;
            case MotionEvent.ACTION_MOVE:
                float dx = downX - ev.getX();
                if (!scrolling) {
                    if (Math.abs(dx) > viewConfiguration.getScaledPagingTouchSlop()) {
                        scrolling = true;
                        getParent().requestDisallowInterceptTouchEvent(true);
                        result = true;
                    }
                }
                break;
        }
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            velocityTracker.clear();
        }
        velocityTracker.addMovement(event);

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                downScrollX = getScrollX();
                break;
            case MotionEvent.ACTION_MOVE:
                float dx = downX - event.getX() + downScrollX;
                if (dx > getWidth()) {
                    dx = getWidth();
                } else if (dx < 0) {
                    dx = 0;
                }
                scrollTo((int) (dx), 0);
                break;
            case MotionEvent.ACTION_UP:
                velocityTracker.computeCurrentVelocity(1000, maxVelocity);
                float vx = velocityTracker.getXVelocity();
                int scrollX = getScrollX();
                int targetPage;
                if (Math.abs(vx) < minVelocity) {
                    targetPage = scrollX > getWidth() / 2 ? 1 : 0;
                } else {
                    targetPage = vx < 0 ? 1 : 0;
                }
                int scrollDistance = targetPage == 1 ? (getWidth() - scrollX) : - scrollX;
                overScroller.startScroll(getScrollX(), 0, scrollDistance, 0);
                postInvalidateOnAnimation();
                break;
        }
        return true;
    }

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

本文地址:https://blog.csdn.net/aha_jasper/article/details/110672173

相关标签: Android