View基础知识
- 什么是View
- View的位置参数
- MotionEvent和TouchSlop
- VelocityTracker,GestureDetector和Scroller
一、View的基础知识
本节主要介绍View的一些基础知识,从而为更好地介绍后续的内容做铺差,主要介绍的内容有:View的位置参数、MotionEvent和TouchSlop、VelocityTracker,GestureDetector和Scroller对象,通过对这些基础知识的介绍,可以方便读者理解更复杂的内容。类似的基础概念还有不少,但是本节所介绍的都是一些比较常用的,其他的自行理解。
1.什么是View
在介绍View的基础知识之前,我们首先要知道到底什么是View。View是Android中所有控件的基类,不光是简单的Button和TextView还是复杂的RelativeLayout和Listview,它们的共同基类都是View。所以说,View是一种界面层的控件的一种抽象,它代表了一个控件,除了View,还有ViewGroup,ViewGroup内部包含了许多个控件,即一组View。在Android的设计中,ViewGroup也继承了View,这就意味着View本身就可以是单个控件也可以是由多个控件组成的一组控件,通过这种关系就形成了View树的结构,这和Web前端中的DOM树的概念是相似的。根据这个概念,我们知道,Button显然是个View,而LinearLayout不但是一个View而且还是一个ViewGroup,而ViewGroup内部是可以有子View的,这个子View同样还可以是ViewGroup
2.View的位置参数
View的位置主要由它的四个顶点来决定,分别对应于View的四个属性:top、left、right,bottom,其中top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是有下角纵坐标。需要注意的是,这些坐标都是相对于View的父容器来说的,因此它是一种相对坐标,View的坐标和父容器的关系如图3所示。在Android中,x轴和y轴的正别为右和下,这点不难理解,不仅仅是Android,大部分显示系统都是按照这个标准来定义坐标系的。
从图中的关系我们很容易得到宽高的关系
width = right- left
height = bottom - top
那么如何得到View的四个参数呢?也很简单,View的源码中它们对应于mLeft、mRight、mTop、mBottom这4个成员变量。获取的方式如下:
Left = getLeft();
Right = getRight();
Top = getTop();
Bottom = getBottom():
从Android3.0开始,View增加了额外的几个参数,x,y,translationX,translationY,其中x,y是View左上角的坐标,而translationX,translationY是左上角相对父容器的偏移量。这几个参数也是相对于父容器的坐标,并且translationX,translationY的默认值是0,和View的四个基本位置参数一样,View也为我们提供了get/set方法,这几个参数换算关系如下:
x = left + translationX
y = top + translationY
需要注意的是,View在平移的过程中,top和left表示在原始左上角的位置信息,其值并不会发生改变,此时发生改变的是x,y,translationX,translationY,这四个参数。
3.MotionEvent和TouchSlop
源码在线阅读地址:http://androidxref.com/9.0.0_r3/
4、VelocityTracker,GestureDetector和Scroller
a.VelocityTracker
速度追踪,用于追踪手指在屏幕上滑动的速度,包括水平和竖直方向上的速度使用过程很简单,首先,在View的onTouchEvent方法里追踪
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
接着,当我们先知道当前的滑动速度时,这个时候可以采用如下的方式得到当前的速度
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
在这一步中有两点需要注意,第一点获取速度的之前必须先计算速度,即getXVelocity和getYVelocity这两个方法前面一定要调用computeCurrentVelocity方法;第二点,这里的速度是指一段时间内手指滑动的屏幕像素数,比如将时间设置为1000ms时,在1s内,手指在水平方向手指滑动100像素,那么水平速度就是100,注意速度可以为负数,当手指从右向左滑动的时候为负,这个需要理解一下,速度的计算公式如下表示
速度 = (终点位置 - 起点位置)/时间段
根据上面的公式再加上Android系统的坐标系,可以知道,手指逆着坐标系的正方向滑动,
所产生的速度为负值。另外,computeCurrentVelocity这个方法的参数表示的是在一个时间单元或者说时间间隔,单位是毫秒,计算速度时得到的速度就是在这个时间间隔内手指水平方向所滑动的像素数,针对上面的例子,如何通过velocityTracker.computeCurrentVelocity(100)来获取速度,那么得到的速度就是在100ms内的像素数,所以水平速度就成了10像素100ms,这里假设是匀速,即水平速度为10。
最后,当不需要使用它的时候,需要调用clear方法来重置并回收内存:
velocityTracker.clear();
velocityTracker.recycle();
上面就是如何使用velocityTracker对象的全过程,看起来并不复杂
b.GestureDetector
下面演示一个实例
测试GestureDetector各种回调的activity
package com.example.testgesturedetector;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import androidx.appcompat.app.AppCompatActivity;
public class TestGestureDetectorActivity extends AppCompatActivity {
private static final String TAG = "My_TestGestureDetector";
private LinearLayout ll_test;
GestureDetector mGestureDetector ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_gesture_detector);
ll_test = findViewById(R.id.ll_test);
mGestureDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
Log.d(TAG,"onDown,返回为true这样后面的滑动事件才能监听到");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.d(TAG,"onSingleTapUp(单击),开发常用");
Log.d(TAG,"测试ll_test的scrollTo方法,实际测试OK,瞬间移动");
ll_test.scrollTo(ll_test.getScrollX()-20,ll_test.getScrollY()-20);
ll_test.invalidate();
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.d(TAG,"onScroll(拖动)");
return false;
}
@Override
public void onLongPress(MotionEvent e) {
Log.d(TAG,"onLongPress(长按),开发常用,需要注意的是不能【mGestureDetector.setIsLongpressEnabled(false)】,但不设置双击又监听不到,看个人取舍");
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d(TAG,"onFling(快速滑动),开发常用");
return false;
}
});
mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.d(TAG,"onSingleTapConfirmed");
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.d(TAG,"onDoubleTap(双击),开发常用,注意需要将【mGestureDetector.setIsLongpressEnabled(false)】才能响应");
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.d(TAG,"onDoubleTapEvent,滑动的时候也会回调");
return false;
}
});
mGestureDetector.setIsLongpressEnabled(false);
Log.d(TAG,"给ll_test设置手势检测,直接将event交给mGestureDetector的onTouchEvent方法处理");
ll_test.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// return true;
return mGestureDetector.onTouchEvent(event);
}
});
}
}
xml配置
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#ffffff"
>
<LinearLayout
android:id="@+id/ll_test"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#0000ff"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="test"
android:textColor="#ffffff" />
</LinearLayout>
</LinearLayout>
我们点击、拖拽、双击多可以看到回调的打印。
c.Scroller
Scroller scroller = new Scroller(getContext());
private void smoothScrollTo(int destX,int destY){
int scrollX = getScrollX();
int delta = destX - scrollX;
//1000ms内滑向destX,效果就是慢慢的滑动
scroller.startScroll(scrollX,0,delta,0,1000);
invalidate();
}
@Override
public void computeScroll() {
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();
}
}
这里基础的知识介绍完成,后面了解下view的滑动。