前言

本文已经收录到我的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. 7.Arrays数组的工具类

    Arrays类: 数组的工具类java.util.Arrays 由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作. ...

  2. 《唐三学node.js系列》—魂士篇&&三哥初始node.js

    前言 如果你有一定的前端基础,比如 HTML.CSS.JavaScript.jQuery.那么Node.js 能让你以最低的成本快速过渡成为一个全栈工程师(我称这个全栈为伪全栈,我认为的全栈也要精通数 ...

  3. doT 这个模板 是怎么实现的?

    之前做过一个微信有关的站 模板用 doT 嗯 这个 用起来很 不错. 但是 它是怎么实现的,想过没有? ps:https://github.com/olado/doT 源码总共 140行. 第90行里 ...

  4. 缓冲区溢出实例(二)--Linux

    原理:crossfire 1.9.0 版本接受入站 socket 连接时存在缓冲区溢出漏洞. 工具: 调试工具:edb: ###python在漏洞溢出方面的渗透测试和漏洞攻击中,具有很大的优势   实 ...

  5. 一文带你了解 HTTP 黑科技

    这是 HTTP 系列的第三篇文章,此篇文章为 HTTP 的进阶文章. 在前面两篇文章中我们讲述了 HTTP 的入门,HTTP 所有常用标头的概述,这篇文章我们来聊一下 HTTP 的一些 黑科技. HT ...

  6. VMware 虚拟机开机黑屏

    先记录一下害我差点点就又跑去重装了 好几天未打开vm 打开就黑屏 打开虚拟机就是黑屏没反应 找了很久(好像是wegame的原因,昨晚上好像是打开了wegame更新2k和lol) 1献上解决方案 管理员 ...

  7. selenium,滚到页面底部的方法

    你可以用 execute_script方法来处理这个. 调用原生javascript的API,这样你想滚到哪里就能滚到哪里.   下面的代码演示了如何滚到页面的最下面:   driver.execut ...

  8. Java8 Stream用法详解

    1.概述 Stream 的原理:将要处理的元素看做一种流,流在管道中传输,并且可以在管道的节点上处理,包括过滤筛选.去重.排序.聚合等.元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结 ...

  9. 微信小程序8种数据通信的方式

    前言 数据通信在开发中是必不可少的一个环节,也是我们必须掌握的知识.知道得越多的数据通信方式,实现业务会更加得心应手. 下面我将这些通信方式归类介绍: 组件通信 全局通信 页面通信 组件通信 prop ...

  10. ES6笔记分享 part 1

    ECMAScript ES6 从一脸懵逼到灵活运用 var let const var let const 的比较 声明与赋值 var声明的变量是可以重新赋值的,也可以重复声明 let和const声明 ...