View基础与滑动

对于Android来说,用户所看见或者交互的界面都是view,view是所有控件的基类。常见的TextView、Button,或者布局RelativeLayout,最终都是继承View类。除了view外,还有ViewGroup,顾名思义,ViewGroup中包含多个view,当然ViewGroup也是继承View的。这样,所有view构成了树的关系,在进行事件分发时,对当前view树进行遍历。本篇文章将就View基础以及其滑动、弹性滑动进行阐述。

View基础

下面将从View的位置参数、MotionEvent、TouchSlop对象、VelocityTracker、GestureDetector和Scroll对象,对View的基础进行介绍。

  • 位置参数
    View的位置主要有它的四个顶点决定。分别为Left、Top、Right、Bottom。 left为左上角横坐标,top为左上角纵坐标,right为右下角横坐标,bottom为右下角纵坐标。其中x、y的方向在android中的方向分别为水平向右,垂直向下。值得注意的是,上述的四个位置坐标,都是view相对于父view而言的,并不是相对原点坐标。
    这里写图片描述
    通过坐标关系,可以知道view的宽高:
width = right - left;
height = bottom - top;

那么,如何获取到view的四个坐标呢:

left = getLeft();
top = getTop();
right = getRight();
bottom = getBottom();

在Android 3.0后,View新增了x、y、translationX、transLationY四个参数。其中x、y为左上角坐标,translationX、translationY是view相对于父view在x和y方向的偏移量。同样的,四个参数的坐标也是相对于父view而言。

x = left + translationX;
y = top + translationY;

view在平移的过程中,其left、top、right、bottom四个坐标是不变的,改变的是x、y、translationX、transLationY四个参数。

  • MotionEvent
    当用户手指触摸手机屏幕后,所触发的事件集合:
 ACTION_DOWN: 手指接触到屏幕
 ACTION_MOVE: 手指在屏幕移动
 ACTION_UP: 手指离开屏幕

通过MotionEvent事件,可以得到点击事件发生的x、y坐标,分别通过getX()、getY(),采用getRawX()和getRawY()方法获取view相对于屏幕左上角,即原点的坐标值。

  • TouchSlop
    系统所能识别的认为滑动的最小距离。换句话说,当用户滑动两点的距离小于这个值时,系统将不会认为是滑动事件。有了这个值,我们在处理滑动事件时,可以通过这个值进行滑动判断,大于该值才判断为滑动事件,提升用户体验。不同的设备,TouchSlop的值也会不同,通过下面代码获取:
ViewConfiguration.get(this).getScaledTouchSlop();
  • VelocityTracker
    用于追踪在滑动过程中,x、y方向上的速度。
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
//内存回收
velocityTracker.clear();
velocityTracker.recycle();

首先通过computeCurrentVelocity方法设定计算速度的时间单位,上面代码设定为1s,然后通过getXVelocity方法获取x方向上的速度,getYVelocity方法获取y方向上的速度。这里的速度,指的是在设定时间内手指滑动的像素值。速度有正负,当向着x、y方向反向滑动时,速度为负。

速度 = (终点 - 起点)/ 时间
  • GestureDetector
    辅助检测用户长按、点击、双击等事件。需要实现OnGestureListener接口,或者OnDoubleTapListener接口,监听双击事件。
    创建GestureDetector对象,并实现接口:
mGestureDetector = new GestureDetector(new GestureDetector.OnGestureListener() {
            @Override
            public boolean onDown(MotionEvent motionEvent) {
                return false;
            }

            @Override
            public void onShowPress(MotionEvent motionEvent) {

            }

            @Override
            public boolean onSingleTapUp(MotionEvent motionEvent) {
                return false;
            }

            @Override
            public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
                return false;
            }

            @Override
            public void onLongPress(MotionEvent motionEvent) {

            }

            @Override
            public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
                return false;
            }
        });

接管目标view的onTouchEvent方法:

mButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                boolean result = mGestureDetector.onTouchEvent(motionEvent);
                return result;
            }
        });

其中,常用方法的功能如下:
这里写图片描述
这里写图片描述
- Scroller
用于实现view的弹性滑动。当我们使用scrollTo/scrollBy方法进行view滑动时,过程是瞬间完成的,没有过渡的效果,用户体验不好。这时,就可以通过Scroller来让滑动在一定时间内完成,产生滑动效果。具体用法后面会介绍。

View滑动
有了上面view的基础,这里我们学习view的滑动。对于Android移动设备而言,其具有屏幕尺寸的限制。通常情况下会采用滑动view,来向用户展示更多的信息。因此,掌握view滑动基础显得很重要。为后面自定义绚丽、多功能的view打好基础。下面介绍view滑动常用的三种方法:

  • scrollTo/scrollBy

    对于View,其自身提供了两个方法,即scrollTo/scrollBy来进行视图的滑动。下面先看看两个方法的源码:

public void scrollTo(int x, int y) {  
      if (mScrollX != x || mScrollY != y) {  
          int oldX = mScrollX;
          int oldY = mScrollY; 
          mScrollX = x;  
          mScrollY = y;  
          //调用说明状态改变  
          onScrollChanged(mScrollX, mScrollY, oldX, oldY);
          if (!awakenScrollBars()) { 
              //通知视图进行重绘   
              invalidate();
          }  
      }  
  }

public void scrollBy(int x, int y) {  
       scrollTo(mScrollX + x, mScrollY + y);  
} 

源码相对来说,比较简单。可以知道scrollBy方法最终调用的是scrollTo方法。scrollTo方法实现的是绝对滑动,而scrollBy实现的是基于当前位置的滑动。在源码中,有两个参数特别重要,mScrollX 、mScrollY 。首先,我们先明确两个概念:
view的位置,即view控件的上下左右位置,在上面的内容已介绍过;view内容的位置,即view控件内内容的位置。mScrollX ,可以通过getScrollX获取其值,等于view左边缘到view内容左边缘的水平距离; mScrollY 。通过getScrollY 获取其值,等于view上边缘到view内容上边缘的值。值得注意的是,scrollTo/scrollBy方法改变的是view内容的值,并不能改变view的布局。同时,mScrollX 、mScrollY两个值都有正负之分。

mScrollX  = View左边缘 - view内容左边缘
mScrollY  = View上边缘 - view内容上边缘

下面通过一张图来展示:
这里写图片描述

  • 使用动画

除了采用View自带的scrollTo/scrollBy方法实现滑动外,还可以通过动画来实现。简单的回忆下动画。在Android中,动画可分为帧动画、视图动画、属性动画。帧动画的实现,是多张静态图片的叠加,产生动画的视觉效果;视图动画,可分为alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转)四种形式;属性动画是3.0后才推出的,顾名思义,是对于对象属性的动画。下面我们通过动画来实现view的滑动:
视图动画:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!--透明度 alpha -->

    <!--缩放 scale-->

    <!--旋转 rotate-->

    <!--移动
         duration:动画持续时间
         fillAfter:是否保持动画结束时状态
         fromXDelta:x方向开始位置
         toXDelta:x方向结束位置-->
    <translate
        android:duration="3000"
        android:fillAfter = "true"
        android:fromXDelta="0"
        android:toXDelta="300"
        android:fromYDelta="0"
        android:toYDelta="300"
        />
</set>

Animation animation = AnimationUtils.loadAnimation(this,R.anim.anim_test);
//animation.setFillAfter(true);
mImageView.startAnimation(animation);

视图动画一般采用xml来定义view的动画效果,这样比较清晰,以及好扩展。

属性动画:

 ObjectAnimator.ofFloat(mImageView,"translationX",0,300).setDuration(2000).start();

通过属性动画来滑动view显得比较简洁。但值得注意的是,通过视图动画滑动view,滑动的其实是view的内容,而view控件本身的位置是没有改变的。相反,属性动画则做到了view的真实滑动。

  • 改变布局参数

改变布局参数来实现view的滑动,主要是改变LayoutParams 参数实现。比如需要将当前view向有平移一段距离,只需要将LayoutParams 的leftMargin 值增加对应值即可。值得一提的是,通过改变布局参数来数显的滑动,view的真身也实现了滑动,而不是像scrollTo/scrollBy只是滑动了view内容。

RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mImageView.getLayoutParams();
layoutParams.leftMargin += 100;
mImageView.requestLayout();

上面介绍了scrollTo/scrollBy、动画、改变布局参数三种方法来实现了view的滑动,各种方法各有有优缺点,在实际的开发中,根据具体需求具体选择。

View弹性滑动

上面介绍了scrollTo/scrollBy、动画、改变布局参数三种方法来实现了view的滑动,但细心的你发现,除了动画可以设置duration参数外,其他方法实现的view的滑动都是瞬间完成的,没有滑动的过程,影响用户体验。下面介绍如何实现view的弹性滑动。

  • Scroller

    很多大家所熟知的控件在内部都是使用Scroller来实现的,如ViewPager、ListView等,关于scroller的使用也很简单:
    1> 创建Scroller的实例
    2> 调用startScroll()方法来初始化滚动数据并刷新界面
    3> 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑

mScroller = new Scroller(context);

public void beginToScoll(int x, int y){
        int beginX = getScrollX();
        int beginY = getScrollY();
        int toX = x - beginX;
        int toY = y - beginY;
        mScroller.startScroll(beginX,beginY,toX,toY,2000);
        invalidate();
    }

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

在创建Scroller实例后,我们调用其startScroll来开始滑动,其实Scroller内部什么也没做,只是保持了我们传入的参数。其中参数一为X方向起始位置,参数二为Y方向上起始位置,参数三为在x方向上滑动的距离,参数四为Y方向上滑动的距离。那么你一定会问,那具体的滑动实现在哪里实现呢?
之所以Scroller要配合computeScroll方法结合实现,原因就在这里。computeScroll起始是view的方法,当我们调用Scroller的sartScroll传递好参数后,调用invalidate方法会促使view的onDraw方法,onDraw方法又会调用computeScroll方法,我们在computeScroll方法中调用scrollTo方法来实现view的滑动。那么好学的你又要提问了,view的scrollTo方法也不能实现弹性滑动啊?那么请看computeScrollOffset方法,具体的源码就不分析了,该方法用于计算当前scrollX、scrollY的值,计算方法也很简单,就是根据时间流逝的值,在根据百分百算出对应的值,相当于动画中差值器的概念。返回值为true时,表示还未滑动结束,需要继续绘制,返回false时,表示滑动完成。所以,当computeScrollOffset为true时,我们不断用scrollTo方法滑动view,并通过postInvalidate刷新UI,这样就实现了弹性滑动。

  • 动画实现弹性滑动

    对于动画来说,其本身就具备渐变的属性,通过下面方法可以实现view在水平方向的弹性滑动。

ObjectAnimator.ofFloat(mImageView,"translationX",0,300).setDuration(2000).start();

另外,在这里我们学习动画的另一种用法:

ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float progess = animator.getAnimatedFraction();
                int startX = 0;
                int step = 100;
                mImageView.scrollTo(startX + (int) (step * progess));
            }
        });
animator.start();

上面的代码中,动画本质上并没有作用在任何对象上,只是在特定时间内完成了整个动画过程。但是通过监听onAnimationUpdate,可以获取到当前动画的进度,然后通过该进度我们可以制定我们想要的操作。比如上面,我们通过该进度,来进行view的弹性滑动,效果类似与Scroller。

  • 使用延时策略实现弹性滑动

我们都知道,对于Handler或者view都有类似于postDelayed的方法,可以达到延时发送消息的效果。当然,线程的sleep方法也可以具有同样的效果。因此,我们可以利用相关的机制,来实现view的弹性滑动。下面以Handler为例:

private final static int MASSAGE_TO_SCROLL = 0;
private final static int DELAY_TIME = 30;
private final static int DELAY_CONUT = 30;
private int mCount = 0;
private Handler mHandler;

mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case MASSAGE_TO_SCROLL:
                        mCount ++;
                        if (mCount <= DELAY_CONUT){
                            float progress = mCount / DELAY_CONUT;
                            int toX = (int) (100 * progress);
                            mImageView.scrollTo(toX,0);
                            mHandler.sendEmptyMessageDelayed(MASSAGE_TO_SCROLL,DELAY_TIME);
                        }
                        break;
                }
            }
        };

上面的代码比较简单,这里就不再讲解了,这里只是提供了一种实现弹性滑动的思路,类似的实现方法还有很多,具体开发中根据实际需求采用最优方法。

好了,回忆一下,通过本篇文章,我们学习了View的基础,包括View的相关属性,对View有了基本的了解;接着学习了通过scrollTo/scrollBy、动画,布局参数来实现View的滑动;最后通过Scroller、动画以及延时策略实现了View的弹性滑动。相信通过本次学习,对View有了更深的认识,也会将相关知识应用到以后的开发中。

猜你喜欢

转载自blog.csdn.net/Mr_azheng/article/details/79091242
今日推荐