IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android Touch事件分发(源码分析) -> 正文阅读

[移动开发]Android Touch事件分发(源码分析)

Android一文让你轻松搞定Touch事件分发?

源码分析

下面,咱们一起通过源码,全面解析事件分发机制,即按顺序讲解:

  • Activity事件分发机制

  • ViewGroup事件分发机制

  • View事件分发机制

Activity事件分发机制

????????Android事件分发机制首先会将点击事件传递到Activity中,具体是执行dispatchTouchEvent()进行事件分发。

Activity.dispatchTouchEvent()源码

/**
??*?创建人:帅次
??*?创建时间:2021/7/5
??*?功能:Activity.dispatchTouchEvent()
??*/
public?boolean?dispatchTouchEvent(MotionEvent?ev)?{
????if?(ev.getAction()?==?MotionEvent.ACTION_DOWN)?{
?//在这里仅用于ACTION_DOWN的判断
?onUserInteraction();
????}
????//返回true
????if?(getWindow().superDispatchTouchEvent(ev))?{
??//Activity.dispatchTouchEvent()就返回true,则方法结束。
???//该点击事件停止往下传递&事件传递过程结束(让爷爷吃了嘿嘿)
???return?true;
????}
????return?onTouchEvent(ev);
}

Activity.onUserInteraction()源码

/**
??*?创建人:帅次
??*?创建时间:2021/7/6
??*?功能:该方法是用户交互,每当向Activity分派按键、触摸或轨迹球事件时调用。
??*/
public?void?onUserInteraction()?{
}

Window.superDispatchTouchEvent()源码

/**
??*?创建人:帅次
??*?创建时间:2021/7/6
??*?功能:Window.superDispatchTouchEvent属于抽象方法。
??*?用于自定义窗口,如Dialog,传递触摸屏事件进一步向下视图层次结构。
??*?应用程序开发人员应该不需要实现或调用它。
??*/
public?abstract?boolean?superDispatchTouchEvent(MotionEvent?event);

因为Window是抽象类,咱就继续挖,就找到了它的唯一实现类「PhoneWindow」

PhoneWindow.superDispatchTouchEvent()源码

//?This?is?the?top-level?view?of?the?window,?containing?the?window?decor.
//这是窗口的顶层View的实例对象。?
private?DecorView?mDecor;
@Override
public?boolean?superDispatchTouchEvent(MotionEvent?event)?{
?//咱们继续往下看
?return?mDecor.superDispatchTouchEvent(event);
}

DecorView.superDispatchTouchEvent()源码

public?boolean?superDispatchTouchEvent(MotionEvent?event)?{
?//super调用父类dispatchTouchEvent方法。那它的父类是谁呢?
?//DecorView?extends?FrameLayout、FrameLayout?extends?ViewGroup!
?//从上面看出DecorView?是ViewGroup的间接子类。
?//看到这里Activity.dispatchTouchEvent()也基本差不多了
?//如果ViewGroup.dispatchTouchEvent?return?true,
?//则Activity.dispatchTouchEvent()return?true。
?//如果ViewGroup.dispatchTouchEvent?return?false,
?//则执行Activity.onTouchEvent(ev)。
?return?super.dispatchTouchEvent(event);
}

Activity.onTouchEvent()源码

/**
??*?创建人:帅次
??*?创建时间:2021/7/6
??*?功能:当Touch事件未被其下的任何View消费时调用。
??*/
public?boolean?onTouchEvent(MotionEvent?event)?{
????//Window.shouldCloseOnTouch来判断是否消费。
????//那咱就继续看看Window.shouldCloseOnTouch是干嘛的
????if?(mWindow.shouldCloseOnTouch(this,?event))?{
????????finish();
????????//已经消费了该事件,则返回true
????????return?true;
????}
????//还没有消费返回false,默认返回false
????return?false;
}

Window.shouldCloseOnTouch()源码

//这里支持的最高版本maxTargetSdk?=?Build.VERSION_CODES.P(28),咱使用SDK30就没办法处理了。
//主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
@UnsupportedAppUsage(maxTargetSdk?=?Build.VERSION_CODES.P,?trackingBug?=?115609023)
public?boolean?shouldCloseOnTouch(Context?context,?MotionEvent?event)?{
????final?boolean?isOutside?=
????????????event.getAction()?==?MotionEvent.ACTION_UP?&&?isOutOfBounds(context,?event)
????????????||?event.getAction()?==?MotionEvent.ACTION_OUTSIDE;
????if?(mCloseOnTouchOutside?&&?peekDecorView()?!=?null?&&?isOutside)?{
?//return?true:说明事件在边界外,即?消费事件
?return?true;
????}
//返回false:在边界内,即未消费(默认)
????return?false;
}

Activity.onTouchEvent()到这里就基本结束了。后面源码完善在再补充。

Activity源码总结

当一个点击事件发生时,从Activity的事件分发开始(Activity.dispatchTouchEvent()),流程如下:

ViewGroup事件分发机制

????????从上面Activity的事件分发机制可知,在Activity.dispatchTouchEvent()实现了将事件从Activity->ViewGroup的传递,ViewGroup的事件分发机制从dispatchTouchEvent()开始。

????????在Activity.dispatchTouchEvent()中遗留了ViewGroup.dispatchTouchEvent()什么时候返回true/false在下面的源码分析中找出来。

ViewGroup.dispatchTouchEvent()源码

先从宏观角度,纵览整个 dispatch 的源码如下:

@Override
public?boolean?onInterceptTouchEvent(MotionEvent?ev)?{
????/**
?????*?一、检查当前ViewGroup是否需要拦截事件
?????*?1、如果事件为DOWN事件,则调用onInterceptTouchEvent进行拦截判断;
?????*?2、mFirstTouchTarget!=null,代表已经有子View捕获了这个事件,
?????子?View?的?dispatchTouchEvent?返回true就是代表捕获touch?件。
?????*/
????/**
?????*?二、将事件分发给子View
?????*?满足条件canceled和intercepted都为false,既不取消也不拦截
?????*?1、actionMasked==MotionEvent.ACTION_DOWN
?????表明事件主动分发的前提是事件为?DOWN?事件
?????*?2、通过for循环,遍历当前ViewGroup下的所有子View;
?????*?3、处判断事件坐标是否在子?View?坐标范围内,并且子?View?并没有处在动画状态;
?????*?4、调用?dispatchTransformedTouchEvent?方法将事件分发给子?View,
?????如果子?View?捕获事件成功,则将?mFirstTouchTarget?赋值给子?View。
?????*/
????/**
?????*?三、根据mFirstTouchTarget再次分发事件
?????*?3.1、mFirstTouchTarget为null,说明在上述的事件分发中并没有子?View?对事件进行了捕获操作。
?????直接调用?dispatchTransformedTouchEvent?方法,并传入child为?null
?????最终会调用?super.dispatchTouchEvent?方法。实际上最终会调用自身的?onTouchEvent?方法,进行处理touch事件。
?????结论:如果没有子?View?捕获处理?touch?事件,ViewGroup会通过自身的onTouchEvent方法进行处理。
?????*?3.2、mFirstTouchTarget?不为?null,说明在上述的事件分发中有子?View?对?touch?事件进行了捕获,
?????则直接将当前以及后续的事件交给?mFirstTouchTarget?指向的?View?进行处理。
?????*/
}

下面咱逐层分析:

@Override
public?boolean?dispatchTouchEvent(MotionEvent?ev)?{
????//?是否按下操作?,?最终的对外返回结果?,?该方法的最终返回值?
????boolean?handled?=?false;
????/*onFilterTouchEventForSecurity以应用安全策略过滤触摸事件。
????/return?true分派事件,return?false删除事件。*/
????if?(onFilterTouchEventForSecurity(ev))?{
????????final?int?action?=?ev.getAction();
????????final?int?actionMasked?=?action?&?MotionEvent.ACTION_MASK;
????????final?boolean?intercepted;
????????/**
?????????*?一、检查当前ViewGroup是否需要拦截事件
?????????*?1、如果事件为DOWN事件,则调用onInterceptTouchEvent进行拦截判断;
?????????*?2、mFirstTouchTarget!=null,代表已经有子View捕获了这个事件,
?????????子?View?的?dispatchTouchEvent?返回true就是代表捕获touch?件。
?????????*/
????????if?(actionMasked?==?MotionEvent.ACTION_DOWN
????????????????||?mFirstTouchTarget?!=?null)?{
????????????final?boolean?disallowIntercept?=?(mGroupFlags?&?FLAG_DISALLOW_INTERCEPT)?!=?0;
????????????if?(!disallowIntercept)?{
????????????????intercepted?=?onInterceptTouchEvent(ev);
????????????????//恢复操作以防它被更改
????????????????ev.setAction(action);
????????????}?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;
????????}
????????//?Check?for?cancelation.
????????final?boolean?canceled?=?resetCancelNextUpFlag(this)
????????????????||?actionMasked?==?MotionEvent.ACTION_CANCEL;

????????//?Update?list?of?touch?targets?for?pointer?down,?if?needed.
????????final?boolean?isMouseEvent?=?ev.getSource()?==?InputDevice.SOURCE_MOUSE;
????????final?boolean?split?=?(mGroupFlags?&?FLAG_SPLIT_MOTION_EVENTS)?!=?0
????????????????&&?!isMouseEvent;
????????TouchTarget?newTouchTarget?=?null;
????????boolean?alreadyDispatchedToNewTouchTarget?=?false;
????????/**
?????????*?二、将事件分发给子View
?????????*?满足条件canceled和intercepted都为false,既不取消也不拦截
?????????*/
????????if?(!canceled?&&?!intercepted)?{
????????????/*1、actionMasked==MotionEvent.ACTION_DOWN表明
????????????事件主动分发的前提是事件为?DOWN?事件*/
????????????if?(actionMasked?==?MotionEvent.ACTION_DOWN
????????????????????||?(split?&&?actionMasked?==?MotionEvent.ACTION_POINTER_DOWN)
????????????????????||?actionMasked?==?MotionEvent.ACTION_HOVER_MOVE)?{
????????????????final?int?actionIndex?=?ev.getActionIndex();?//?always?0?for?down
????????????????final?int?idBitsToAssign?=?split???1?<<?ev.getPointerId(actionIndex)
????????????????????????:?TouchTarget.ALL_POINTER_IDS;

????????????????//?Clean?up?earlier?touch?targets?for?this?pointer?id?in?case?they
????????????????//?have?become?out?of?sync.
????????????????removePointersFromTouchTargets(idBitsToAssign);

????????????????final?int?childrenCount?=?mChildrenCount;
????????????????if?(newTouchTarget?==?null?&&?childrenCount?!=?0)?{
????????????????????final?float?x?=
????????????????????????????isMouseEvent???ev.getXCursorPosition()?:?ev.getX(actionIndex);
????????????????????final?float?y?=
????????????????????????????isMouseEvent???ev.getYCursorPosition()?:?ev.getY(actionIndex);
????????????????????//?Find?a?child?that?can?receive?the?event.
????????????????????//?Scan?children?from?front?to?back.
????????????????????final?ArrayList<View>?preorderedList?=?buildTouchDispatchChildList();
????????????????????final?boolean?customOrder?=?preorderedList?==?null
????????????????????????????&&?isChildrenDrawingOrderEnabled();
????????????????????final?View[]?children?=?mChildren;
????????????????????//2、通过for循环,遍历当前ViewGroup下的所有子View;
????????????????????for?(int?i?=?childrenCount?-?1;?i?>=?0;?i--)?{
????????????????????????final?int?childIndex?=?getAndVerifyPreorderedIndex(
????????????????????????????????childrenCount,?i,?customOrder);
????????????????????????final?View?child?=?getAndVerifyPreorderedView(
????????????????????????????????preorderedList,?children,?childIndex);
????????????????????????//3、处判断事件坐标是否在子?View?坐标范围内,并且子?View?并没有处在动画状态;
????????????????????????if?(!child.canReceivePointerEvents()
????????????????????????????????||?!isTransformedTouchPointInView(x,?y,?child,?null))?{
????????????????????????????continue;
????????????????????????}

????????????????????????newTouchTarget?=?getTouchTarget(child);
????????????????????????if?(newTouchTarget?!=?null)?{
????????????????????????????//?Child?is?already?receiving?touch?within?its?bounds.
????????????????????????????//?Give?it?the?new?pointer?in?addition?to?the?ones?it?is?handling.
????????????????????????????newTouchTarget.pointerIdBits?|=?idBitsToAssign;
????????????????????????????break;
????????????????????????}

????????????????????????resetCancelNextUpFlag(child);
????????????????????????/*4、调用?dispatchTransformedTouchEvent?方法将事件分发给子?View,
?????????????????????????如果子?View?捕获事件成功,则将?mFirstTouchTarget?赋值给子?View。*/
????????????????????????if?(dispatchTransformedTouchEvent(ev,?false,?child,?idBitsToAssign))?{
????????????????????????????//?Child?wants?to?receive?touch?within?its?bounds.
????????????????????????????mLastTouchDownTime?=?ev.getDownTime();
????????????????????????????if?(preorderedList?!=?null)?{
????????????????????????????????//?childIndex?points?into?presorted?list,?find?original?index
????????????????????????????????for?(int?j?=?0;?j?<?childrenCount;?j++)?{
????????????????????????????????????if?(children[childIndex]?==?mChildren[j])?{
????????????????????????????????????????mLastTouchDownIndex?=?j;
????????????????????????????????????????break;
????????????????????????????????????}
????????????????????????????????}
????????????????????????????}?else?{
????????????????????????????????mLastTouchDownIndex?=?childIndex;
????????????????????????????}
????????????????????????????mLastTouchDownX?=?ev.getX();
????????????????????????????mLastTouchDownY?=?ev.getY();
????????????????????????????newTouchTarget?=?addTouchTarget(child,?idBitsToAssign);
????????????????????????????alreadyDispatchedToNewTouchTarget?=?true;
????????????????????????????break;
????????????????????????}


????????????????????}
????????????????????if?(preorderedList?!=?null)?preorderedList.clear();
????????????????}
????????????}
????????}
????????/**
?????????*?三、根据mFirstTouchTarget再次分发事件
?????????*/
????????//3.1mFirstTouchTarget为null,
????????if?(mFirstTouchTarget?==?null)?{
????????????/*说明在上述的事件分发中并没有子?View?对事件进行了捕获操作。
????????????直接调用?dispatchTransformedTouchEvent?方法,并传入child为?null
????????????最终会调用?super.dispatchTouchEvent?方法。
????????????实际上最终会调用自身的?onTouchEvent?方法,进行处理touch事件。*/
????????????/*结论:如果没有子?View?捕获处理?touch?事件,
????????????ViewGroup会通过自身的onTouchEvent方法进行处理。*/
????????????handled?=?dispatchTransformedTouchEvent(ev,?canceled,?null,
????????????????????TouchTarget.ALL_POINTER_IDS);
????????}?else?{
????????????//?Dispatch?to?touch?targets,?excluding?the?new?touch?target?if?we?already
????????????//?dispatched?to?it.??Cancel?touch?targets?if?necessary.
????????????TouchTarget?predecessor?=?null;
????????????TouchTarget?target?=?mFirstTouchTarget;
????????????while?(target?!=?null)?{
????????????????final?TouchTarget?next?=?target.next;
????????????????if?(alreadyDispatchedToNewTouchTarget?&&?target?==?newTouchTarget)?{
????????????????????handled?=?true;
????????????????}?else?{
????????????????????final?boolean?cancelChild?=?resetCancelNextUpFlag(target.child)
????????????????????????????||?intercepted;

????????????????????/*mFirstTouchTarget?不为?null,说明在上述的事件分发中有子?View?对?touch?事件进行了捕获,
????????????????????则直接将当前以及后续的事件交给?mFirstTouchTarget?指向的?View?进行处理。
?????????????????????*/
????????????????????if?(dispatchTransformedTouchEvent(ev,?cancelChild,
????????????????????????????target.child,?target.pointerIdBits))?{
????????????????????????handled?=?true;
????????????????????}
????????????????}
????????????????predecessor?=?target;
????????????????target?=?next;
????????????}
????????}
????}
????return?handled;
}

ViewGroup.onInterceptTouchEvent()源码

/**
??*?创建人:帅次
??*?创建时间:2021/7/6
??*?前提ViewGroup.dispatchTouchEvent?return?super.dispatchTouchEvent(ev);
??*?功能:是否拦截事件?return?true拦截,return?false(默认)不拦截
??*/
public?boolean?onInterceptTouchEvent(MotionEvent?ev)?{
????if?(ev.isFromSource(InputDevice.SOURCE_MOUSE)
????????????&&?ev.getAction()?==?MotionEvent.ACTION_DOWN
????????????&&?ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
????????????&&?isOnScrollbarThumb(ev.getX(),?ev.getY()))?{
????????return?true;
????}
????return?false;
}

ViewGroup.onTouchEvent()源码

????????ViewGroup是没有onTouchEvent()这个方法的,因为ViewGroup是View的子类,所以ViewGroup可以直接调用View的onTouchEvent()方法。这里就不做详细介绍,下面在View事件分发机制中一起解答。

ViweGroup源码总结

????????Android事件分发传递到Acitivity后,总是先传递到ViewGroup、再传递到View。流程总结如下:(假设已经经过了Acitivity事件分发传递并传递到ViewGroup)

View的事件分发机制

从上面ViewGroup事件分发机制知道,View事件分发机制从dispatchTouchEvent()开始。

View.dispatchTouchEvent()源码

public?boolean?dispatchTouchEvent(MotionEvent?event)?{
????/*一、(mViewFlags?&?ENABLED_MASK)?==?ENABLED
?????1、该条件是判断当前点击的控件是否enable
?????2、由于很多View默认enable,故该条件恒定为true(除非手动设置为false)
?????*/

????/*二、mOnTouchListener?!=?null
?????1、mOnTouchListener变量在View.setOnTouchListener()里赋值
?????2、即只要给控件注册了Touch事件,mOnTouchListener就一定被赋值(即不为空)
?????*/

?????/*三、mOnTouchListener.onTouch(this,?event)
?????1、即回调控件注册Touch事件时的onTouch();
?????2、需手动复写设置,具体如下(以View为例)
?????view.setOnTouchListener(new?OnTouchListener()?{
????????@Override
????????public?boolean?onTouch(View?v,?MotionEvent?event)?{
????????????MLog.logEvent("MyTouchTrueView.onTouch自行处理:",event);
????????????return?true;
????????????//?1、若在onTouch()返回true,就会让上述三个条件全部成立,
????????????从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
????????????//?2、若在onTouch()返回false,就会使得上述三个条件不全部成立,
????????????从而使得View.dispatchTouchEvent()中跳出if,执行onTouchEvent(event)
????????????//?3、onTouchEvent()源码分析?
????????}
????});
?????*/
????if?(?(mViewFlags?&?ENABLED_MASK)?==?ENABLED?&&
????????????mOnTouchListener?!=?null?&&
????????????mOnTouchListener.onTouch(this,?event))?{
????????return?true;
????}

????return?onTouchEvent(event);
}

View.onTouchEvent()源码

public?boolean?onTouchEvent(MotionEvent?event)?{??
????...?//?仅展示关键代码
????//?若该控件可点击,则进入switch判断中
????if?(((viewFlags?&?CLICKABLE)?==?CLICKABLE?||?(viewFlags?&?LONG_CLICKABLE)?==?LONG_CLICKABLE))?{??
????????//?根据当前事件类型进行判断处理
????????switch?(event.getAction())?{?
????????????//?抬起View
????????????case?MotionEvent.ACTION_UP:??
????????????????????performClick();?
????????????????????break;??
????????????//?按下
????????????case?MotionEvent.ACTION_DOWN:??
????????????????postDelayed(mPendingCheckForTap,?ViewConfiguration.getTapTimeout());??
????????????????break;??
????????????//?取消
????????????case?MotionEvent.ACTION_CANCEL:??
????????????????refreshDrawableState();??
????????????????removeTapCallback();??
????????????????break;
????????????//?滑动
????????????case?MotionEvent.ACTION_MOVE:??
????????????????final?int?x?=?(int)?event.getX();??
????????????????final?int?y?=?(int)?event.getY();??
????????????????int?slop?=?mTouchSlop;??
????????????????if?((x?<?0?-?slop)?||?(x?>=?getWidth()?+?slop)?||??
????????????????????????(y?<?0?-?slop)?||?(y?>=?getHeight()?+?slop))?{??
????????????????????removeTapCallback();??
????????????????????if?((mPrivateFlags?&?PRESSED)?!=?0)?{??
????????????????????????removeLongPressCallback();??
????????????????????????mPrivateFlags?&=?~PRESSED;??
????????????????????????refreshDrawableState();??
????????????????????}??
????????????????}??
????????????????break;??
????????}??

????????//?若该控件可点击,就一定返回true
????????return?true;??
????}??
??//?若该控件不可点击,就一定返回false
??return?false;??
}
??public?boolean?performClick()?{??
??????if?(mOnClickListener?!=?null)?{
??????????//?只要通过setOnClickListener()为控件View注册1个点击事件
??????????//?那么就会给mOnClickListener变量赋值(即不为空)
??????????//?则会往下回调onClick()?&?performClick()返回true
??????????playSoundEffect(SoundEffectConstants.CLICK);??
??????????mOnClickListener.onClick(this);??
??????????return?true;??
??????}??
??????return?false;??
??}??

View源码总结

????????Android事件分发传递到Acitivity后,总是先传递到ViewGroup、再传递到View。流程总结如下:(假设已经经过了ViewGroup事件分发传递并传递到View)

总结

重点分析了 dispatchTouchEvent 的事件的流程机制,这一过程主要分 3 部分:

  • 1、判断是否需要拦截 —> 主要是根据 onInterceptTouchEvent 方法的返回值来决定是否拦截;

  • 2、在 DOWN 事件中将 touch 事件分发给子 View —> 这一过程如果有子 View 捕获消费了 touch 事件,会对 mFirstTouchTarget 进行赋值;

  • 3、最后一步,DOWN、MOVE、UP 事件都会根据 mFirstTouchTarget 是否为 null,决定是自己处理 touch 事件,还是再次分发给子 View。

然后介绍了整个事件分发中的几个特殊的点。

  • 1、DOWN 事件的特殊之处:事件的起点;决定后续事件由谁来消费处理;

  • 2、mFirstTouchTarget 的作用:记录捕获消费 touch 事件的 View,是一个链表结构;

  • 3、CANCEL 事件的触发场景:当父视图先不拦截,然后在 MOVE 事件中重新拦截,此时子 View 会接收到一个 CANCEL 事件。

以上就是本文的全部内容,希望对大家学习 Android 事件分发有所帮助和启发。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-29 11:46:00  更:2021-07-29 11:46:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/2 16:36:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码