View的滑动方法

基本思想:当触摸View时,系统记下当前触摸点的坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标,这样不断重复,从而实现滑动过程。

先看一下简单的app:


方法一:layout方法

原理:

我们知道,View进行绘制时,会调用onLayout方法来设置显示的位置。同样,可以通过修改View的left、top、right、bottom四个属性来控制View的坐标。

代码:

public class MainActivity extends AppCompatActivity {

    private Button btn;
    private int    startX;
    private int    startY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                //获取点击事件发生的xy坐标
                int x = (int) event.getX();
                int y = (int) event.getY();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        startX = x;
                        startY = y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //计算偏移量
                        int offsetX = x - startX;
                        int offsetY = y - startY;
                        //在当前lefttoprightbottom的基础上加上偏移量
                        //这样每次移动后,View都会调用Layout方法来对自己重新布局,
                        // 从而达到移动View的效果
                        btn.layout(btn.getLeft() + offsetX, btn.getTop() + offsetY,
                                btn.getRight() + offsetX, btn.getBottom() + offsetY);
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return true;
            }
        });

    }
方法二:offsetLeftAndRight()与offsetTopAndBottom()

原理:

 这个方法相当于系统提供了一个对左右、上下移动的API的封装。原理和方法一一样。

代码:

public class MainActivity extends AppCompatActivity {

    private Button btn;
    private int    startX;
    private int    startY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                //获取点击事件发生的xy坐标
                int x = (int) event.getX();
                int y = (int) event.getY();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        startX = x;
                        startY = y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //计算偏移量
                        int offsetX = x - startX;
                        int offsetY = y - startY;
                        //同时对leftright进行偏移
                        btn.offsetLeftAndRight(offsetX);
                        //同时对leftright进行偏移
                        btn.offsetTopAndBottom(offsetY);
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return true;
            }
        });
    }
方法三:LayoutParams

原理:

LayoutParams保存了一个View的布局参数。因此可以在程序中,通过改变LayoutParams来动态地修改一个布局的位置参数(通常改变的是View的Margin属性),从而达到改变View位置的效果。

代码:

public class MainActivity extends AppCompatActivity {

    private Button btn;
    private int    startX;
    private int    startY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                //获取点击事件发生的xy坐标
                int x = (int) event.getX();
                int y = (int) event.getY();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        startX = x;
                        startY = y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //计算偏移量
                        int offsetX = x - startX;
                        int offsetY = y - startY;
                        /**
                         * 注意:通过getLayoutParams()获取LayoutParams时,
                         * 需要根据View所在父布局的类型来设置不同的类型,
                         * (为什么呢?我们知道LayoutParams作用就是子视图告诉父视图
                         * 它们想要变成什么样的布局,因此必须要使用父控件.LayoutParams,况且只有ViewGroup
                         * 才有LayoutParams属性)
                         * 我们的button是放在RelativeLayout中,
                         * 那么就可以使用RelativeLayout.LayoutParams
                         */
                        RelativeLayout.LayoutParams layoutParams =
                                (RelativeLayout.LayoutParams) btn.getLayoutParams();
                        layoutParams.leftMargin = btn.getLeft() + offsetX;
                        layoutParams.topMargin = btn.getTop() + offsetY;
                        btn.setLayoutParams(layoutParams);
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return true;
            }
        });
    }
当然,除了使用父布局的LayoutParams之外,还可以使用ViewGroup.MarginLayoutParams来实现这样的功能。(这样更加方便,不需要考虑父布局的类型,它们本质是一样的,这也是我一般使用的方法)

ViewGroup.MarginLayoutParams layoutParams =
        (ViewGroup.MarginLayoutParams) btn.getLayoutParams();
layoutParams.leftMargin = btn.getLeft() + offsetX;
layoutParams.topMargin = btn.getTop() + offsetY;
btn.setLayoutParams(layoutParams);
方法四:scrollTo与scrollBy

原理:

在一个View中,系统提供了scrollTo、scrollBy两种方式来改变一个View的位置。这两个方法的区别:

scrollTo(x,y)表示移动到一个具体的坐标点(x,y);

scrollBy(dx,dy)表示移动的增量为dx、dy;

注意:scrollTo、scrollBy方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollTo、scrollBy方法,那么移动的将是所有子View,但如果在View中使用,那么移动的将是View的内容,例如:TextView,content就是它的文本,ImageView,content就是它的drawable对象。所以不能在View中使用这两个方法来拖动这个View。

因此:我们使用(View)getParent()获取到当前View所在的ViewGroup

之后调用scrollBy(-offsetX,-offsetY)即可。

纳尼?偏移量怎么写个负数?

解释:

我们之前说了,scrollTo、scrollBy方法移动的是View的content,必须得写成负数才能随着手指的移动而移动。画个小图可以看的更明白:


代码:

public class MainActivity extends AppCompatActivity {

    private Button btn;
    private int    startX;
    private int    startY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                //获取点击事件发生的xy坐标
                int x = (int) event.getX();
                int y = (int) event.getY();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        startX = x;
                        startY = y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //计算偏移量
                        int offsetX = x - startX;
                        int offsetY = y - startY;
                        ((View) btn.getParent()).scrollBy(-offsetX,-offsetY);
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return true;
            }
        });
    }
我们可以看源码就知道scrollBy底层也调用了scrollTo

public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}
说到源码,这里由不得提一嘴:

mScrollX = getScrollX();

mScrollY = getScrollY();

在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,而mScrollY的值总是等于View上边缘和View内容上边缘在竖直方向的距离。

方法五:Scroller

原理:

其实原理和scrollTo、scrollBy是类似。但是如果现在有这样一个需求:通过点击按钮,让一个ViewGroup的子View向右移动200个像素。我们当然可以使用scrollBy方法设置偏移量,但是不管使用scrollTo还是scrollBy方法,子View的平移都是瞬间发生的,在事件执行的时候平移就已经完成了,这样用户体验太差了,同时也不符合Google建议的使用自然的过度动画来实现移动效果的原则。因此Scroller类就应用而生了,通过Scroller类可以实现平滑移动的效果。

使用三部曲:

第一步:初始化Scroller

通过它的构造方法来创建一个Scroller对象

Scroller scroller = new Scroller(context);
第二步:重写computeScroll()方法,实现模拟滑动

@Override
public void computeScroll() {
    super.computeScroll();
    //判断Scroller是否执行完毕
    if (mScroller.computeScrollOffset()) {
        ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        //通过重绘来不断调用computeScroll方法
        invalidate();
    }
}
第三步:startScroll开启模拟过程

mScroller.startScroll(int startX,int startY,int dx,int dy,int duration);

方法六:属性动画

ObjectAnimator
        .ofFloat(targetView,"translationX",0,100)
        .setDuration(100)
        .start();
方法七:ViewDragHelper以及一些开源的动画库如Nineoldandroids

没有写实例代码的会在接下来的代码中都有涉及。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunnyRivers

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值