前言

本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍:

我的GIthub博客

学习清单:

  • View是什么
  • View的位置参数
  • View的触控
  • View的滑动

涉及以下各个知识点:

  • View的各种滑动方式及其对比
  • 弹性滑动
  • 滑动冲突
  • View的动画
  • View的事件分发机制
  • View的工作原理
  • View的自定义方式

一.为什么要学习View?

View,是Android中十分重要的一个知识点,是所有控件的基类,尽管View不属于四大组件,但是它的作用堪比四大组件,甚至重要性大于ContentProviderBroadcast Receivers

ViewGroupView的继承,它的内部包含了一组View。

很多时候,面对产品经理的各种奇葩的需求,仅仅使用系统提供的控件是不能满足需求的,因此,我们就需要自定义特定的控件,而自定义控件就需要对View体系有一定程度的理解;有时候,涉及到滑动事件的自定义View的时候,难免会出现各种各样的滑动冲突,而要解决滑动冲突的话,还需要对View的事件分发机制了然于心。

综上,掌握好View这方面的知识,不仅可以让你在日常开发中对自定义View的各种场景胸有成竹,还可以让你在面试官的重重追问(ai hu)下游刃有余(xin tai bao zha)。

二.核心知识点归纳

2.1 View的位置参数

Q1:Android坐标系是怎样的呢?

以屏幕的左上角为坐标原点,向右为x轴增大方向,向下为y轴增大方向

Q2:View的位置怎么确定?

  • 由四个顶点确定,分别对应四个属性:top、left、right、bottom
  • left是左上角的横坐标,left = getLeft()
  • right是右下角的横坐标,right = getRight()
  • top是左上角的纵坐标,top = getTop()
  • bottom是右下角的纵坐标,bottom=getBottom()

注意:这些坐标是相对于父容器而言的,属于相对坐标;如果想要得到绝对坐标,需要调用getRawX(),绝对坐标的知识在下文将会详细讲解。

因此,View的宽高和坐标关系:

  • width = right - left,可直接通过getWidth()得到
  • height = bottom - top,可直接通过getHeight()得到

Q3:View偏移量translation

translationXtranslationY是View 左上角相对父容器左上角的偏移量,它们默认值是0。这些参数也是相对于View父容器

  • 存在关系:x = left + translationX,y = top + translationY
  • 由此可见,x和left不同体现在:
  • left是View的初始坐标,在绘制完毕后就不会再改变;
  • x是View偏移后的实时坐标,是实际坐标。y和top的区别同理。

需要注意的是,在onCreate()方法里无法获取到View的坐标参数,这是因为此时View还未开始绘制,全部坐标参数将都是0。

2.2 View的触控

2.2.1 MotionEvent

它是手指触摸屏幕所产生的一系列事件。典型事件有:

  • ACTION_DOWN:手指刚接触屏幕
  • ACTION_MOVE:手指在屏幕上滑动
  • ACTION_UP:手指在屏幕上松开的一瞬间

事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件,任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件

  • 通过MotionEvent 对象可以得到触摸事件的x、y坐标。其中通过getX()getY()可获取相对于当前view(注意:不是父容器)左上角的x、y坐标(相对坐标);

  • 通过getRawX()getRawY()可获取相对于手机屏幕左上角的x,y坐标(绝对坐标)。

    具体关系见下图:

2.2.2 TouchSlop

  • 系统所能识别的被认为是滑动的最小距离。即当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。
  • 该常量和设备有关,可用它来判断用户的滑动是否达到阈值
  • 获取方法:ViewConfiguration.get(getContext()).getScaledTouchSlop()

2.2.3 VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。

使用过程:

  • 在view的onTouchEvent方法中追踪当前单击事件的速度:

    VelocityTracker velocityTracker = VelocityTracker.obtain();//实例化一个VelocityTracker 对象
    velocityTracker.addMovement(event);//添加追踪事件
  • ACTION_UP事件中获取当前的速度

    velocityTracker .computeCurrentVelocity(1000);//获取速度前先计算速度,这里计算的是在1000ms内
    float xVelocity = velocityTracker .getXVelocity();//得到的是1000ms内手指在水平方向从左向右滑过的像素数,即水平速度
    float yVelocity = velocityTracker .getYVelocity();//得到的是1000ms内手指在水平方向从上向下滑过的像素数,垂直速度

    注意速度方向,这个速度方向和下面的mScrollX的方向相反

  • 当不需要使用它的时候,需要调用clear方法来重置并回收内存

    velocityTracker.clear();
    velocityTracker.recycle();

2.2.4 GestureDetector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为

使用过程:

  • 创建一个GestureDetecor对象并实现OnGestureListener接口,根据需要实现单击等方法

    GestureDetector mGestureDetector = new GestureDetector(this);//实例化一个GestureDetector对象
    mGestureDetector.setIsLongpressEnabled(false);// 解决长按屏幕后无法拖动的现象
  • 接管目标view的onTouchEvent方法,在待监听view的onTouchEvent方法中添加如下实现

    boolean consume = mGestureDetector.onTouchEvent(event);
    return consume;
  • 有选择的实现OnGestureListener和OnDoubleTapListener中的方法

建议:如果只是监听滑动操作,建议在onTouchEvent中实现;如果要监听双击这种行为,则使用GestureDetector

2.3 View的滑动

2.3.1 View滑动的七种方式

1. scrollTo/scollBy
  • 区别:scrollBy是内部调用了scrollTo的,它是基于当前位置的相对滑动;而scrollTo绝对滑动,因此如果利用相同输入参数多次调用scrollTo()方法,由于View初始位置是不变只会出现一次View滚动的效果而不是多次。
  • 注意:两者都只能对view内容进行滑动,而不能使view本身滑动。
  • 方向:手指从右向左滑动,mScrollX为正值,反之为负值;手指从下往上滑动,mScrollY为正值,反之为负值。(更直观感受:查看下一张照片或者查看长图时手指滑动方向为正)
  • 滑动类型:非弹性滑动

2. LayoutParams
  • 原理:通过改变View的LayoutParams使得View重新布局:比如将一个View向右移动100像素,向右,只需要把它的marginLeft参数增大即可
  • 滑动类型:非弹性滑动
MarginLayoutParams params = (MarginLayoutParams) btn.getLayoutParams();
params.leftMargin += 100;
btn.requestLayout();// 请求重新对View进行measure、layout
3. 动画
  • 动画分为View动画和属性动画,View动画又分为帧动画和补间动画

  • 如果使用属性动画的话,为了能够兼容3.0以下版本,需要采用开源动画库nineoldandroids。

  • 属于弹性滑动

ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();//在100ms内使得View从原始位置向右平移100像素

想要了解动画详细内容的读者,可以看一下笔者这篇文章:进阶之路 | 奇妙的Animation之旅

4. layout()
  • 基本思想:记下触摸点的坐标移动之后,记下移动后的坐标算出偏移量

  • 使用方式:在onTouchEvent中获取到手指的横纵坐标,在ACTION_DOWN中存储上次的x,在ACTION_MOVE中计算移动的距离,最后调用layout方法重新放置View

public boolean onTouchEvent(MotionEvent event) {
//获取到手指处的横坐标和纵坐标
int x = (int) event.getX();
int y = (int) event.getY(); switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//lastX是存储上一次的x
lastX = x;
lastY = y;
break; case MotionEvent.ACTION_MOVE:
//计算移动的距离
int offsetX = x - lastX;
int offsetY = y - lastY;
//调用layout方法来重新放置它的位置,左上右下
layout(getLeft()+offsetX, getTop()+offsetY,
getRight()+offsetX , getBottom()+offsetY);
break;
return true;
}
5. offsetLeftAndRight()offsetTopAndBottom()

使用方式类似于layout(),将 layout(getLeft()+offsetX, getTop()+offsetY,getRight()+offsetX , getBottom()+offsetY)换成offsetLeftAndRight(offsetX)offsetTopAndBottom(offsetY)即可

           // 对left和right进行偏移
offsetLeftAndRight(offsetX);
//对top和bottom进行偏移
offsetTopAndBottom(offsetY);
6. Scroller
  • 与scrollTo/scrollBy不同:scrollTo/scrollBy过程是瞬间完成的,非平滑;而Scroller则有过渡滑动的效果
  • 注意:Scoller本身无法让View弹性滑动,它需要和View的computeScroll方法配合使用。
  • 原理:Scoller的computeScrollOffset()根据时间的流逝动态计算一小段时间里View滑动的距离,并得到当前View位置,再通过scrollTo继续滑动。即把一次滑动拆分成无数次小距离滑动从而实现弹性滑动。

Scroller惯用代码:

Scroller scroller = new Scroller(mContext); //实例化一个Scroller对象

private void smoothScrollTo(int dstX, int dstY) {
int scrollX = getScrollX();//View的左边缘到其内容左边缘的距离
int scrollY = getScrollY();//View的上边缘到其内容上边缘的距离
int deltaX = dstX - scrollX;//x方向滑动的位移量
int deltaY = dstY - scrollY;//y方向滑动的位移量
scroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000); //开始滑动
invalidate(); //刷新界面
} //计算一段时间间隔内偏移的距离,并返回是否滚动结束的标记
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurY());
postInvalidate();//通过不断的重绘不断的调用computeScroll方法
}
}

startScroll()的源码:

只是进行前期的准备工作,并没有进行实际的滑动操作,而是通过后续invalidate()方法去做滑动动作。

public void startScroll(int startX,int startY,int dx,int dy,int duration){
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;//滑动时间
mStartTime = AnimationUtils.currentAminationTimeMills();//开始时间
mStartX = startX;//滑动起点
mStartY = startY;//滑动起点
mFinalX = startX + dx;//滑动终点
mFinalY = startY + dy;//滑动终点
mDeltaX = dx;//滑动距离
mDeltaY = dy;//滑动距离
mDurationReciprocal = 1.0f / (float)mDuration;
}

7. 延时策略
  • 通过发送一系列延时信息从而达到一种渐近式的效果,具体可以通过Handler/ViewpostDelayed,也可使用线程的sleep方法。
  • 缺点:无法精确地定时;原因:系统的消息调度也需要时间

2.3.2 滑动冲突

Q1:产生原因

一般情况下,在一个界面里存在内外两层可同时滑动的情况时,会出现滑动冲突现象。

Q2:出现的场景:

  • 场景一:外部滑动和内部滑动方向不一致:如ViewPager嵌套ListView(实际这么用没问题,因为ViewPager内部已处理过)。
  • 场景二:外部滑动方向和内部滑动方向一致:如ScrollView嵌套ListView。

读者如果想要了解出现原因以及解决方式,笔者推荐一篇文章:ScrollView嵌套ListView时可能产生的问题解决

  • 场景三:上面两种情况的嵌套

Q3:处理规则

  • 对场景一:当用户左右/上下滑动时让外部View拦截点击事件,当用户上下/左右滑动时让内部View拦截点击事件。即根据滑动的方向判断谁来拦截事件。关于判断是上下滑动还是左右滑动,可根据滑动的距离或者滑动的角度去判断。
  • 对场景二:一般从业务上找突破点。即根据业务需求,规定何时让外部View拦截事件何时由内部View拦截事件。
  • 对场景三:相对复杂,可同样根据需求在业务上找到突破点。

Q4:解决方式

这里的onInterceptTouchEventdispatchTouchEventrequestDisallowInterceptTouchEvent等方法在View的事件分发机制会详细说明

A1:外部拦截法

  • 含义:指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。
  • 方法:需要重写父容器的onInterceptTouchEvent方法,在内部做出相应的拦截。
//重写父容器的拦截方法
public boolean onInterceptTouchEvent (MotionEvent event){
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://对于ACTION_DOWN事件必须返回false,一旦拦截后续事件将不能传递给子View
intercepted = false;
break;
case MotionEvent.ACTION_MOVE://对于ACTION_MOVE事件根据需要决定是否拦截
if (父容器需要当前事件) {
intercepted = true;
} else {
intercepted = flase;
}
break;
}
case MotionEvent.ACTION_UP://对于ACTION_UP事件必须返回false,一旦拦截子View的onClick事件将不会触发
intercepted = false;
break;
default : break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}

A2:内部拦截法

  • 含义:指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。

  • 方法:需要配合requestDisallowInterceptTouchEvent方法。重写子ViewdispatchTouchEvent()

    public boolean dispatchTouchEvent ( MotionEvent event ) {
    int x = (int) event.getX();
    int y = (int) event.getY(); switch (event.getAction) {
    case MotionEvent.ACTION_DOWN:
    parent.requestDisallowInterceptTouchEvent(true);//为true表示禁止父容器拦截
    break;
    case MotionEvent.ACTION_MOVE:
    int deltaX = x - mLastX;
    int deltaY = y - mLastY;
    if (父容器需要此类点击事件) {
    parent.requestDisallowInterceptTouchEvent(false);//为fasle表示允许父容器拦截
    }
    break;
    case MotionEvent.ACTION_UP:
    break;
    default :
    break;
    } mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
    }

    除子容器需要做处理外,父容器也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子容器调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。

    因此,父View需要重写onInterceptTouchEvent()

    public boolean onInterceptTouchEvent (MotionEvent event) {
    int action = event.getAction();
    if(action == MotionEvent.ACTION_DOWN) {
    return false;
    } else {
    return true;
    }
    }

内部拦截法要求父容器不能拦截ACTION_DOWN的原因:

由于该事件并不受FLAG_DISALLOW_INTERCEPT(由requestDisallowInterceptTouchEvent方法设置)标记位控制,一旦ACTION_DOWN事件到来,该标记位会被重置。所以一旦父容器拦截了该事件,那么所有的事件都不会传递给子View,内部拦截法也就失效了。

2.4 View的事件分发机制

读者看完本篇对事件分发机制还有些模糊的话,笔者墙裂推荐一篇浅显易懂的文章:android中的事件传递和处理机制

Q1:了解setContentView()

我们将从源码的角度,一步步带大家深入setContentView()的本质,为后面事件分发机制的了解打好基础

因此,我们可以得到Activity的构成,如下图所示

Q2:事件分发本质是什么:

就是对MotionEvent事件分发的过程。即当一个MotionEvent产生了以后,系统需要将这个点击事件传递到一个具体的View上。(关于MotionEvent介绍见本篇2.2.1)

Q3:事件分发需要的主要方法是什么

  • dispatchTouchEvent:进行事件的分发(传递)。返回值是 boolean 类型,受当前onTouchEvent下级viewdispatchTouchEvent影响
  • onInterceptTouchEvent:对事件进行拦截。该方法只在ViewGroup中有,View(不包含 ViewGroup)是没有的。如果一旦拦截,则执行ViewGrouponTouchEvent,在ViewGroup中处理事件,而不接着分发给View,且只调用一次,所以后面的事件都会交给ViewGroup处理。
  • onTouchEvent:进行事件处理

  • 事件分发是逐级下发的,目的是将事件传递给一个View。
  • ViewGroup一旦拦截事件,就不往下分发,同时调用onTouchEvent处理事件。

2.5 View的工作原理

2.5.1 View工作流程

measure测量->layout布局->draw绘制

  • measure确定View的测量宽高
  • layout确定View的最终宽高四个顶点的位置
  • draw将View 绘制到屏幕
  • 对应onMeasure()onLayout()onDraw()三个方法。

具体过程:

  • ViewRoot对应于ViewRootImpl类,它是连接WindowManagerDecorView的纽带
  • View的绘制流程是从ViewRoot.performTraversals开始。
  • performTraversals()依次调用performMeasure()performLayout()performDraw()三个方法,完成顶级 View的绘制。
  • 其中,performMeasure()会调用measure()measure()中又调用onMeasure(),实现对其所有子元素的measure过程,这样就完成了一次measure过程;接着子元素会重复父容器的measure过程,如此反复至完成整个View树的遍历。layout和draw同理。过程图如下:

2.5.2 measure

先来理解MeasureSpec

  • 作用:通过宽测量值widthMeasureSpec和高测量值heightMeasureSpec决定View的大小

  • 组成:一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize( 某种测量模式下的规格大小)。

  • 三种模式:

    a.UNSPECIFIED: 父容器不对View有任何限制,要多大有多大。常用于系统内部。

    b.EXACTLY(精确模式): 父视图为子视图指定一个确切的尺寸SpecSize。对应LayoutParams中的match_parent具体数值

    c.AT_MOST(最大模式): 父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content

  • 决定因素:由子View的布局参数LayoutParams父容器MeasureSpec值共同决定。

现在,分别讨论两种measure

  • View的measure:只有一个原始的View,通过measure()即可完成测量。

getDefaultSize()中可以看出,直接继承View的自定义View需要重写onMeasure()并设置wrap_content时的自身大小,否则效果相当于macth_parent。解决上述问题的典型代码:

方法一:

	@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//分析模式,根据不同的模式来设置
if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,mHeight);
}else if(widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecSize);
}else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,mHeight);
}
}

方法二:

	@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int width=resolveSize(mWidth, widthMeasureSpec);
int height=resolveSize(mHeight, heightMeasureSpec);
setMeasuredDimension(width,height);
}
  • ViewGroup的measure:除了完成ViewGroup自身的测量外,还会遍历去调用所有子元素的measure方法。

ViewGroup中没有重写onMeasure(),而是提供measureChildren()

如果读者对onMeasure的详细重写例子感兴趣的话,笔者推荐一篇文章:自定义View Measure过程 - 最易懂的自定义View原理系列(2)

2.5.3 layout

  • 确定View的最终宽高和四个顶点的位置

大致流程:从顶级View开始依次调用layout(),其中子View的layout()会调用setFrame()来设定自己的四个顶点(mLeft、mRight、mTop、mBottom),接着调用onLayout()来确定其坐标,注意该方法是空方法,因为不同的ViewGroup对其子View的布局是不相同的。

如果读者对onLayout()的详细重写例子感兴趣的话,笔者推荐一篇文章:(3)自定义View Layout过程 - 最易懂的自定义View原理系列

2.5.4 draw

推荐阅读对View工作流程的理解(源码)

  • 绘制到屏幕

绘制顺序:

  • 绘制背景:background.draw(canvas)
  • 绘制自己:onDraw(canvas)
  • 绘制children:dispatchDraw(canvas)
  • 绘制装饰:onDrawScrollBars(canvas)

注意:View有一个特殊的方法setWillNotDraw(),该方法用于设置 WILL_NOT_DRAW 标记位(其作用是当一个View不需要绘制内容时,系统可进行相应优化)。默认情况下View是没有这个优化标志的(设为true)。

2.6 自定义View

如果想了解自定义View实例的读者,笔者推荐一篇文章:手把手教你写一个完整的自定义View

Q1:自定义View的类型有哪些

特别提醒

三.课堂小测试

恭喜你!已经看完了前面的文章,相信你对View已经有一定深度的了解,下面,进行一下课堂小测试,验证一下自己的学习成果吧!

Q1:View的测量宽高和最终宽高有什么区别

这个问题具体为ViewgetMeasuredWidthgetWidth有什么区别?

  • 答案揭晓:

    View默认实现中,测量宽高和最终宽高相等,但是测量宽高的赋值时机比最终宽高的赋值时机稍微早一点,测量宽高形成于measure过程,最终宽高形成于View的layout过程。

Q2:什么情况下测量宽高和最终宽高不一致呢

  • 重写了View的layout方法

    public void layout(int l,int t,int r, int b){
    super.layout(l,t,r+100,b+100);
    }
  • 在某些情况下,View需要多次measure才能确定自己的测量宽高,在前几次的测量过程中,得出的测量宽高有可能和最终宽高不一致,但最终两者还是一致的。


如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力

本文参考链接:

进阶之路 | 奇妙的View之旅的更多相关文章

  1. 进阶之路 | 奇妙的Window之旅

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习清单: Window&WindowManagerService Window&Window ...

  2. 进阶之路 | 奇妙的Animation之旅

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习清单: 动画的种类 自定义View动画 View动画的特殊使用场景 属性动画 使用动画的注意事项 一.为什 ...

  3. 进阶之路 | 奇妙的Thread之旅

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 需要已经具备的知识: Thread的基本概念及使用 AsyncTask的基本概念及使用 学习清单: 线程概述 ...

  4. 进阶之路 | 奇妙的Handler之旅

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 需要已经具备的知识: Handler的基本概念及使用 学习导图: 一.为什么要学习Handler? 在Andr ...

  5. 进阶之路 | 奇妙的IPC之旅

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习清单: IPC的基础概念 多进程和多线程的概念 Android中的序列化机制和Binder Android ...

  6. 进阶之路 | 奇妙的Activity之旅

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 本篇文章需要已经具备的知识: Activity的基本概念 AndroidManifest.xml的基本概念 学 ...

  7. 进阶之路 | 奇妙的Drawable之旅

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习清单: Drawable简介 Drawable分类 自定义Drawable 一.为什么要学习Drawabl ...

  8. 浅谈Android进阶之路

    过去十年是移动互联网蓬勃发展的黄金期,相信每个人也都享受到了移动互联网红利,在此期间,移动互联网经历了曙光期.成长期.成熟期.现在来说已经进入饱和期.依然记得在 2010-2013 年期间,从事移动开 ...

  9. Scala进阶之路-Scala中的泛型介绍

    Scala进阶之路-Scala中的泛型介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 通俗的讲,比如需要定义一个函数,函数的参数可以接受任意类型.我们不可能一一列举所有的参数类 ...

随机推荐

  1. 代码审计之CVE-2018-7600-Drupal远程代码执行漏洞-Render API

    今天学习一下Drupal的另一个漏洞,由于渲染数组不当造成的漏洞 poc: url:http://localhost/drupal-8.5.0/user/register?element_parent ...

  2. GoldenGate DB11gr2配置手册

    GoldenGate DB11gr2配置手册 源端数据库配置 1.1源端数据库打开Archive Log: SQL>shutdown immediate; SQL>startup moun ...

  3. KMP——强大的next数组

    \(KMP\) 的原理不在这里仔细讲了,主要说说最近刷题总结出的 \(next\) 数组的强大功能. 部分例题来自<信息学奥赛一本通>的配套练习. 基于定义--字符串相同前后缀 " ...

  4. idea命令行、撤销commit

    原文地址:https://blog.csdn.net/chzphoenix/article/details/38090349 近期在使用git,最开始在idea界面操作,后来要求用命令行.刚开始还不是 ...

  5. JPA基本注解的使用

    一:JPA基本注解 使用: 使用: 使用: 查看表: 二:用table来生成主键 使用: allocationSize:每次增加多少 tablel:指定使用那张表 执行两次main方法后查看表: jp ...

  6. python应用airtest库的环境搭建

    参考https://blog.csdn.net/ywyxb/article/details/64126927 注意:无论是在线还是离线安装,最好在管理员权限下执行命令 1.安装Python36(32位 ...

  7. Java之函数式接口@FunctionalInterface详解(附源码)

    Java之函数式接口@FunctionalInterface详解 函数式接口的定义 在java8中,满足下面任意一个条件的接口都是函数式接口: 1.被@FunctionalInterface注释的接口 ...

  8. re模块的使用

    re模块下的函数 compile(pattern):创建模式对象 import re pat = re.compile('D') m = pat.search('CBA') #等价于re.search ...

  9. php--->php 缓冲区 buffer 原理

    php 缓冲区 buffer 原理 1.缓冲流程 从php脚本echo(print.print_r...)内容之后,是如何显示给用户的呢,下面看看流程 echo.print => php out ...

  10. 为什么不在spring容器管理controller

    Spring容器与SpringMVC容器 1.疑问:为什么不用spring去管理所有类? 我们配置springMVC 中,为什么controller不直接交给spring 管理而要spring MVC ...