前言
View事件分发
在Android中,View无处不在,不管是一个普通的视图,还是一个复杂的布局,都是依靠View来实现。而View中的事件分发和处理,是其核心知识点之一,也是开发者学习View时的难点。
在分析事件处理前,我们需要明白,android中一个完整的手势(gesture)包括如下4个操作:
DOWN: 当用户手指按下时
MOVE: 当用户开始滑动时
UP: 用户抬起手指
CANCEL: 取消操作,事件被无法到达时
任何一个手势都需要从DOWN开始。
在事件分发中,我们需要区分View和ViewGroup,虽然后者也是继承与View,但是ViewGroup重写了dispatchTouchEvent()方法,同时,也只有在ViewGroup会处理onInterceptTouchEvent()方法,而一个事件分发过程,是从一个ViewGroup开始,在这过程中我们需要了解以下三个重要的方法:
dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()
注意:子View没有 onInterceptTouchEvent,因为 子View已经是最底层了,不用在向下传递,只有 ViewGroup 有 onInterceptTouchEvent方法;
总结:
1>:dispatchTouchEvent:
返回super:调用 子View 的 dispatchTouchEvent,如果没有 子View,调用自己的 onTouchEvent;
返回 true:自己消费事件,并且结束 事件传递,且所有的 onTouchEvent都不会执行(View的、ViewGroup的、Activity的onTouchEvent都不会执行);
返回 false: 自己不处理事件,结束事件传递,不调用自己的 onTouchvent,而是直接调用 父控件的 onTouchEvent;
2>:onInterceptTouchEvent:
返回true:自己拦截事件,然后调用自己的 onTouchEvent,不会向下传递事件;
返回super/false:自己不拦截事件,继续向下传递事件;
3>:onTouchEvent:
返回true:自己消费事件,事件结束 传递;
返回super/false:自己不处理事件,事件向上传递;
图例执行说明:
方法说明
//分发
//super.dispatchTouchEvent(ev) --> 将事件向上传递 --> 交给父容器 处理是否分发
//true --> 不向下分发 --> 不向上传递 --> 我自己来处理所有事件
//false --> 不向下分发 --> 向上传递 --> 完成父容器的事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("GAO", "CustomView-dispatchTouchEvent");
return false;
}
//拦截
//return true 进行拦截 --> 不向下给子控件传递事件
//return false 不进行拦截
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("GAO", "CustomView-onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
//所有触摸事件
//按下
//移动
//抬起
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("GAO", "CustomView-onTouchEvent");
return super.onTouchEvent(event);
}
解说
a. 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
b. ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
c. ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
d. View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
下拉刷新实际实际案例
package com.fenghongzhang.exam;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class FragmentRefreshLayout extends FrameLayout {
private LinearLayout mRefreshLayout;
private int mMaxRefreshHeight = 200;
private PointF mBeforePointF;
public FragmentRefreshLayout(@NonNull Context context) {
super(context);
initView();
initData();
}
public FragmentRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
initData();
}
private void initView() {
mRefreshLayout = new LinearLayout(getContext());
LayoutParams refreshParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, mMaxRefreshHeight);
refreshParams.topMargin = -mMaxRefreshHeight;
mRefreshLayout.setLayoutParams(refreshParams);
mRefreshLayout.setBackgroundColor(Color.RED);
addView(mRefreshLayout);
}
private void initData() {
mBeforePointF = new PointF();
}
//分发事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mBeforePointF.x = ev.getX();
mBeforePointF.y = ev.getY();
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
//偏移量
float xDimension = Math.abs(ev.getX() - mBeforePointF.x);
float yDimension = Math.abs(ev.getY() - mBeforePointF.y);
//判读滑动方向
if (xDimension > yDimension * 2) {
//X轴滑动
return super.dispatchTouchEvent(ev);
}
//最小响应距离
if (yDimension < 5) {
return super.dispatchTouchEvent(ev);
}
//可滑动范围
//Main下拉
FrameLayout.LayoutParams mainParams = (LayoutParams) mRefreshLayout.getLayoutParams();
mainParams.topMargin += ev.getY() - mBeforePointF.y;
if (mainParams.topMargin > 0) {
mainParams.topMargin = 0;
}
//
if (mainParams.topMargin < -mMaxRefreshHeight) {
mainParams.topMargin = -mMaxRefreshHeight;
}
mRefreshLayout.setLayoutParams(mainParams);
mBeforePointF.x = ev.getX();
mBeforePointF.y = ev.getY();
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
//当滑动位置,打开或关闭
LayoutParams mainParams = (LayoutParams) mRefreshLayout.getLayoutParams();
if (mainParams.topMargin < -mMaxRefreshHeight / 2 ) {
startAnim(mainParams.topMargin, -mMaxRefreshHeight);
} else {
startAnim(mainParams.topMargin, 0);
}
}
return true;
}
private void startAnim(int start, int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end);
valueAnimator.setDuration(500);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
FrameLayout.LayoutParams mainParams = (LayoutParams) mRefreshLayout.getLayoutParams();
mainParams.topMargin = (int) animation.getAnimatedValue();
mRefreshLayout.setLayoutParams(mainParams);
// requestLayout();
}
});
valueAnimator.start();
}
}
实例滑动冲突
https://www.jianshu.com/p/982a83271327
布局
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ss"
android:scrollbars="vertical"
>
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="texttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttext"
android:textSize="50dp"
android:id="@+id/text"></TextView>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lv"></ListView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="texttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttext"
android:textSize="50dp"
android:id="@+id/text1"></TextView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="texttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttext"
android:textSize="50dp"
android:id="@+id/text2"></TextView>
</LinearLayout>
</ScrollView>
</LinearLayout>
代码
package com.fenghongzhang.test;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.lv);
List<String> list = new ArrayList<>();
list.add("diyige1");
list.add("diyige2");
list.add("diyige3");
list.add("diyige4");
list.add("diyige");
list.add("diyige");
list.add("diyige");
list.add("diyige");
list.add("diyige");
list.add("diyige");
//解决listView的高度问题
ViewGroup.LayoutParams layoutParams = lv.getLayoutParams();
layoutParams.height = 300;
lv.setLayoutParams(layoutParams);
lv.setAdapter(new MyAdapter(list,this));
lv.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//不允许父层拦截或干扰本控件
lv.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
}
}