第八天事件分发及案例

前言

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;
            }
        });

    }
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
下面是一个简单的案例,演示如何使用全局事件分发器和局部事件分发器。 假设我们有一个主窗口 MainWindow,其中包含一个按钮 button。我们想要在用户点击按钮时执行一些操作,同时也想要在用户在窗口中移动鼠标时输出鼠标坐标。 ```python from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton from PyQt5.QtCore import QEvent, Qt class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('事件分发器示例') self.setGeometry(100, 100, 400, 300) # 创建一个按钮 self.button = QPushButton('按钮', self) self.button.setGeometry(100, 100, 100, 50) # 将按钮的点击事件连接到槽函数 self.button.clicked.connect(self.on_button_clicked) # 安装全局事件过滤器 qapp = QApplication.instance() qapp.installEventFilter(self) def on_button_clicked(self): print('按钮被点击了!') def eventFilter(self, obj, event): if event.type() == QEvent.MouseMove and obj == self: print('鼠标移动到了坐标:', event.pos()) return super().eventFilter(obj, event) if __name__ == '__main__': app = QApplication([]) window = MainWindow() window.show() app.exec_() ``` 在上面的代码中,我们创建了一个 MainWindow 窗口,其中包含一个按钮 button。我们将按钮的点击事件连接到 on\_button\_clicked 槽函数中。 我们还安装了一个全局事件过滤器,以便在应用程序中捕获所有事件。在事件过滤器中,我们检查了事件类型是否为鼠标移动事件,并且检查了事件对象是否为 MainWindow 窗口。如果是,则输出鼠标坐标。 当用户在窗口中移动鼠标时,我们可以看到鼠标坐标被输出到控制台。当用户点击按钮时,我们也可以看到按钮被点击了的消息被输出到控制台。这说明我们成功地使用了全局事件分发器和局部事件分发器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

馮贰爺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值