Android开发艺术笔记 | View的事件分发机制原理详析与源码分析(ing)

本文主要是介绍Android开发艺术笔记 | View的事件分发机制原理详析与源码分析(ing),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原理解析

  • 这里要分析的对象就是MotionEvent,即点击事件
    点击事件事件分发,本质是对MotionEvent事件分发过程
    即,
    当一个MotionEvent产生了以后,
    系统需要把这个事件传递给一个具体的View
    而这个传递的过程就是分发过程

分发与拦截

  • 点击事件的分发过程由三个重要方法共同完成:dispatchTouchEventonInterceptTouchEventonTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev)
  • 用来进行事件的分发传递。
  • 如果事件能够传递给当前View,那么此方法一定会被调用,
  • 返回值是boolean类型,
    返回结果受当前ViewonTouchEvent
    下级ViewdispatchTouchEvent方法的影响;
  • 表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent event)
  • dispatchTouchEvent()内部调用,用来判断是否拦截某个事件;
  • 如果当前View拦截了某个事件,那么在同一个事件序列当中,
    此方法不会被再次调用
  • 返回结果表示是否拦截当前事件
  • 该方法只在ViewGroup中有,View(不包含 ViewGroup)是没有的。
  • 一旦拦截,
    则执行ViewGroup的onTouchEvent,
    在ViewGroup中处理事件,而不接着分发给View。
  • 且只调用一次,所以后面的事件都会交给ViewGroup处理。
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;}
  • 通过以上伪代码,可以大致了解点击事件在View层传递规则
    • 对于一个根ViewGroup来说,
      点击事件产生后,首先会传递给它,
      这时其dispatchTouchEvent会被调用;

    • 如果这个ViewGroup的onInterceptTouchEvent方法
      返回true就表示它要拦截当前事件,
      接着事件就会交给这个ViewGroup处理,
      即它的onTouchEvent方法就会被调用;!!!

    • 如果这个ViewGroup的onInterceptTouchEvent方法
      返回false就表示它不拦截当前事件,
      这时当前事件就会继续传递给它的子元素
      接着子元素dispatchTouchEvent方法就会被调用
      如此反复直到事件被最终处理。

  • 即,
    接收到事件 --> 分发 --> 是否拦截
    --> 拦截则就地处理【ViewGroup/View:调用自身onTouch()-->onTouchEvent() -->performClick() --> onClick()】!!!,
    否则继续往下传!

这里可以看一下文末的两篇博客!

9125154-178a9e6a54910761.png

事件处理

  • 当一个View需要处理事件时,
    如果它设置了OnTouchListener
    OnTouchListener中的onTouch方法会被回调;

  • 这时事件如何处理还要看onTouch返回值

    • 如果返回false,【事件不消费,继续往下传递】
      当前ViewonTouchEvent方法会被调用,
      接着是performClick() --> onClick()被调用;
      然后
      它的父容器的onTouchEvent将会被调用,
      依此类推。

      【注意这里跟onInterceptTouchEvent不一样,
      onInterceptTouchEvent仅在ViewGroup级,
      true表拦截处理,调用ViewGroup自身的onTouch()-->onTouchEvent()
      onTouch在View级时候,
      false继续流程,调用View自身的onTouchEvent()

    • 如果返回true,【事件被消费】
      那么onTouchEvent方法将不会被调用。

  • 由此可见,
    给View设置的OnTouchListener,其优先级比onTouchEvent要高。
    onTouchEvent方法中,
    如果当前设置的有OnClickListener,那么它的onClick方法会被调用。
    而常用的OnClickListener,其优先级最低,即处于事件传递的尾端。

优先级:onTouch()-->onTouchEvent() -->performClick() --> onClick()

以上是事件处理方法的优先级顺序,按照这个顺序,
只要排在前面事件方法返回true消耗处理点击事件了,
点击事件便就地结束,不再下发,
排在后面点击事件也就不会再被调用和响应了;
【文末有实例】


另,
onTouch()的实现需要实现onTouchListener
onTouchEvent()/performClick()直接在自定义View文件中重写即可;
onClick()的实现需要实现onClick

  • 当一个点击事件产生后,
    其传递过程顺序:Activity -> Window -> 顶级View(上述说的表示View层中的顺序);

    9125154-1b90a505d7877583.png

  • 顶级View接收到事件后,就会按照事件分发机制去分发事件。

  • 如果一个View的onTouchEvent返回false
    那么它的父容器的onTouchEvent将会被调用,
    依此类推。
    【除非下往上回传到某个返回true的onTouchEvent(),
    则在那里停止,否则——】

  • 如果所有的元素都不处理这个事件,
    那么这个事件将会最终传递给Activity处理,
    ActivityonTouchEvent方法会被调用。

  • 形象地举个例子,
    假如点击事件是一个难题,
    这个难题最终被上级领导分给了一个程序员去处理(类似事件分发过程),
    结果这个程序员搞不定(onTouchEvent返回了false),
    但难题必须要解决,
    那只能交给水平更高的上级解决(上级的onTouchEvent被调用),
    如果上级再搞不定,那只能交给上级的上级去解决,
    就这样将难题一层层地向上抛。
    【即一个从上到下(分发传递),再从下到上的过程(onTouchEvent(),
    例见事件拦截机制大概流程(Android群英传)中的图例】

关于事件传递机制的一些结论(每一个点前面的短语是一个笔者自提的概况中心,便于记忆)

根据它们可以更好地理解整个传递机制:
(1)【事件序列,定义】
同一个事件序列” 的定义:
指从手指接触屏幕的那一刻
到手指离开屏幕的那一刻结束
在这个过程中所产生的一系列事件,
这个事件序列以down事件开始,
中间含有数量不定move事件,
最终以up事件结束。

9125154-6fc64c8abd12c3c6.png

(2)【处理事件,独一无二】
正常情况下,一个事件序列只能被一个View拦截且消耗!!!
这一条的原因可以参考(3),
因为一旦一个元素拦截了某此事件,
那么同一个事件序列内所有事件都会直接交给它处理!!!
因此同一个事件序列中事件不能分别由两个View同时处理!!!
除非,
将本该由某个View自己处理的事件
通过onTouchEvent强行传递给其他View处理。


(3)【事件序列,从一而终】
某个View一旦决定拦截,则这一个事件序列都只能由它来处理
(如果事件序列能够传递给它的话),
并且它的onInterceptTouchEvent不会再被调用!!!
当一个View决定拦截一个事件后,
那么系统会把同一个事件序列内其他方法都直接交给它来处理,
因此
就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。

(4)【短期失信】
某个View一旦开始处理事件
如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),
那么同一事件序列中其他事件都不会再交给它来处理,
【即,View放弃处理ACTION_DOWN,便放弃了整个事件序列!!!】
并且事件将重新交由它的父元素去处理,
即父元素的onTouchEvent会被调用。【事件向上“回传”】
即,
事件一旦交给一个View处理,那么它就必须消耗掉!!!
否则同一事件序列中剩下的事件就不再交给它来处理了!!!
好比上级交给程序员一件事,如果这件事没有处理好,
短期内上级就不敢再把事情交给这个程序员做。

(5)【余粮上缴】
如果View不消耗除ACTION_DOWN以外的其他事件,
那么这个点击事件会消失,
此时父元素的onTouchEvent并不会被调用,
并且当前View可以持续收到后续的事件,
最终这些消失的点击事件会传递给Activity处理。

(6)ViewGroup默认不拦截任何事件。
Android源码中
ViewGroup的onInterceptTouch-Event方法默认返回false

(7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。

(8)View的onTouchEvent默认都会消耗事件(返回true)!!!!!!!
除非它是不可点击的(clickablelongClickable同时为false)。
View的longClickable属性默认都为false
clickable属性要分情况,
比如Button的clickable属性默认为true
TextView的clickable属性默认为false

(9)【enable无用,clickable居上】
View的enable属性不影响onTouchEvent默认返回值。哪怕一个View是disable状态的!!!!!
只要它的clickable或者longClickable有一个为true
那么它的onTouchEvent就返回true!!!

(10)onClick会发生的前提是当前View是可点击的,并且它收到了downup的事件。

(11)【由外而内;以下犯上】
事件传递过程是由外向内的,
即事件总是先传递给父元素,然后再由父元素分发给子View
通过requestDisallowInterceptTouchEvent方法可以在子元素干预父元素事件分发过程,但是ACTION_DOWN事件除外。

稍微复习一下:
事件方法的优先级:onTouch()-->onTouchEvent() -->performClick() --> onClick()


以上是事件处理方法的优先级顺序,按照这个顺序,
只要排在前面事件方法返回true消耗处理点击事件了,
点击事件便就地结束,不再下发,
排在后面点击事件也就不会再被调用和响应了;



下面是关于事件优先级的一个实例:

public class DragView3 extends View implements View.OnClickListener {private int lastX;private int lastY;public DragView3(Context context) {super(context);ininView();}public DragView3(Context context, AttributeSet attrs) {super(context, attrs);ininView();}public DragView3(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);ininView();}private void ininView() {setBackgroundColor(Color.BLUE);this.setOnClickListener(this);//测试onTouchEvent与onClick的优先级!!}@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// 记录触摸点坐标lastX = (int) event.getX();lastY = (int) event.getY();break;case MotionEvent.ACTION_MOVE:// 计算偏移量int offsetX = x - lastX;int offsetY = y - lastY;ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
//                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();layoutParams.leftMargin = getLeft() + offsetX;layoutParams.topMargin = getTop() + offsetY;setLayoutParams(layoutParams);break;}return true;}//测试onTouchEvent与onClick的优先级!!@Overridepublic void onClick(View v) {setBackgroundColor(Color.RED);}
}
  • 如上代码,
    • 给自定义View配置了onClick监听器
      如果onClick响应,点击View之后会从蓝色变成红色
      但是运行之后我们发现并没有变色,即onClick没有被调用;
      View响应的只是onTouchEvent中的滑动逻辑而已。
      (下面图一)

    • 这是因为onTouchEvent返回true,把事件消耗掉了!!
      于是事件在onTouchEvent处理结束,不再往下传,传不到onClick那里!!!

    • 如果,
      将以上代码中的onTouchEvent注释掉,
      使之默认返回false,不消耗事件,这时onClick则会响应!
      那么再次运行程序,可以发现点击View之后,
      View从蓝色变成红色!!!
      (下面图二)

  • 由此,事件处理方法优先级不言而喻!
    9125154-eb550ca9392a9e05.png
    图一
    9125154-af3932cec32b86f0.png
    图二

小结

  1. 三个关键方法:dispatchTouchEventonInterceptTouchEventonTouchEvent;分别的作用和关系;
  2. 分发与拦截,是一个依据分发顺序从上往下的过程!!!!!
    逻辑骨架就是,
    接收到事件 --> 分发 --> 是否拦截
    --> 拦截则就地处理【ViewGroup/View:调用自身onTouch()-->onTouchEvent() -->performClick() --> onClick()】!!!,
    否则继续往下传,传到最下层的View为止,接着进入处理过程!
    分发的顺序是Activity -> Window(PhoneWindow) -> DecorView -> 顶级View(上述说的表示View层中的顺序) -> ViewGroup -> View

    这里可以看一下文末的两篇博客!
    9125154-178a9e6a54910761.png
  3. 事件的处理则是分发的“回溯”,!!!!!
    顺序与分发相反,是一个从下到上的过程,
    最下层的View开始到最上层(即Activity),
    如果所有元素都不消耗这个事件,事件最终就传回Activity;
    消耗指onTouch、onTouchEvent、onClick等;









源码分析

  • 上面说了,
    Android事件分发流程: Activity -> ViewGroup -> View;

  • 所以,想充分理解Android分发机制,本质上是要理解:
  1. Activity对点击事件的分发过程
  2. ViewGroup对点击事件的分发过程
  3. View对点击事件的分发过程

Activity对点击事件的分发过程

  • 点击事件MotionEvent来表示,
    当一个点击操作发生时,事件最先传递给当前Activity,
    由Activity的dispatchTouchEvent来进行事件派发,
    具体的工作是由Activity内部Window来完成的!!!!!!!!

  • Window会将事件传递给decor view
    decor view一般就是当前界面的底层容器(即setContentView所设置的View的父容器),
    通过Activity.getWindow.getDecorView()可以获得。

  • 先从Activity的dispatchTouchEvent开始,源码:

    public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}

如上,
首先事件开始交给Activity所附属的Window进行分发,如果返回true
整个事件循环就结束了:

if (getWindow().superDispatchTouchEvent(ev)) {return true;}

返回false意味着事件没有元素处理,
所有View的onTouchEvent都返回了false,
那么Activity的onTouchEvent就会被调用。

return onTouchEvent(ev);
  • 接下来看Window是如何将事件传递给ViewGroup的;
    Window是个抽象类!!!
    WindowsuperDispatchTouchEvent方法也是个抽象方法!!!
    因此我们必须找到Window的实现类才行。源码:
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

  • Window的实现类其实是PhoneWindow
    这一点从Window的源码中有这么一段话:

Abstract base class for a top-level window look and behavior policy. 
An instance of this class should be used as the top-level view added to 
the window manager. It provides standard UI policies such as a background, title area, 
default key processing, etc.
The only existing implementation of this abstract class is android. policy. 
PhoneWindow,which you should instantiate when needing a Window. 
Eventually that class will be refactored and a factory method added for creating 
Window instances without knowing about a particular implementation.
  • 大概是说,

    • Window类可以控制顶级View外观行为策略!!!
    • 它的唯一实现位于android.policy.PhoneWindow中!!!
    • 当你要实例化这个Window类的时候,
      你并不知道它的节,因为这个类会被重构
      只有一个工厂方法可以使用。
  • 所以可以看下android.policy.PhoneWindow
    尽管实例化的时候此类会被重构,仅是重构而已,功能是类似的。

  • 由于Window的唯一实现是PhoneWindow
    接下来看PhoneWindow是如何处理点击事件的,PhoneWindow.superDispatchTouchEvent源码:

    public boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}
  • 可以清楚看到,
    PhoneWindow将事件直接传递给了DecorView!!!!!!!!!!

  • DecorView是什么:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker// This is the top-level view of the window,containing the window decor.private DecorView mDecor;@Override
public final View getDecorView() {if (mDecor == null) {installDecor();}return mDecor;}
  • 通过((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0)可以获取Activity所设置的View!!!!!!!!
    这个mDecor就是getWindow().getDecorView()返回的View!!!
    而通过setContentView设置的View是它(DecorView mDecor)的一个子View【所谓顶级View】!!!

  • 至此,事件传递到了DecorView这儿,
    由于DecorView继承自FrameLayout且是父View
    所以最终事件会传递给View!!!
    从而应用响应点击事件!!

  • 从这里开始,
    事件已经传递到顶级View了,

    Activity中通过setContentView所设置的View
    另外顶级View也叫根View
    顶级View一般都是ViewGroup

9125154-80f3a22dc75d61a6.png

顶级View对点击事件的分发过程

  • 点击事件达到顶级View(一般是一个ViewGroup)以后,
    会调用ViewGroupdispatchTouchEvent方法,
    然后,
    如果顶级ViewGroup拦截事件即onInterceptTouchEvent返回true,
    则事件由ViewGroup处理,
    如果ViewGroup的mOnTouchListener被设置则onTouch会被调用,
    否则onTouchEvent会被调用。
    如果都提供的话,onTouch会屏蔽掉onTouchEvent。

  • 在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用!!!!!!!!!
    如果顶级ViewGroup不拦截事件,
    则事件会传递给它所在的点击事件链上的子View,
    这时子ViewdispatchTouchEvent会被调用。
    到此,事件已经从顶级View传递给了下一层View,接下来的传递过程和顶级View是一致的,如此循环,完成整个事件的分发。

以上是对原理部分的回顾;
下面开始顶级View的源码分析;

  • ViewGroup对点击事件的分发过程,
    其主要实现在ViewGroup的dispatchTouch-Event方法中,
    这个方法比较长,这里分段说明。

首先下面一段,描述当前View是否拦截点击事情这个逻辑。

    // Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}
  • 如上,
    • ViewGroup在如下两种情况下会判断是否要拦截当前事件:
      事件类型为ACTION_DOWN或者mFirstTouchTarget != null
      ACTION_DOWN事件好理解,那么mFirstTouchTarget != null是什么?

    • 这个从后面的代码逻辑可以看出来,
      当事件由ViewGroup的子元素成功处理时,
      mFirstTouchTarget会被赋值并指向子元素【于是 != null】,
      换种方式来说,
      当ViewGroup【不拦截事件并将事件交由子元素处理
      mFirstTouchTarget != null】。
      反过来,
      一旦事件由当前ViewGroup拦截时,
      mFirstTouchTarget != null就不成立。

    • 那么当ACTION_MOVE和ACTION_UP事件到来时,由于(actionMasked == MotionEvent. ACTION_DOWN || mFirstTouchTarget != null)这个条件为false,将导致ViewGroup的onInterceptTouchEvent不会再被调用,并且同一序列中的其他事件都会默认交给它处理。
      当然,这里有一种特殊情况,那就是FLAG_DISALLOW_INTERCEPT标记位,这个标记位是通过requestDisallowInterceptTouchEvent方法来设置的,一般用于子View中。FLAG_DISALLOW_INTERCEPT一旦设置后,ViewGroup将无法拦截除了ACTION_DOWN以外的其他点击事件。为什么说是除了ACTION_DOWN以外的其他事件呢?这是因为ViewGroup在分发事件时,如果是ACTION_DOWN就会重置FLAG_DISALLOW_INTERCEPT这个标记位,将导致子View中设置的这个标记位无效。因此,当面对ACTION_DOWN事件时,ViewGroup总是会调用自己的onInterceptTouchEvent方法来询问自己是否要拦截事件,这一点从源码中也可以看出来。在下面的代码中,ViewGroup会在ACTION_DOWN事件到来时做重置状态的操作,而在resetTouchState方法中会对FLAG_DISALLOW_INTERCEPT进行重置,因此子View调用request-DisallowInterceptTouchEvent方法并不能影响ViewGroup对ACTION_DOWN事件的处理。

...


参考:

  • 《Android开发艺术探索》
  • 《Android群英传》
  • Android事件分发机制详解(源码)!!!
  • 事件拦截机制大概流程(Android群英传)
  • 要点提炼|开发艺术之View
9125154-322dedfbbaf2eff2.png

这篇关于Android开发艺术笔记 | View的事件分发机制原理详析与源码分析(ing)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/406802

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和