Android实现滑动方式汇总

一、Layout方法

在view绘制的时候会调用onLayout()方法来设置显示的位置,同样可以通过修改View的left、top、right、bottom四个属性来控制View的坐标。在每次回调onTouchEvent的时候,我们都获取一下触摸点的坐标,首先在ACTION_DOWN事件中记录触摸点的坐标,然后可以在ACTION_MOVE事件中计算偏移量,并将偏移量作用到layout 方法中,这样在每次移动后,View都会调用layout方法来对自己重新布局,从而达到移动View的效果。

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offX = x - lastX;
                int offY = y - lastY;
                layout(getLeft() + offX, getTop() + offY, getRight() + offX, getBottom() + offY);
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }

        return true;
    }

二、LayoutParams

LayoutParams保存了一个View的布局参数,可以通过LayoutParams来动态的修改一个布局参数,从而达到改变View位置的效果。使用getLayoutParams()来获取一个View的LayoutParams,当获取到偏移量之后就可以通过setLayoutParams来改变View的位置
 @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offX = x - lastX;
                int offY = y - lastY;
                ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
                layoutParams.leftMargin += offX;
                layoutParams.topMargin += offY;
                setLayoutParams(layoutParams);
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }

        return true;
    }

三、scrollTo与scrollBy

View中提供了scrollTo、scrollBy两个方法来改变一个View的位置。这两个方法的区别非常好理解,scrollTo(x,y)表示移动到一个坐标点(x,y),而scrollBy(x,y)表示移动的增量为x,y。
scrollTo、scrollBy方法移动的是View的content,即让view的内容移动,不妨想象手机屏幕时一个中空的盖板,盖板下面是一个巨大的画布,也就是我们想要显示的内容。当把这个盖板在画布上移动到某一处的时候,透过中间空的矩形,我们可以看见手机屏幕上显示的视图,而画布上其他地方的视图,则被盖板盖住了无法看到。我们的视图与这个例子非常的相似,我们没有看到的视图,并不代表他不存在,有可能只是在屏幕外面而已
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offX = lastX - x;
                int offY = lastY - y;
                ((View) getParent()).scrollBy(offX, offY);
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }

        return true;
    }

四、Scroller与OverScroller

Scroller和OverScroller,这两个是AndroidUI框架下实现滚动效果的最关键的类,ScrollView内部的实现也是使用的OverScroller,所以熟练的使用这两个类的相关API,可以让我们满足大部分的开发需求。
 scrollTo()和scrollBy。这两个方法可以实现View内容的移动,注意,是内容,不是位置!是移动,不是滚动!什么叫做内容呢?比如说一个TextView,如果使用scrollTo(),那么移动的是里面的文字,而不是位置,scrollBy()也是一样的。那么为什么是移动,不是滚动呢?这是因为这两个方法完成的都是瞬间完成,即瞬移,而不是带有滚动过程的滚动,所以说,如果要实现效果比较好的滚动,光靠View自带的方法还是不行滴,还是要Scrollers出马
 mScroller.getCurrX() //获取mScroller当前水平滚动的位置  
 mScroller.getCurrY() //获取mScroller当前竖直滚动的位置  
 mScroller.getFinalX() //获取mScroller最终停止的水平位置  
 mScroller.getFinalY() //获取mScroller最终停止的竖直位置  
 mScroller.setFinalX(int newX) //设置mScroller最终停留的水平位置,没有动画效果,直接跳到目标位置  
 mScroller.setFinalY(int newY) //设置mScroller最终停留的竖直位置,没有动画效果,直接跳到目标位置  

 //滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间  
 mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默认完成时间250ms  
 mScroller.startScroll(int startX, int startY, int dx, int dy, int duration)
 mScroller.computeScrollOffset() //返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。这是一个很重要的方法,通常放在View.computeScroll()中,用来判断是否滚动是否结束。  
自定义一个CustomView,使用Scroller实现滚动:
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Scroller;

public class CustomView extends LinearLayout {

	private static final String TAG = "Scroller";

	private OverScroller mScroller;

	public CustomView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mScroller = new OverScroller(context);
	}

	//调用此方法滚动到目标位置
	public void smoothScrollTo(int fx, int fy) {
		int dx = fx - mScroller.getFinalX();
		int dy = fy - mScroller.getFinalY();
		smoothScrollBy(dx, dy);
	}

	//调用此方法设置滚动的相对偏移
	public void smoothScrollBy(int dx, int dy) {

		//设置mScroller的滚动偏移量
		mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
		invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
	}
	
	@Override
	public void computeScroll() {
	
		//先判断mScroller滚动是否完成
		if (mScroller.computeScrollOffset()) {
		
			//这里调用View的scrollTo()完成实际的滚动
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			
			//必须调用该方法,否则不一定能看到滚动效果
			postInvalidate();
		}
		super.computeScroll();
	}
}

五、ViewDragHelper

  • ViewDragHelper.Callback是连接ViewDragHelper与view之间的桥梁(这个view一般是指拥子view的容器即parentView);
  • ViewDragHelper的实例是通过静态工厂方法创建的;
  • ViewDragHelper可以检测到是否触及到边缘;
  • ViewDragHelper并不是直接作用于要被拖动的View,而是使其控制的视图容器中的子View可以被拖动,如果要指定某个子view的行为,需要在Callback中想办法;
  • ViewDragHelper的本质其实是分析onInterceptTouchEvent和onTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位置( 通过offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 )
  • 能在触摸的时候判断当前拖动的是哪个子View;
1.ViewDragHelper的初始化
ViewDragHelper一般用在一个自定义ViewGroup的内部,比如下面自定义了一个继承于RelativeLayout的DragLayout:
public class DragLayout extends RelativeLayout {
    private ViewDragHelper mDragHelper;

    public DragLayout(Context context) {
        super(context, null);
        init();
    }

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

    private void init() {
        /**
         * @params ViewGroup forParent 必须是一个ViewGroup
         * @params float sensitivity 灵敏度
         * @params Callback cb 回调
         */
        mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
        
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            mDragHelper.cancel();
            return false;
        }
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mDragHelper.processTouchEvent(ev);
        return true;
    }

    private class DragHelperCallback extends ViewDragHelper.Callback {

        /**
         * 尝试捕获子view,一定要返回true
         * @param View child 尝试捕获的view
         * @param int pointerId 指示器id?
         * 这里可以决定哪个子view可以拖动
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            Toast.makeText(getContext(), "onViewCaptured", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            Toast.makeText(getContext(), "onViewReleased", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
            switch (state) {
                case ViewDragHelper.STATE_DRAGGING:  // 正在被拖动
                    break;
                case ViewDragHelper.STATE_IDLE:  // view没有被拖拽或者 正在进行fling/snap
                    break;
                case ViewDragHelper.STATE_SETTLING: // fling完毕后被放置到一个位置
                    break;
            }
        }


        @Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
            Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show();
        }

        /**
         * 当拖拽到状态改变时回调
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
        }

        /**
         * 处理水平方向上的拖动
         * @param View child 被拖动的view
         * @param int left 移动到达的x轴的距离
         * @param int dx 建议的移动的x距离
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);
            final int leftBound = getPaddingLeft();
            final int rightBound = getWidth() - child.getWidth();
            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
            return newLeft;
        }

        /**
         *  处理竖直方向上的拖动
         * @param View child 被拖动的view
         * @param int top 移动到达的y轴的距离
         * @param int dy 建议的移动的y距离
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            final int topBound = getPaddingTop();
            final int bottomBound = getHeight() - child.getHeight();
            final int newTop = Math.min(Math.max(top, topBound), bottomBound);
            return newTop;
        }
    }

}























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值