动画入门和进阶文章列表:

Animation动画概述和执行原理
Android动画之补间动画TweenAnimation
Android动画之逐帧动画FrameAnimation
Android动画之插值器简介和系统默认插值器
Android动画之插值器Interpolator自定义
Android动画之视图动画的缺点和属性动画的引入
Android动画之ValueAnimator用法和自定义估值器
Android动画之ObjectAnimator实现补间动画和ObjectAnimator自定义属性
Android动画之ObjectAnimator中ofXX函数全解析-自定义Property,TypeConverter,TypeEvaluator
Android动画之AnimatorSet联合动画用法
Android动画之LayoutTransition布局动画
Android动画之共享元素动画
Android动画之ViewPropertyAnimator(专用于view的属性动画)
Android动画之Activity切换动画overridePendingTransition实现和Theme Xml方式实现
Android动画之ActivityOptionsCompat概述
Android动画之场景变换Transition动画的使用
Android动画之Transition和TransitionManager使用
Android动画之圆形揭露动画Circular Reveal

1 Animation动画简介

Developers:https://developer.android.google.cn/reference/android/view/animation/package-summary

Android中动画非常常用,很多效果都需要动画的配合,android提供了多种动画类型,为创建多彩的android程序提供了支持。提供的动画类型包括:补间动画,帧动画,属性动画,补间动画和帧动画被称为视图动画。

对于Animation动画,android提供了两种机制来创建视图动画,
一种是tweened animation(补间动画),
一种是frame-by-frame animation(逐帧动画) 。
Tweened animation 可以实现view一系列简单的转换(位置,尺寸,旋转,透明度),
frame-by-frame 通过加载一系列drawable资源,实现动画。

视图动画只能作用于View,且动画类型是固定的。

补间动画:确定了view的开始的视图样式和结束的视图样式,动画过程中系统会补全变化中的状态,最终就实现了动画效果。

补间动画的种类:

  • translate (平移动画)
  • scale (缩放动画)
  • rotate (旋转动画)
  • alpha (透明度动画)

补间动画可以利用xml文件和动画类进行实现,对应的具体动画类:

  • translate(平移动画) 对应 TranslateAnimation
  • scale (缩放动画) 对应 ScaleAnimation
  • rotate (旋转动画) 对应 RotateAnimation类
  • alpha ( 透明度动画) 对应 AlphaAnimation 类

补间动画一般利用xml文件实现,如果利用xml文件实现动画,需要在res/anim文件夹下穿件动画文件。

2 Animation 基类

Animation作为补间动画的基类,具有许多动画公共的属性和方法:

在android.view.animation包下,可以看出是作用于view的。
直接子类有:AlphaAnimation,AnimationSet,RotateAnimation,ScaleAnimatioin,TranslateAnimation。
XML属性包括:

下面会列举Animation中公共属性在xml文件中的表示和代码类中的设置方式及效果:
每一项包括Animation中公共属性在xml文件中的表示和代码类中的设置方式及效果

  • android:detachWallpaper 对应setDetachWallpaper(boolean):是否在壁纸上运行,取值true,flase;
  • android:duration 对应setDuration(long):动画持续时间,参数单位为毫秒;
  • android:fillAfter 对应setFillAfter(boolean):动画结束时view是否保持动画最后的状态,默认值为false;
  • android:fillBefore 对应setFillBefore(boolean):动画结束时view是否还原到开始动画前的状态,和fillAfter行为是冲突的,所以只有当fillBefore为true或者fillEnabled不为true才生效。默认是true
  • android:fillEnabled 对应setFillEnabled(boolean):如果 fillEnabled 取值为true,animation将使用fillBefore的值,否则fillBefore将被忽略。都是在动画结束时还原到原来的状态。
  • android:interpolator 对应setInterpolator(Interpolator):设定插值器;
  • android:repeatCount对应setRepeatCount(int):动画重复次数,可以是具体次数,也可以是INFINITE(-1)一直循环。
  • android:repeatMode 对应setRepeatMode(int):重复类型有两个值,reverse表示倒序回放,restart表示从头播放,需要和repeateCount配合使用。
  • android:startOffset对应setStartOffset(long):调用start函数之后等待开始运行的时间,单位为毫秒;
  • android:zAdjustment 对应setZAdjustment(int)表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal,一般不需要设置。

Animation构造函数:一般情况用不到
Animation():duration默认0ms,default interpolator,fillBefore默认true,fillAfter默认false
Animation(Context context, AttributeSet attrs):利用attributeset和context初始化

3 动画开启的方法:start(),startNow()

这两个方法有什么区别呢?

/*** Convenience method to start the animation the first time* {@link #getTransformation(long, Transformation)} is invoked.*/
public void start() {setStartTime(-1);
}/*** Convenience method to start the animation at the current time in* milliseconds.*/
public void startNow() {setStartTime(AnimationUtils.currentAnimationTimeMillis());
}

看两个函数的注释知道:
start()函数当getTransformation()第一次被调用的时候开始执行。
startNow()动画被立即执行
start和startNow内部都是调用setStartTime函数,setStartTime函数是设置动画开始执行的时间。start函数设置setStartTime(-1)会等待getTransformation第一次执行时才开始执行动画,startNow是setStartTime(AnimationUtils.currentAnimationTimeMillis()设置了具体的开始时间,动画会立刻开始执行。
所以start函数调用后不是立即执行动画,startNow是立即执行动画。

4 动画真正实现的地方在哪里

Animation是动画的基类,所以具体动画的操作一定在其子类中,通过分析可知道,最终实现动画操作在Animation类的applyTransformation()方法中,各个子类会实现这个方法,进行动画操作。

/*** Helper for getTransformation. Subclasses should implement this to apply* their transforms given an interpolation value.  Implementations of this* method should always replace the specified Transformation or document* they are doing otherwise.** @param interpolatedTime The value of the normalized time (0.0 to 1.0)*        after it has been run through the interpolation function.* @param t The Transformation object to fill in with the current*        transforms.*/
protected void applyTransformation(float interpolatedTime, Transformation t) {}

从Animation类内部可知applyTransformation()函数会被getTransformation()函数调用。Transformation类包括matrix,scale,clip等变换信息。

getTransformation()内部调用了applyTransformation(),来看看getTransformation内部的逻辑:

getTransformation()

getTransformation函数内部判断动画是否执行完毕,如果执行完毕返回false,如果动画还没有执行完返回true.

public boolean getTransformation(long currentTime, Transformation outTransformation) {//如果mStartTime == -1,初始化动画开始时间if (mStartTime == -1) {mStartTime = currentTime;}//计算动画已经执行到的位置final long startOffset = getStartOffset();final long duration = mDuration;float normalizedTime;if (duration != 0) {normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /(float) duration;} else {normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;}//判断动画是否被取消或者时间超过1.0f,为true表示动画结束或者已经被取消final boolean expired = normalizedTime >= 1.0f || isCanceled();
//设置动画是否完成标识mMore = !expired;
//处理fillEnableif (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
//处理其他参数if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {if (!mStarted) {fireAnimationStart();mStarted = true;if (NoImagePreloadHolder.USE_CLOSEGUARD) {guard.open("cancel or detach or getTransformation");}}if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);if (mCycleFlip) {normalizedTime = 1.0f - normalizedTime;}final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//执行动画具体操作applyTransformation(interpolatedTime, outTransformation);}//如果动画已经结束,判断重复执行操作if (expired) {if (mRepeatCount == mRepeated || isCanceled()) {if (!mEnded) {mEnded = true;guard.close();fireAnimationEnd();}} else {if (mRepeatCount > 0) {mRepeated++;}if (mRepeatMode == REVERSE) {mCycleFlip = !mCycleFlip;}mStartTime = -1;mMore = true;fireAnimationRepeat();}}//动画还没有执行完if (!mMore && mOneMoreTime) {mOneMoreTime = false;return true;}//所以mMore表示动画是否执行完了,为true时表示还没有执行完return mMore;
}

getTransformation函数又是在哪里执行的呢?

5 View 如何执行动画

分析getTransformation在哪里执行我们需要先分析View如何执行动画。
一般的步骤是定义好Animation对象设置属性之后,调用startAnimation()函数。
View中startAnimation函数源码:

/*** Start the specified animation now.** @param animation the animation to start now*/
public void startAnimation(Animation animation) {animation.setStartTime(Animation.START_ON_FIRST_FRAME);setAnimation(animation);invalidateParentCaches();invalidate(true);
}

首先设置了START_ON_FIRST_FRAME表示,它的值为-1,相当于调用了Animation的start()函数,然后调用了setAnimation设置了animation方法,之后调用了invalidateParentCaches和invalidate函数。startAnimation这个函数的作用是立即开始执行动画,所以我们就知道了执行动画需要设置以上四个参数。

再看View 的setAnimation的方法:

/*** Sets the next animation to play for this view.* If you want the animation to play immediately, use* {@link #startAnimation(android.view.animation.Animation)} instead.* This method provides allows fine-grained* control over the start time and invalidation, but you* must make sure that 1) the animation has a start time set, and* 2) the view's parent (which controls animations on its children)* will be invalidated when the animation is supposed to* start.** @param animation The next animation, or null.*/
public void setAnimation(Animation animation) {mCurrentAnimation = animation;if (animation != null) {if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF&& animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());}animation.reset();}
}

setAnimation 把animation对象设置给了mCurrentAnimation,然后设置了animation的startTime,最后调用了animation的reset函数。
仔细阅读注释:调用了setAnimation 方法后,如果想让动画执行需要两个条件,第一个是有个开始执行的时间,另外一个是view的父类调用了invalidated方法,这样动画才会执行。
所以还得继续观察invalidateParentCaches函数,内部只是设置了表示,再看invalidate(true)方法。

/*** This is where the invalidate() work actually happens. A full invalidate()* causes the drawing cache to be invalidated, but this function can be* called with invalidateCache set to false to skip that invalidation step* for cases that do not need it (for example, a component that remains at* the same dimensions with the same content).** @param invalidateCache Whether the drawing cache for this view should be*            invalidated as well. This is usually true for a full*            invalidate, but may be set to false if the View's contents or*            dimensions have not changed.* @hide*/
public void invalidate(boolean invalidateCache) {invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

invalidate() 内部其实是调用了 ViewGroup 的 invalidateChild(),内部会一直向上会执行 ViewRootImpl 的 invalidateChildInParent() ,最终触发的是ViewRootImpl 的 performTraversals(),进而执行view的测量,布局,绘制工作。(具体流程会在后续分析view绘制流程时讲解)。

所以需要执行动画时,最终会触发一次view树形结构的遍历绘制工作,动画的执行应该在view的绘制过程中进行。

看View类顶部关于Animation的注释:

* You can attach an {@link Animation} object to a view using
* {@link #setAnimation(Animation)} or
* {@link #startAnimation(Animation)}. The animation can alter the scale,
* rotation, translation and alpha of a view over time. If the animation is
* attached to a view that has children, the animation will affect the entire
* subtree rooted by that node. When an animation is started, the framework will
* take care of redrawing the appropriate views until the animation completes.
* </p>

最后一句当animation 开始运行后,framework 将关注重新绘制view视图知道动画结束,所以动画跟随view的绘制一起执行。对应上面的结论,动画开始时会触发view树的重新绘制。

View绘制过程中会调用view的draw方法,draw方法内部会调用applyLegacyAnimation。

//*** Utility function, called by draw(canvas, parent, drawingTime) to handle the less common* case of an active Animation being run on the view.*/
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,Animation a, boolean scalingRequired) {Transformation invalidationTransform;final int flags = parent.mGroupFlags;final boolean initialized = a.isInitialized();
//动画还没有初始化,就初始化动画并告诉子view,当前view添加了动画
if (!initialized) {a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);onAnimationStart();}final Transformation t = parent.getChildTransformation();
//获取动画是否执行完boolean more = a.getTransformation(drawingTime, t, 1f);if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {if (parent.mInvalidationTransformation == null) {parent.mInvalidationTransformation = new Transformation();}invalidationTransform = parent.mInvalidationTransformation;a.getTransformation(drawingTime, invalidationTransform, 1f);} else {invalidationTransform = t;}//如果动画没有结束,循环调用,会触发view树的遍历绘制if (more) {if (!a.willChangeBounds()) {if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;parent.invalidate(mLeft, mTop, mRight, mBottom);}} else {if (parent.mInvalidateRegion == null) {parent.mInvalidateRegion = new RectF();}final RectF region = parent.mInvalidateRegion;a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,invalidationTransform);parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;final int left = mLeft + (int) region.left;final int top = mTop + (int) region.top;parent.invalidate(left, top, left + (int) (region.width() + .5f),top + (int) (region.height() + .5f));}}return more;
}

applyLegacyAnimation这个函数内部调用了getTransformation函数,最终动画得到执行,getTransformation函数会返回动画是否完成的状态,完成为false,没完成为true,如果没有完成会再次遍历view树进行绘制。

所以viewgroup下的任何一个view执行动画,那么都会导致view执行整个绘制流程,最终会调用viewGroup的dispatchDraw()然后内部又调用drawChild去绘制各个子View,子view内部调用draw方法绘制自身。

view 动画怎么绘制的呢?

既然知道了动画是在view的draw函数中绘制的,我们看一下view的draw函数。
draw三个参数的方法:
可以看到内部获取了Animation和getChildTransformation,然后对画布进行了变换,就实现了对view的动画操作。

 /*** This method is called by ViewGroup.drawChild() to have each child view draw itself*/boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {boolean more = false;Transformation transformToApply = null;boolean concatMatrix = false;final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;//获取动画final Animation a = getAnimation();if (a != null) {//有动画,通知执行more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);concatMatrix = a.willChangeTransformationMatrix();if (concatMatrix) {mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;}//获取Transformtion信息transformToApply = parent.getChildTransformation();} else {。。。。。。}。。。。。。。int restoreTo = -1;//执行动画之前保存画布if (!drawingWithRenderNode || transformToApply != null) {restoreTo = canvas.save();}//对画布进行操作if (offsetForScroll) {canvas.translate(mLeft - sx, mTop - sy);} else {if (!drawingWithRenderNode) {canvas.translate(mLeft, mTop);}if (scalingRequired) {if (drawingWithRenderNode) {// TODO: Might not need this if we put everything inside the DLrestoreTo = canvas.save();}// mAttachInfo cannot be null, otherwise scalingRequired == falsefinal float scale = 1.0f / mAttachInfo.mApplicationScale;canvas.scale(scale, scale);}}float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());if (transformToApply != null|| alpha < 1|| !hasIdentityMatrix()|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {if (transformToApply != null || !childHasIdentityMatrix) {int transX = 0;int transY = 0;if (offsetForScroll) {transX = -sx;transY = -sy;}if (transformToApply != null) {if (concatMatrix) {if (drawingWithRenderNode) {renderNode.setAnimationMatrix(transformToApply.getMatrix());} else {// Undo the scroll translation, apply the transformation matrix,// then redo the scroll translate to get the correct result.canvas.translate(-transX, -transY);canvas.concat(transformToApply.getMatrix());canvas.translate(transX, transY);}parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;}float transformAlpha = transformToApply.getAlpha();if (transformAlpha < 1) {alpha *= transformAlpha;parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;}}if (!childHasIdentityMatrix && !drawingWithRenderNode) {canvas.translate(-transX, -transY);canvas.concat(getMatrix());canvas.translate(transX, transY);}}// Deal with alpha if it is or used to be <1if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {if (alpha < 1) {mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;} else {mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;}parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;if (!drawingWithDrawingCache) {final int multipliedAlpha = (int) (255 * alpha);if (!onSetAlpha(multipliedAlpha)) {if (drawingWithRenderNode) {renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());} else if (layerType == LAYER_TYPE_NONE) {canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),multipliedAlpha);}} else {// Alpha is handled by the child directly, clobber the layer's alphamPrivateFlags |= PFLAG_ALPHA_SET;}}}} else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {onSetAlpha(255);mPrivateFlags &= ~PFLAG_ALPHA_SET;}。。。。。。。//恢复画布if (restoreTo >= 0) {canvas.restoreToCount(restoreTo);}if (more && hardwareAcceleratedCanvas) {if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {// alpha animations should cause the child to recreate its display list//还有动画继续通知invalidate(true);}}mRecreateDisplayList = false;return more;}

绘制子view都会先对画布状态进行保存save(),绘制完后,又会恢复restore(),所以一个view的绘制不会影响另外一个子view的绘制,但如果该view是viewgroup,会影响到其所有的子view的绘制,所以动画发生时不是类似调用invalidate,只绘制view自身,而是由上而下,重绘ViewGroup导致了绘制子View,子view绘制,只是变换了自己所在的画布的坐标系,其实属性没有改变。
Android动画就是通过父view来不断调整子view的画布canvas坐标系来实现的,发生动画的其实是父View而不是该view。所以 补间动画其实只是调整了子view画布canvas的坐标系,其实并没有修改任何属性,所以只能在原位置才能处理触摸事件。

以上我们反向推导了动画执行的过程,下面总结一下:
当view调用了 View.startAnimation() 时动画并没有马上就执行,会触发遍历view树的绘制,
调用到 View 的 draw() 方法,如果 View 有绑定动画,那么会去调用applyLegacyAnimation(),内部调用 getTransformation() 来根据当前时间计算动画进度,紧接着调用 applyTransformation() 并传入动画进度来应用动画。getTransformation() 会返回动画是否执行完成的状态, applyLegacyAnimation() 会根据 getTransformation() 的返回值来决定是否通知 ViewRootImpl 再发起一次遍历请求,遍历 View 树绘制,重复上面的步骤,直到动画结束。

补间动画的绘制实际上是父布局不停地改变自己的Canvas坐标,而子view虽然位置没有变化,但是画布所在Canvas的坐标发生了变化视觉效果也就发生了变化,其实并没有修改任何属性,所以只能在原位置才能处理触摸事件。

Animation动画概述和执行原理相关推荐

  1. 第100天:CSS3中animation动画详解

    CSS3属性中有关于制作动画的三个属性:Transform,Transition,Animation: 一.Animation定义动画 CSS3的Animation是由"keyframes& ...

  2. css帧动画点击执行一次_CSS动画深入浅出

    实现向右移动动画 如果我们想要一个按钮,控制一个div向右移动的动画效果,该怎么实现呢? 有三种实现的方法,下面我们分别介绍三种方法 1.通过设定left 实现思路:通过改变left实现div的右移 ...

  3. Android Animation动画原理分析

    动画在Android应用中是很常用的,而系统提供的Animation相关动画机制由于简单实用,经常是我们实现动画的首要选择.本文旨在分析Animation相关类实现动画的原理,目的是了解动画实现的主要 ...

  4. 【Flutter】Animation 动画 ( AnimatedBuilder 动画使用流程 | 创建动画控制器 | 创建动画 | 创建动画作用的组件 | 关联动画与组件 | 动画执行 )

    文章目录 ◯.AnimatedBuilder 引入 一.创建动画控制器 二.创建动画 三.创建动画作用的组件 四.创建 AnimatedBuilder 关联动画与组件 五.动画运行 六.完整代码示例 ...

  5. html动画效果停顿几秒,css3 animation动画执行结束,停顿几秒后重新开始执行

    要实现css3 animation动画执行结束,停顿几秒后重新开始执行的效果,首先想到的是延时执行:animation-delay,然后设置animation-iteration-count为infi ...

  6. js 监听css3动画的执行,animation动画暂停

    CSS 动画播放时,会发生以下三个事件: animationstart -CSS 动画开始后触发 animationiteration - CSS 动画重复播放时触发 animationend - C ...

  7. 【Flutter】Animation 动画 ( Flutter 动画类型 | Flutter 动画的核心类 )

    文章目录 一.Flutter 动画类型 二.Flutter 动画的核心类 三.相关资源 Flutter Animation 动画 : Flutter 动画类型 为 Widget 组件添加动画 为动画添 ...

  8. jQuery源码解析(5)—— Animation动画

    闲话 jQuery的动画机制有800行, 虽然不如样式的1300行,难度上却是不减.由于事前不了解animate接口的细节使用规则,看代码期间吃了很多苦头,尤其是深恶痛绝的defaultPrefilt ...

  9. Android动画渲染过程及原理(matrix),matrix动画,动画优化

    -- 矩阵(Matrix), Matrix动画 矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,最早来自于方程组的系数及常数所构成的方阵.在物理学中,矩阵于电路学.力学.光学和量子物理中都 ...

最新文章

  1. kosaraju算法
  2. 10、MySQL常用运算符概述
  3. JVM源码阅读-Dalvik类的加载
  4. 时间加减计算器_手机上的计算器这样也可以,太方便了,赶快转告家人朋友
  5. java lambda max_在Java中使用Lambda表达式查找Max
  6. Leetcode-区域和检索-数组不可变
  7. 气象数据源-要素、数据集、空间分辨率、网址
  8. 当鼠标移动到上面时的应用
  9. 十二时辰及经络走向图
  10. IT服务及相关概念界定
  11. SVN clean up报错
  12. Docker 启动nginx报错Error starting userland proxy: listen tcp 0.0.0.0:80: bind: address already in use.
  13. 基于itchat获取微信好友头像
  14. 家政服务:保姆朋友圈鄙视链在上海
  15. python打印输出世界你好!_Python语句 print(\\\世界,你好”)的输出是?
  16. Shiro教程,整合SpringBoot项目实战(笔记)
  17. 传播知识,分享快乐--我的相关资源下载(不定期更新)
  18. 对流氓软件应群起攻之
  19. sql注入解析(四)避开过滤
  20. 《吴忠老年书画作品集》序

热门文章

  1. Android人脸识别的初步学习,移动端开发技术创新
  2. android 人脸 sdk,Android 基于人脸识别 SDK使用总结
  3. 应用计算机技术的纺织行业,计算机技术在纺织行业中的应用
  4. 3.OSPF的协议报文和链路状态通告
  5. STM32F103ZE开发板WS2812B RGB灯带调试
  6. halcon中阈值分割算子用法
  7. 业务流程图与功能流程图到底有什么区别?
  8. Android画一条竖线
  9. 创意黑板彩色粉笔PPT模板
  10. 3.4 Kaggle自然场景图片分类