前言
View是所有可视化空间的父类,系统提供了很多基础控件,比如Button,TextView等,但是仅仅使用他们是完全不能满足需求的,因此我们就需要自定义控件,而自定义控件,就需要对Android的View体系有充分的了解。有一个经典场景就是屏幕的滑动,当处于不同层级的View都响应用户滑动,就会导致滑动冲突。为了正常的响应滑动事件,我们就要对View的事件分发机制有充分的了解。
View基础知识
我们先从实践出发,了解View的一些功能。再深入去了解他在复杂场景的下的使用方法。
View的子类
View是Android中所有控件的父类,除了View以外,还有一个View的子类叫做ViewGroup,从名字上看,表示控件组,这意味着View不单单指一个,他也可以是一组控件。
所以Button是一个View,不是ViewGroup,而LinearLayout既是View又是ViewGroup。并且ViewGroup的内部,既可以有View,也可以有ViewGroup。
View的位置
View的位置由四个顶点决定,分别是bottom,top,left,right。这些坐标都是相对于View的父容器而言,因此它是相对坐标。
我们可以使用getLeft(),getRight(),getTop(),getBottom()来获得View的位置。
Android3额外增加了几个参数:x、y、translationX、translationY。换算关系为 x = left + translationX。(需要实践)
View 在平移的过程中,top 和 left 表示的是原始左上角的位置信息,其值并不会发生改变,此时发生改变的是x、y、translationX和 translationY这四个参数。
View的触碰事件
1、MotionEvent
ACTION_DOWN–手指刚解除屏幕
ACTION_MOVE–手指在屏幕上移动
ACTIONUP — 手指松开一瞬间
一般有手机点击和手机滑动松开的两种情况。对应的为down-up , down-move-up。
视图类中重写onTouchEvent方法
查看代码
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("Action",""+event.getAction());
float x = event.getX();//带event才是触摸点坐标,不带为view坐标,并且X是相对view的坐标,view的左上角为0,0。只有rawX才是整个屏幕的
float y = event.getY();
Log.d("public button",x+" "+y);
return true;
}
2、 TouchSlop
TouchSlop是系统所能识别为滑动的最小距离,不同设备不同。是一个常量
ViewConfiguration.get(getContextO).getScaledTouchSlop()
3、VelocityTracker
速度追踪,能够识别在一段时间内,手指滑动的速度
视图类中重写onTouchEvent方法
查看代码
@Override
public boolean onTouchEvent(MotionEvent event) {
VelocityTracker velocityTracker= VelocityTracker.obtain();
velocityTracker.addMovement (event);
velocityTracker.computeCurrentVelocity (1000);
int xVelocity =(int) velocityTracker.getXVelocity();
int yVelocity =(int) velocityTracker.getYVelocity();
Log.d("speed",xVelocity+" "+yVelocity);
return true;
}
4、GestureDetector
手势检测。可以检测长按,轻触,单击,双击等行为。需要先创建一个监听器对象GestureDetector.OnGestureListener,然后创建GestureDetector时传入监听器,再为视图设置TouchEvent监听器,在onTouch事件中使用GestureDetector接管即可
实例
GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
Log.d("down","down");
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
};
LinearLayout ll = findViewById(R.id.MA_ll);
GestureDetector gestureDetector= new GestureDetector(this,listener);
ll.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
5、Scroller
弹性滑动,实现组件的缓慢移动
View的滑动
视图手指滑动
查看代码
@Override
public boolean onTouchEvent(MotionEvent event) {
//每次回调onTouchEvent的时候,我们获取触摸点的代码
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 在当前left、top、right、bottom的基础上加上偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
break;
}
return true;
}
Scroll滑动
被谁捕获就移动谁
查看代码
public PublicButton1(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.button1,this);//加载某个布局,并为他添加父布局
Button qwq = findViewById(R.id.publicButton);
LinearLayout linearLayout = findViewById(R.id.linearlayout);
linearLayout.setBackgroundColor(getResources().getColor(R.color.design_default_color_secondary));
qwq.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
scrollBy(-10,-10);
}
});
scrollBy(-100,-100);
}
动画滑动
待定
弹性滑动
Scroller实现,需要重写computeScroll方法
查看代码
private void smoothScrollTo(int destX,int destY) {
int scrollX = getScrollX();
int deltaX = destX - scrollX;
scroller.startScroll(scrollX, 0, deltaX, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if(scroller.computeScrollOffset())
{
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();
}
}
View的事件分发机制
点击事件的传递规则
分析的对象是MotionEvent,所谓对点击事件的分发,就是对MotionEvent的分发,系统需要把这个事件发给具体的一个View。
分发过程由三个方法共同完成:dispatchTouchEvent、onlnterceptTouchEvent和 onTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View 的 onTouchEvent 和下级 View 的 dispatchTouchEvent 方法的影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent event)
在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
public boolean onTouchEvent(MotionEvent event)
在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View 无法再次接收到事件。
他们的关系可以用以下伪代码表示
查看代码
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent就会被调用,
如果这个ViewGroup 的 onInterceptTouchEvent方法返回 true 就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用;
如果这个ViewGroup 的onInterceptTouchEvent方法返回 false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,
接着子元素的dispatchTouchEvent 方法就会被调用,如此反复直到事件被最终处理。
当一个View 需要处理事件时,
如果它设置了 OnTouchListener,那么OnTouchListener中的 onTouch方法会被回调。
这时事件如何处理还要看 onTouch 的返回值,
如果返回 false,则当前View的 onTouchEvent 方法会被调用;
如果返回 true,那么 onTouchEvent 方法将不会被调用。
可以把OnTouchListener理解为外部监听器,而onTouchEvent理解为内部监听器。onTouchListener在外部用set绑定,而onTouchEvent是重写组件方法实现的。
在 onTouchEvent 方法中,如果当前设置的有OnClickListener,那么它的 onClick 方法会被调用。可以看出,平时我们常用的 OnClickListener,其优先级最低,即处于事件传递的尾端。
当一个点击事件产生后,它的传递过程遵循如下顺序∶Activity->Window->View,顶级 View 接收到事件后,就会按照事件分发机制去分发事件。
从上到下(点击事件),再从下到上(onTouchEvent)
原创文章,作者:dweifng,如若转载,请注明出处:https://blog.ytso.com/273296.html