View篇之View的滑动

学习了Android的坐标系,接下来,我将讲解如何实现Android的View滑动,以及常见的实现view滑动的方法和对比,最后讲解每一个方法。

如何实现Android的View滑动?

实现View的滑动方法有很多,但是万变不离其宗,实现的思想总是相同的或者类似的。当触碰View时,记录当前触碰点的坐标;当手指移动时,记录移动后的触碰点的坐标;从何计算出相当于前一次坐标的偏移量,并通过偏移量修改View的坐标。

常见的实现View的方法

  • layout
  • offsetLeftAndRight + offsetTopAndBottom
  • setLayoutParams 修改View的LayoutParams
  • scrollBy / scrollTo
  • Scroller + ((View)getParent()).scrollTo
  • 动画
  • ViewDragHelper

其中View的滑动可以瞬时的,也可以非瞬时的,按瞬时和非瞬时分类

  • 瞬时滑动
    • layout
    • offsetLeftAndRight + offsetTopAndBottom
    • setLayoutParams
    • scrollBy / scrollTo
  • 非瞬时滑动,
    • Scroller + ((View)getParent()).scrollTo
    • 动画
    • ViewDragHelper
方法讲解和使用
1. layout

原理:当View绘制时,会调用onLayout()方法设置它的显示位置。我们调用layout对自己重新布局,它内部的实现调用onLayout,从何达到View的移动。layout实现的部分代码如果:

		
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b); // layout 内部调用onLayout
        }

关键代码:
首先每一次触碰时,获取触碰点的坐标,代码如下所示:

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

接着在 ACTION_DOWN记录触碰点的坐标,代码如下所示:

            case MotionEvent.ACTION_DOWN:
                //记录触摸点的坐标
                mLastX = rawX;
                mLastY = rawY;
                break;

最后在 ACTION_MOVE中计算偏移量,并将偏移量作用到layout方法中,代码如下所示:

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

                //在当前的left、top、right、bottom的基础上添加偏移量
                layout(getLeft() + offsetX, getTop() + offsetY,
                        getRight() + offsetX, getBottom() + offsetY);
                
                //重新设置初始坐标
                mLastX = rawX;
                mLastY = rawY;
                break;

注意:在上述代码我们使用的getRawX()、getRawY()获得绝对坐标来计算偏移量,当然我们可以用getX()、getY()获得相当坐标来计算偏移量。

2. offsetLeftAndRight + offsetTopAndBottom

offsetLeftAndRight 、offsetTopAndBottom 是系统封装了对View左右、上下移动的方法。使用它们的代码如下:

                offsetLeftAndRight(offsetX);
                offsetTopAndBottom(offsetY);
3. 修改View的LayoutParams

LayoutParams保存了View的布局参数,因此通过修改LayoutParams达到改变View位置的效果。关键代码如下:

                ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
                params.leftMargin = getLeft() + offsetX;
                params.topMargin = getTop() + offsetY;
                setLayoutParams(params);

注意:根据父布局的类型不同,需要根据情况使用不同LayoutParams,如LinearLayout.LayoutParams、RelativeLayout.LayoutParams。这么我们用的是ViewGroup.MarginLayoutParams,因为它是ViewGroup的LayoutParams的一个子类,是其他布局的LayoutParams的父类。用它不用考虑父布局的类型。

4. scrollBy / scrollTo

scrollBy和scrollTo的区别很好理解,根据名字来理解。

  • scrollTo(x, y)表示移动一个具体的坐标点(x, y)
  • scrollBy(dx, dy)表示移动的增量为dx, dy

在获得偏移量之后,我们使用scrollBy来移动View。代码如下所示:

scrollBy(offsetX, offsetY)

你会发现View并没有移动。scrollBy 和scrollTo移动的是View的content,移动的是View的内容。在ViewGroup中使用scrollBy 、scrollTo,移动是所有的子View;在View中使用scrollBy 、scrollTo,移动使View的内容,例如TextView的content就是它的文本、ImageView的content就是它的drawable对象。

因此我们应该在ViewGroup中使用scrollBy 来移动它的子View,scrollBy 代码中调用scrollTo实现的。代码如下所示:

    ((View)getParent()).scrollBy(offsetX, offsetY);

但是,当再拖动View时,你会发现View虽然动了,却是在乱动。下面我将给大家讲解一下view移动的知识了。

如下面的图所示,UI可见区域,为屏幕,即红色矩阵范围。绿色区域就是View,content就是它的内容,画布。画布可以很大,可以很小,我们看不见的View,不代表它不存在,比如在下面的图中,可见区域只有一个Button(20, 10),还有一个Button我们就看不见。

Android 屏幕

而scrollBy 移动就是屏幕,当调用scrollBy(20, 10),屏幕在X轴正方向(右边)平移20,在Y轴反方向(下发)平移10,得到如下所示的结果。可以区域的button坐标,变成了(0, 0)。scrollBy(20, 10)屏幕沿X轴正方向,Y轴正方向移动了,但是button相当于屏幕来说却向X轴负方向,Y轴负方向移动了。因为参考系选择不同。
在这里插入图片描述

总结一下:

  1. scrollBy移动是View的content,如果要移动View,获取ViewGroup,调用scrollBy;
  2. scrollBy移动是屏幕,(dx, dy)是屏幕移动的方向,因为参考系的不同,其实对于View的content来说是移动是(-dx, -dy);
  3. scrollBy是基于scrollTo实现的;

综上所述,修改上面代码。即可达到我们想要的结果,代码如下所示:

((View)getParent()).scrollBy(-offsetX, -offsetY);
5. Scroller

使用Scroller类实现非瞬时滑动,即平滑移动的效果。Scroller实现的原理图如下所示。
Scroller 原理图
使用Scroller有三个步骤。

  • 初始化Scroller
mScroller = new Scroller(getContext());
  • 重写View的computeScroll()方法。
    重写computeScroll(),它是使用Scroller的核心,系统绘制View的时候,会在draw()方法,调用该方法。该方法实际上就是使用scrollTo,完成对View的移动。移动的坐标点,通过Scroller对象,获得。
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()){ //判断是否完成绘制
            ((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

computeScrollOffset()判断是否完成整个滑动,getCurrX(),getCurrY()获得当前滑动的坐标。 computeScroll不能直接调用,我们通过invalidate()→draw()→computeScroll()方式,间接调用。

  • startScroll 开始滑动过程。
    通常可以使用getScrollX(),getScrollY()方法来获得父视图中content所滑动到点的坐标,这一个值的正负与scrollBy、scrollTo的情况一样。
public void startScroll(int startX, int startY, int dx, int dy)
public void startScroll(int startX, int startY, int dx, int dy, int duration)

例子:修改例子4的代码,达到手机离开屏幕时,让子View平滑的移动到初始位置。监听手指离开,之需要监听ACTION_UP事件就可以了,关键代码如下:

            case MotionEvent.ACTION_UP:
                View view = (View)getParent();
                mScroller.startScroll(view.getScrollX(), view.getScrollY(),
                        -view.getScrollX(), -view.getScrollY());
                invalidate();//通知View进行重新绘制,从而调用computeScroll
                break;

Scroller补充:Scroller原理简单讲解。

  • Scroller可以设置不同Interpolator插值器,我们上一个例子是匀速的滑动,我们可以设置插值器达到加速、减少,先加速再减少的效果,在两点之间有无数个函数,所有我们想达到的效果可是无穷的。
  • Scroller本身是不能滑动View,滑动View的是scrollTo方法。
  • startScroll只是做属性值的初始化。
  • computeScrollOffset用于判断是否完成滑动,它主要工作是根据插值器和已经发生的时间,更新当前的滑动坐标(mCurrX, mCurrY)。
6. 动画

动画这里简单的讲解一下,下面章节会详细讲解动画

  • 视图动画,xml实现View移动
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
    <translate
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:duration="3000"
        android:toXDelta="500"
        android:toYDelta="500"
        />
</set>
 this.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.translate));
  • 视图动画,Java实现View移动
        TranslateAnimation animation = new TranslateAnimation( 0, 500, 0, 500);
        this.setAnimation(animation);
        animation.setDuration(3000);
        animation.setFillAfter(true);
        animation.startNow();
  • 属性动画
        ObjectAnimator animatorX = ObjectAnimator.ofFloat(this, "translationX", 0, 500);
        ObjectAnimator animatorY = ObjectAnimator.ofFloat(this, "translationY", 0, 500);
        AnimatorSet animationSet = new AnimatorSet();
        animationSet.setDuration(3000);
        animationSet.playTogether(animatorX, animatorY);
        animationSet.start();
7. ViewDragHelper

以后讲解
参考博客

https://www.jianshu.com/p/543b88fa609c
https://blog.csdn.net/guolin_blog/article/details/48719871

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值