关闭

Android Scroll 分析

标签: android
68人阅读 评论(0) 收藏 举报
分类:

Android Scroll 分析

一、视图坐标系

视图坐标和布局坐标

  • 视图坐标 : 没有边界限制,以屏幕左上角为原点,X 轴向右为负,Y 轴向下为负
  • 布局坐标 : 以屏幕的左上角为原点, X 轴向右为正, Y 轴向下为正

View 提供的获取坐标方法

 view.getTop()      // 获取View自身的顶边到其父布局顶边的距离
 view.getBottom()   // 获取View自身的底边到其父布局顶边的距离
 view.getLeft()     // 获取View自身的左边到其父布局左边的距离
 view.getRight()    // 获取View自身的右边到其父布局左边的距离


MotionEvent 提供的方法

 motionEvent.getX();       // 获取事件距离控件左边的距离,即视图坐标
 motionEvent.getY();       // 获取事件距离控件顶边的距离,即视图坐标
 motionEvent.getRawX();    // 获取事件距离屏幕左边的距离,即绝对坐标
 motionEvent.getRawY();    // 获取事件距离屏幕顶边的距离,即绝对坐标


二、相关知识点

TouchShlp 系统所识别的滑动距离

ViewConfiguration.get(this).getScaledTouchSlop();

GestureDetector 处理手势

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        GestureDetector detector = new GestureDetector(mContext, this);
        detector.setIsLongpressEnabled(false);   // 解决长按屏幕后无法拖动的现象
        return detector.onTouchEvent(event);
    }

    /**
     * 落下
     */
    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    /**
     * 触摸并没有进行下一步操作
     */
    @Override
    public void onShowPress(MotionEvent e) {

    }

    /**
     * 单击
     */
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    /**
     * 拖动
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    /**
     * 长按
     */
    @Override
    public void onLongPress(MotionEvent e) {

    }

    /**
     * 快速滑动
     */
    @Override  
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }


三、滑动及实现的七种方法

实现的基本思路 :

  1. 当触摸 View 时,记下当前触摸点坐标
  2. 当手指移动时,记下移动后的触摸点坐标
  3. 计算移动后的坐标和前一次坐标点的偏移量
  4. 通过偏移量来修改 View 的坐标

1. 通过 layout 方法滑动

布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.ken.view.scroll.DragView
        android:layout_width="100dp"
        android:background="@android:color/black"
        android:layout_height="100dp" />

</LinearLayout>
public class DragView extends View {

    private int lastX;
    private int lastY;

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = rawX;
            lastY = rawY;
            break;

        case MotionEvent.ACTION_MOVE:
//          计算偏移量
            int offsetX = rawX - lastX;
            int offsetY = rawY - lastY;

//          在当前的位置上加上偏移量
            layout(getLeft() + offsetX, 
                   getTop() + offsetY, 
                   getRight() + offsetX, 
                   getBottom() + offsetY);

//          重新设置初始坐标
            lastX = rawX;
            lastY = rawY;
            break;
        }
        return true;
    }
}


2. offsetLeftAndRight 和 offsetTopAndBottom


public class DragView extends View {

    private int lastX;
    private int lastY;

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = rawX;
            lastY = rawY;
            break;

        case MotionEvent.ACTION_MOVE:
//          计算偏移量
            int offsetX = rawX - lastX;
            int offsetY = rawY - lastY;

//          同时对 left 和 right 进行偏移
            offsetLeftAndRight(offsetX);
//          同时对 top 和 bottom 进行偏移
            offsetTopAndBottom(offsetY);

//          重新设置初始坐标
            lastX = rawX;
            lastY = rawY;
            break;
        }
        return true;
    }
}


3. LayoutParams


public class DragView extends View {

    private int lastX;
    private int lastY;

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = rawX;
            lastY = rawY;
            break;

        case MotionEvent.ACTION_MOVE:
            int offsetX = rawX - lastX;
            int offsetY = rawY - lastY;
            ViewGroup.MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
            params.leftMargin = getLeft() + offsetX;
            params.topMargin  = getTop()  + offsetY;
            setLayoutParams(params);

            lastX = rawX;
            lastY = rawY;
            break;
        }
        return true;
    }
}


4. scrollBy 和 scrollTo

  • 移动的是 View 的内容或 ViewGroup 的所有子View
  • 采用视图坐标系, 要对偏移量取反
public class DragView extends View {

    private int lastX;
    private int lastY;

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = rawX;
            lastY = rawY;
            break;

        case MotionEvent.ACTION_MOVE:
            int offsetX = rawX - lastX;
            int offsetY = rawY - lastY;
//          scrollBy 只能移动自己的内容, 所以要通过父控件来移动他
            ((View)getParent()).scrollBy(-offsetX, -offsetY);  // scrollBy 和 scrollTo 采用视图坐标系, 所以要对偏移量取反

            lastX = rawX;
            lastY = rawY;
            break;
        }
        return true;
    }
}


5. Scroller

  • 需要与 View 的 computeScroll() 方法配合使用
public class DragView extends View {

    private Scroller mScroller;
    private int lastX;
    private int lastY;

    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }

    /**
     * 在 draw() 方法中调用, 主要功能是计算拖动的位移量、更新背景、设置要显示的屏幕
     */
    @Override
    public void computeScroll() {
//      判断动画是否执行结束
        if(mScroller.computeScrollOffset()) {
            ((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();      
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = rawX;
            lastY = rawY;
            break;

        case MotionEvent.ACTION_MOVE:
            View view = ((View)getParent());
            mScroller.startScroll(view.getScrollX(), 
                                  view.getScrollY(), 
                                  -(rawX - lastX), 
                                  -(rawY - lastY), 0);
            lastX = rawX;
            lastY = rawY;
            invalidate();
        }
        return true;
    }
}


6. ViewDragHelper




/**
 * 侧滑菜单
 * @author Ken
 *
 */
public class DragViewGroup extends FrameLayout {

    private ViewDragHelper mViewDragHelper;
    private View mMenuView;
    private View mMainView;
    private int mWidth = 500;

    public DragViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    protected void onFinishInflate() {
        mMenuView = getChildAt(0);
        mMainView = getChildAt(1);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mWidth = mMenuView.getMeasuredWidth();
    }



    private ViewDragHelper.Callback callback = new Callback() {

        /**
         * 何时开始检测触摸事件
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return mMainView == child; // 如果当前触摸的 child 是 mMainView 时开始检测
        }

        /**
         * 处理垂直滑动
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return 0;
        };

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if(left > mWidth) return mWidth;  
            if(left < 0)   return 0;
            return left;
        };

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            mViewDragHelper.smoothSlideViewTo(mMainView, mMainView.getLeft() < mWidth ? 0 : mWidth, 0);
            ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
        };
    };

    @Override
    public void computeScroll() {
        if(mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:164次
    • 积分:22
    • 等级:
    • 排名:千里之外
    • 原创:1篇
    • 转载:0篇
    • 译文:2篇
    • 评论:0条
    文章分类
    文章存档