目录

  • 一.View的基础知识
    • 1.什么是View
    • 2.View的位置参数
    • 3.MotionEvent
    • 4. TouchSlop
    • 5. VelocityTracker
    • 6. GestureDetector
  • 二.View的滑动
    • 1. 使用scrollTo/scrollBy
      • 1.1 getScrollX()、getScrollY()
      • 1.2 scrollTo/scrollBy
      • 1.3 scrollTo实例使用
    • 2. 使用动画
    • 3. 改变布局参数
    • 4. 各种滑动方式对比
  • 三.弹性滑动
    • 1.使用Scroller
    • 2.使用动画
    • 3.使用延时策略
  • 四.例子源码

一.View的基础知识

1.什么是View

View是一种界面层的控件的一种抽象,它代表了一个控件,是Android中所有控件的基类。

2.View的位置参数


3.MotionEvent

在我们触摸屏幕的过程中,可以分为三种情况,分别是按下、滑动、弹起。Android中为我们封装好了一个MotionEvent类,使得我们对屏幕的一系列操作事件都可以记录在这个MotionEvent里面。

  • ACTION_DOWN —— 手指刚接触屏幕
  • ACTION_MOVE —— 手指在屏幕上移动
  • ACTION_UP —— 手指从屏幕上松开的一瞬间

通过MotionEvent对象我们可以得到点击事件发生的x和y坐标

  • getX/getY 相对当前View左上角的x和y坐标
  • getRawX/getRawY 相对手机屏幕左上角的x和y坐标

事件序列:由一个ACTION_DOWN事件,0个或者1个或者多个ACTION_MOVE事件,加上一个ACTION_UP事件组成的一个序列

4. TouchSlop

TouchSlop是系统所能识别出的被认为是滑动的最小距离,这是一个常量,和设备有关,在不同设备上这个值可能不同
ViewConfiguration这个类主要定义了UI中所使用到的标准常量,像超时、尺寸、距离,如果我们需要得到这些常量的数据,我们就可以通过这个类来获取,具体方法如下:
获取ViewConfiguration实例:

 ViewConfiguration viewConfiguration = ViewConfiguration.get(Context);

常用的方法

//  获取touchSlop (系统 滑动距离的最小值,大于该值可以认为滑动)
int touchSlop = viewConfiguration.getScaledTouchSlop();
//  获得允许执行fling (抛)的最小速度值
int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
//  获得允许执行fling (抛)的最大速度值
int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
//  Report if the device has a permanent menu key available to the user
//  (报告设备是否有用户可找到的永久的菜单按键)
//  即判断设备是否有返回、主页、菜单键等实体按键(非虚拟按键)
boolean hasPermanentMenuKey = viewConfiguration.hasPermanentMenuKey();  

5. VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度,包含水平和竖直方向的速度。
使用过程:

//VelocityTracker是Android系统内置的速度追踪类,首先调用它来追踪当前
//点击事件的速度,event一般是通过onTouchEvent函数传递的MotionEvent对象
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);//先调用computeCurrentVelocity函数,用于设定计算速度的时间间隔
velocityTracker.computeCurrentVelocity(1000);
//这里的速度指的是一段时间内手指划过的像素数,比如将时间间隔设置为1000ms,
//在1s内,手指在水平方向划过100像素,水平速度就是100.
//  速度的计算为(终端位置-起始位置)/间隔时间。
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();//不需要时,调用clear方法回收并重置内存
velocityTracker.clear();
velocityTracker.recycle();

6. GestureDetector

手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为,使用系统的GestureDector来监听这些事件

GestureDetector内部的Listener接口:

监听器 简介
OnGestureListener 手势检测,主要有:按下(Down)、快速滑动(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress)和单击抬起(SingleTapUp)
OnDoubleTapListener 双击检测,主要有三个回调类型:双击(DoubleTap)、单击确认(SingleTapConfirmed)和双击事件回调(DoubleTapEvent)
OnContextClickListener 这是Android6.0(23)才添加的,用于检测外部设备上按钮是否按下,一般情况下可以忽略
SimpleOnGestureListener 该类是GestureDetector提供给我们的一个更方便非响应不同手势的类,这个类实现了上述三个接口(但是所有方法都是空的),该类是static类,也就是说它是一个外部类。可以在外部继承这个类,重写里面的手势处理方法。

使用:

  • 实现OnGestureListener/OnGestureListener/SimpleOnGestureListener接口
  • 实例化GestureDetectorCompat类
  • 接管目标View的OnTouchEvent方法
GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {//手指按下的瞬间return false;}@Overridepublic void onShowPress(MotionEvent e) {//手指触摸屏幕,并且尚未松开或拖动。与onDown的区别是,onShowPress强调没用松开和没有拖动}@Overridepublic boolean onSingleTapUp(MotionEvent e) {//手指离开屏幕(单击)return false;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//手指按下并拖动,当前正在拖动return false;}@Overridepublic void onLongPress(MotionEvent e) {//手指长按事件}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//手指快速滑动return false;}};mGestureDetector = new GestureDetector(this,listener);//防止长按后无法拖动的问题mGestureDetector.setIsLongpressEnabled(false);
 @Overridepublic boolean onTouchEvent(MotionEvent event) {//既然要让GestureDetector来识别各种动作事件,那么就得让GestureDetector来接管事件管理,即在onTouchEvent里面只写入如下代码return mGestureDetector.onTouchEvent(event);}

二.View的滑动

View的滑动的实现有3种方法:

  • 使用scrollTo/scrollBy
  • 使用动画
  • 改变布局参数

1. 使用scrollTo/scrollBy

scrollTo、scrollBy方法是View中的,因此任何的View都可以通过这两种方法进行移动。首先要明白的是, scrollTo、scrollBy滑动的是View中的内容(而且还是整体滑动),而不是View本身。我们的滑动控件如SrollView可以限定宽、高大小,以及在布局中的位置,但是滑动控件中的内容(或者里面的childView)可以是无限长、宽的,我们调用View的scrollTo、scrollBy方法,相当于是移动滑动控件中的画布Canvas,然后进行重绘

1.1 getScrollX()、getScrollY()

getScrollX()、getScrollY()得到的是偏移量,是相对自己初始位置的滑动偏移距离,只有当有scroll事件发生时,这两个方法才能有值,否则getScrollX()、getScrollY()都是初始时的值0,而不管你这个滑动控件在哪里。所谓自己初始位置是指,控件在刚开始显示时、没有滑动前的位置。以getScrollX()为例,其源码如下:

public final int getScrollX() {return mScrollX;
}

可以看到getScrollX()直接返回的就是mScrollX,代表水平方向上的偏移量,getScrollY()也类似。偏移量mScrollX的正、负代表着,滑动控件中的 内容相对于 初始位置在水平方向上偏移情况,mScrollX为正代表着当前内容相对于初始位置向左偏移了 mScrollX的距离,mScrollX为负表示当前内容相对于初始位置向右偏移了mScrollX的距离。 这里的坐标系和我们平常的认知正好相反。

1.2 scrollTo/scrollBy

scrollTo(int x,int y)移动的是View中的内容,而滑动控件中的内容都是整体移动的,scrollTo(int x,int y)中的参数表示View中的内容要相对于内容初始位置移动x和y的距离,即将内容移动到距离内容初始位置x和y的位置。正如前面所说,在处理偏移、滑动问题时坐标系和平常认知的坐标系是相反的。以一个例子说明scrollTo():
(1)调用scrollTo(100,0)表示将View中的内容移动到距离内容初始显示位置的x=100,y=0的地方,效果如下图:

(2)调用scrollTo(0,100)效果如下图:

源码展示:

public void scrollTo(int x, int y) {if (mScrollX != x || mScrollY != y) {int oldX = mScrollX;int oldY = mScrollY;mScrollX = x;mScrollY = y;invalidateParentCaches();onScrollChanged(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) {postInvalidateOnAnimation();}}}

scrollTo是相对于初始位置来进行移动的,而scrollBy(int x ,int y)则是相对于上一次移动的距离来进行本次移动。scrollBy其实还是依赖于scrollTo的,如下源码:

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

1.3 scrollTo实例使用

一个ViewGroup的实现类,实现手指水平滑动功能,这里只展示onTouchEvent里面的关键代码,完整代码后面给出

@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();       //相对当前View左上角的x坐标switch (event.getAction()){case MotionEvent.ACTION_DOWN:mLastX = x;           //记录开始滑动时当前View左上角的x坐标break;case MotionEvent.ACTION_MOVE:int dx = mLastX - x;            //本次手势滑动了多大距离int oldScrollX = getScrollX();  //原来的偏移量int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量if(preScrollX > (getChildCount() - 1) * getWidth()){preScrollX = (getChildCount() - 1) * getWidth();}if(preScrollX < 0){preScrollX = 0;}// scrollTo移动,注意只移动水平方向scrollTo(preScrollX,getScrollY());mLastX = x;break;}return true;}

2. 使用动画

通过使用动画,我们也可以实现一个View的平移,主要也是操作View的translationX和translationY,既可以补见动画,也可以采取属性动画。

关于动画:Android三种动画详解
 
普通动画

//layout下anim包中新建translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"android:fillAfter="true"android:zAdjustment="normal"><translateandroid:duration="100"android:fromXDelta="0"android:fromYDelta="0"android:interpolator="@android:anim/linear_interpolator"android:toXDelta="100"android:toYDelta="100" />
</set>

属性动画

ObjectAnimator.ofFloat(layout,"translationX",0,100).setDuration(1000).start();

注意:

  • View动画是对View的影像进行操作的。也就是说View动画并不能真正的改变View的位置。
  • 属性动画是真正改变View的位置,但它是从Android3.0开始推出的。

3. 改变布局参数

改变布局参数,即改变LayoutParams,比如:我们想把一个Button向右平移100px,我们只需将这个Button的LayoutParams里的marginLeft参数的值增加100px即可

MarginLayoutParams params = (MarginLayoutparamsmButton1.getLayoutParams();params.width + = 100;params.leftMargin + = 100;mButton1.requestLayout();//或者 mButton1.setLayoutParams(params);

还有一种做法:在将要移动的View前面,设置一个空的,默认宽度为0的View,若想要平均移动View,只需要设置空View的宽度即可

4. 各种滑动方式对比

三.弹性滑动

实现View的弹性滑动,即渐进式滑动。实现的方法很多,但都有一个共同的思想,将一次大的滑动分成若干次小的滑动,并在一个时间段中完成,下面就是常见的实现滑动的方法。

1.使用Scroller

两个重要的方法

方法名 解释
startScroll(int startX, int startY, int dx, int dy, int duration) 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处。
computeScrollOffset() 滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中。

部分源码解读:

public class Scroller {
private int mStartX;//水平方向,滑动时的起点偏移坐标
private int mStartY;//垂直方向,滑动时的起点偏移坐标
private int mFinalX;//滑动完成后的偏移坐标,水平方向
private int mFinalY;//滑动完成后的偏移坐标,垂直方向private int mCurrX;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,水平方向
private int mCurrY;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,垂直方向
private int mDuration; //本次滑动的动画时间
private float mDeltaX;//滑动过程中,在达到mFinalX前还需要滑动的距离,水平方向
private float mDeltaY;//滑动过程中,在达到mFinalX前还需要滑动的距离,垂直方向public void startScroll(int startX, int startY, int dx, int dy) {startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}/*** 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处
*/
public void startScroll(int startX, int startY, int dx, int dy, int duration) {mMode = SCROLL_MODE;mFinished = false;mDuration = duration;mStartTime = AnimationUtils.currentAnimationTimeMillis();mStartX = startX;mStartY = startY;mFinalX = startX + dx;//确定本次滑动完成后的偏移坐标mFinalY = startY + dy;mDeltaX = dx;mDeltaY = dy;mDurationReciprocal = 1.0f / (float) mDuration;
}/*** 滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中* @return
*/
public boolean computeScrollOffset() {if (mFinished) {//已经完成了本次动画控制,直接返回为falsereturn false;}int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);if (timePassed < mDuration) {switch (mMode) {case SCROLL_MODE:final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);mCurrX = mStartX + Math.round(x * mDeltaX);//计算出当前的滑动偏移位置,x轴mCurrY = mStartY + Math.round(x * mDeltaY);//计算出当前的滑动偏移位置,y轴break;...}}else {mCurrX = mFinalX;mCurrY = mFinalY;mFinished = true;}return true;}...
}

Scroller类中最重要的两个方法就是startScroll()和computeScrollOffset(),但是Scroller类只是一个滑动计算辅助类,它的 startScroll()和computeScrollOffset()方法中也只是对一些轨迹参数进行设置和计算,真正需要进行滑动还是得通过View的scrollTo()、scrollBy()方法。为此,View中提供了computeScroll()方法来控制这个滑动流程。computeScroll()方法会在绘制子视图的时候进行调用。其源码如下:

/**  - Called by a parent to request that a child update its values for mScrollX  - and mScrollY if necessary. This will typically be done if the child is  - animating a scroll using a {@link android.widget.Scroller Scroller}  - object.  - 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制   */
public void computeScroll() { //空方法 ,自定义滑动功能的ViewGroup必须实现方法体    }

因此Scroller类的基本使用流程可以总结如下:

  • 首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算;
  • 在调用 startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;
  • 在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘。

如下的一个简单代码示例:

@Override  public boolean onTouchEvent(MotionEvent ev) {  initVelocityTrackerIfNotExists();  mVelocityTracker.addMovement(ev);  int x = (int) ev.getX();  switch (ev.getAction()){  case MotionEvent.ACTION_DOWN:  if(!mScroller.isFinished()){  mScroller.abortAnimation();  }  mLastX = x;  break;  case MotionEvent.ACTION_MOVE:  int dx = mLastX - x;  int oldScrollX = getScrollX();//原来的偏移量  int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量  if(preScrollX > (getChildCount() - 1) * getWidth()){  preScrollX = (getChildCount() - 1) * getWidth();  }  if(preScrollX < 0){  preScrollX = 0;  }  //开始滑动动画      mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0);//第一步  //注意,一定要进行invalidate刷新界面,触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,只是一个辅助类,并不会触发界面的绘制  invalidate();  mLastX = x;  break;  }  return true;  }  @Override  public void computeScroll() {  super.computeScroll();  if(mScroller.computeScrollOffset()){//第二步  scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//第三步  invalidate();  }  }

2.使用动画

动画本身就是一种渐进的过程,因此通过它来实现太天然就具备弹性效果。比如,下面的代码就可以让一个VIew在100ms向右移动100像素

ObjectAnimator.ofFloat(layout,"translationX",0,100).setDuration(100).start();

3.使用延时策略

通过发送一系列延时消息从而达到一种渐进式的效果,具体说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。对于postDelayed方法来说,我们可以通过它来延时发送一个消息,然后在消息中来进行View的滑动,如果接连不断地发送这种延时消息,那么就可以实现弹性滑动的效果,对于sleep方法来说,通过在while循环中不断地滑动View和sleep,就可以实现弹性滑动的效果。
以Handled为例,下面的代码将VIew的内容向左移动100像素。

private static final int MESSAGE_SCROLL_TO = 1;private static final int FRAME_COUNT = 30;private static final int DELAYED_TIME = 33;private int mCount;private Handler mHandler = new Handler() {public void handleMessage(Message msg) {switch (msg.what) {case MESSAGE_SCROLL_TO: {mCount++;if (mCount <= FRAME_COUNT) {float fraction = mCount / (float) FRAME_COUNT;int scrollX = (int) (fraction * 100);Button mButton;mButton.scrollTo(scrollX,0);mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,DELAYED_TIME);}break;}default:break;}}};

四.例子源码

ViewText:关于上面滑动的demo

Android View(一)——View的基础知识相关推荐

  1. Android随机点名器,Excel基础知识-详解随机点名器

    说道制作个案例纯粹意外,我多少有点选择恐惧症,为了不在"选择"上纠结,就自己小玩了一下,就用了程序做了个选择器,其实很简单,就是有小时候玩的"点兵点将",稍微变 ...

  2. java android 小游戏_Android开发基础知识及小游戏

    学习目的 首次进入Android阶段的学习,首先需要我们掌握Activity(界面),程序的生命周期,界面启动,界面布局.控件 相关技术.及其使用 1.Activity: 管理一个界面从创建到运行结束 ...

  3. Android 游戏开发必备的基础知识

    Android游戏开发: View类开发框架 SurfaceView开发框架 Graphics类开发框架 Paint与Color类介绍 Canvas介绍 几何图形绘制 字符串绘制 图像的绘制 图像的旋 ...

  4. 3G应用开发之Android 传智播客 基础知识总结

    3G应用开发之Android Android应用开发之3G  3G应用开发之Android 3G应用开发之Android 应用开发之 讲师: 讲师:黎活明 北京传智 播客教育 www.itcast.c ...

  5. Java Android几个重要的基础知识

    为什么80%的码农都做不了架构师?>>>    Java 1.数据类型 bit(位):0或1计算机存储处理信息的最基本的单位 byte(字节):8个bit(上面表格数字的单位是byt ...

  6. Android技能树 — 树基础知识小结(一)

    前言: 现在安卓面试,对于数据结构的问题也越来越多了,也经常看到别人发的面试题都是问什么红黑树,二叉树查找等,所以我们虽然不会马上就会各种难的面试题,但起码树的基础知识还是要会的,这样才能去进一步学. ...

  7. Android车载应用开发与分析(6)- 车载多媒体(一)- 音视频基础知识与MediaPlayer

    多媒体应用是车载信息娱乐系统的一个重要组成部分,一般包含音视频播放.收音机.相册等.车载应用多媒体系列初步计划分为六篇,这是第一篇. 参考资料 视频和视频帧:视频和帧基础知识整理 百度百科 - 声道 ...

  8. Android 并发/多线程 的基础与应用

    本篇文章主要目的为总结 覆盖80%场景的20% Android端并发所需基础知识和应用. Android 端应用主要使用 Java 语言开发,所以基础与 Java 的并发基础基本一样,深入了解推荐细读 ...

  9. linux下tc、htb、iptables基础知识及openwrt 下qos使用介绍

    htb基础知识:Linux Htb队列规定指南中文版:http://wenku.baidu.com/view/64da046825c52cc58bd6beac.html TC基础知识: Linux 的 ...

  10. Android自定义view之基础知识

    Android自定义view之基础知识 虽然Android已经自带了很多实用的view和layout,加以调教能实现很美观的界面,但是有一些情况下,需要实现特殊的界面效果,比如我们比较熟悉的各种播放器 ...

最新文章

  1. 包含近 20 万本图书,OpenAI 级别的训练数据集上线
  2. c3p0数据库连接池的使用详解
  3. Android系统所有版本源码Kernel源码
  4. jquery瀑布流布局和鼠标滚动加载
  5. EOS开发步骤(2) 钱包操作
  6. java本地可以发到linux不行,java 使用 ftp 在windows环境下可以正常下载文件,在linux环境下不行...
  7. IntelliJ IDEA 如何设置编辑窗口的背景图片
  8. Html5表单元素-搜索框和上传文件框
  9. element手机验证格式_vue封装 element-ui form表单验证 正则匹配手机号 自定义校验表格内容...
  10. Ubuntu 多版本Cuda(8.0,9.0)以及CuDnn安装
  11. 解决hibernate双向关系造成的一方重复执行SQl,或者死循环的问题
  12. 企业级分布式 HTAP 数据库管理系统,腾讯 TBase 正式开源 ​
  13. three.js glb 多个_直降7.1万元 奔驰GLB开始“大甩卖”
  14. Matlab Tricks(十九)—— 序列左右移的实现
  15. Android:Intent传递数据
  16. 下载安装tomcat和jdk,配置运行环境,与Intellij idea 2017关联
  17. 实现isodd() 参数为整数,如果整数为基数,返回True 否则返回False
  18. git branch -vv
  19. 浙江大学软件学院2020年保研真题Distance of Triples (25 分)
  20. 第23天:如何使用带有哈利·波特PortKey的ARKit和Unity构建应用程序

热门文章

  1. 扔鸡蛋问题-方程-动态规划
  2. 组合泛化太难?试试解析式学习,100%准确率!
  3. Vue中使用svg(图片不显示问题)
  4. 【数据分析】全球医疗卫生开放数据概览
  5. 南京计算机与通信工程学院,谢胜东(南京信息工程大学计算机与软件学院讲师)_百度百科...
  6. Mybatis中TypeHandler的简单应用
  7. 华云数据蝉联中国大数据50强 成为中国大数据产业生态联盟理事单位
  8. 子类不能继承或覆盖父类的private方法
  9. C# 将两个DataTable合并
  10. 2008年下半年软件水平考试程序员试题分析