天啦噜!原来Android属性动画也不过如此
前两篇《天啦噜!原来Android补间动画可以这么玩》和《天啦噜!原来Android帧动画这么简单》重点讲述了Android开发过程中补间动画和帧动画知识点,本篇文章我们重点总结一下属性动画的使用和原理。
Android动画系列:
- 《天啦噜!原来Android补间动画可以这么玩》
- 《天啦噜!原来Android帧动画这么简单》
- 《天啦噜!原来Android属性动画也不过如此》
什么是属性动画
在一段时间内通过修改对象的属性而形成的动画叫属性动画(Property Animation),Google官方在Android 3.0添加Property Animation
。属性动画的主要是修改对象的属性,如 View 的背景颜色、透明值、位置等。
属性动画和补间动画的区别
有同学可能会问不是已经有补间动画吗,为什么要引入属性动画?换句话说,Property Animation 到底能干哪些 Tween Animation 不能干的活呢?
Tween Animation 存在的问题:
- Tween Animation 只能作用于 View,不能作用于普通 Object 的属性。
- Tween Animation 只能改变 View 的一部分属性。Tween Animation 只支持修改 View 的这几个方面:Alpha、Scale、Translate、Rotate 和这些的组合,一旦想要改变的 View 的属性不在这个范围内,Tween Animation 就无能为力了,如 View 的 BackgroundColor。
- Tween Animation 只能改变 View 的“表面”位置,不能改变 View 的实际位置。
属性动画相关类
属性动画涉及的类主要有:
- Animator,所有 Animator 的父类,主要用于定义通用的接口。
- AnimatorSet,主要用于组合多个属性动画。
- ValueAnimator,属性动画的一种,主要用于根据起始值和终止值产生动画,只负责产生在起始值和终止值之间的值,
不负责更新界面,需要用户自己实现更新界面的逻辑。 - ObjectAnimator,属性动画的一种,主要用于根据起始值和终止值产生动画,并将动画产生的值设置在目标对象上。
- TimeAnimator,提供了一个简单的回调机制,通过 TimeAnimator.TimeListener,在动画的每一帧处通知你。这个动画器没有时间,插值或是对象值设定。回调监听器为每一帧动画接受信息,包括总运行时间和从前一帧到现在的运行时间。
继承结构如下:
ValueAnimator和ObjectAnimator主要区别
该类作为ValueAnimator的子类不仅继承了ValueAnimator的所有方法和特性,并且还封装很多实用的方法,方便开发人员快速实现动画。同时,由于属性值会自动更新,使用ObjectAnimator实现动画不需要像ValueAnimator那样必须实现 ValueAnimator.AnimatorUpdateListener ,因此实现任意对象的动画显示就更加容易了。我们在大部分的开发工作中,都会使用ObjectAnimator而非ValueAnimator实现我们所需的动画效果。
属性动画实现形式
属性动画的实现形式有两种:xml创建和code实现。其中xml创建的xml动画文件要放在res/animator
目录下,注意此处和补间动画(Tween Animation)存放位置不同。
通常情况下属性动画一般建议通过代码进行实现,因为他更灵活,尤其是在自定义View中常常有属性动画的身影。当然也需要根据实际场景自行选择,下边就通过这两种形式来总结一下属性动画几个类的使用。
ValueAnimator
ValueAnimator是Property Animation系统的核心类,它包含了配置Property Animation属性的大部分方法,那要实现一个Property Animation,都需要直接或间接使用ValueAnimator类。
一般使用ValueAnimator实现动画分为以下几个步骤:
- 调用ValueAnimation类中的ofInt(int…values)、ofFloat(String propertyName,float…values)等静态方法实例化ValueAnimator对象;
- 调用addUpdateListener(AnimatorUpdateListener mListener)方法为ValueAnimator对象设置属性变化的监听器,并在AnimatorUpdateListener 中的实现方法为目标对象的属性设置计算好的属性值。
- 创建自定义的插值器(Interpolator),调用setInterpolator(TimeInterpolator value)为ValueAniamtor设置自定义的Interpolator;(可选,不设置默认为缺省值)
- 创建自定义的估值器(TypeEvaluator),调用setEvaluator(TypeEvaluator value)为ValueAnimator设置自定义的TypeEvaluator;(可选,不设置默认为缺省值)
- 设置动画的持续时间、是否重复及重复次数等属性;
- 为ValueAnimator设置目标对象并开始执行动画。
需要注意目标对象的需要被设置的属性必须拥有get\set
方法,格式类似 set()。
通过XML创建
语法:
<animatorxmlns:android="http://schemas.android.com/apk/res/android"android:duration="int"android:interpolator="@[package:]anim/interpolator_resource"android:valueType=["intType" | "floatType"]android:valueFrom="float | int | color"android:valueTo="float | int | color"android:startOffset="int"android:repeatCount="int"android:repeatMode=["repeat" | "reverse"]/>
示例:
//1. 创建 value_animator.xml
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"android:duration="1800"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:valueType="floatType"android:valueFrom="-100"android:valueTo="800"android:startOffset="0"android:repeatCount="infinite"android:repeatMode="reverse"/>//2. 在代码中使用 value_animator
ValueAnimator mValueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.value_animator);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mTarget.setY((Float) animation.getAnimatedValue());}
});
mValueAnimator.start();
通过代码实现
语法:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(float... values);
valueAnimator.setDuration(long duration);
valueAnimator.setInterpolator(TimeInterpolator value);
valueAnimator.addUpdateListener(AnimatorUpdateListener listener);
…
valueAnimator.start();
示例:
ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 800);
mValueAnimator.setDuration(1800);
mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mTarget.setY((Float) animation.getAnimatedValue());}
});
mValueAnimator.start();
ObjectAnimator
要动画显示 View 对象的某个属性,比如颜色或旋转值,我们所有要做的事情就是创建一个 Property animation,并设定对应的 View 属性。那接下来我们就用ObjectAnimator类来分别实现View的透明度渐变、收缩、移动和旋转等动画效果,那在此之前我们也来总结下使用ObjectAnimator实现动画的几个步骤,如下:
- 通过调用ofFloat()、ofInt()等方法创建ObjectAnimator对象,并设置目标对象、需要改变的目标属性名、初始值和结束值;
- 设置动画的持续时间、是否重复及重复次数等属性;
- 启动动画。
常用的几个属性值解释:
- translationX 和 translationY:这两个属性控制着 View 的屏幕位置坐标变化量,以 layout 容器的左上角为坐标原点;
- rotation、rotationX 和 rotationY:这三个属性控制着 2D 旋转角度(rotation属性)和围绕某枢轴点的 3D 旋转角度;
- scaleX、scaleY:这两个属性控制着 View 围绕某枢轴点的 2D 缩放比例;
- pivotX 和 pivotY: 这两个属性控制着枢轴点的位置,前述的旋转和缩放都是以此点为中心展开的,缺省的枢轴点是 View 对象的中心点;
- x 和 y:这是指 View 在容器内的最终位置,等于 View 左上角相对于容器的坐标加上 translationX 和 translationY 后的值;
- alpha:表示 View 的 alpha 透明度。缺省值为 1 (不透明),为 0 则表示完全透明(看不见);
通过XML创建
语法:
<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"android:duration="int"android:interpolator="@[package:]anim/interpolator_resource"android:propertyName="string"android:valueType=["intType" | "floatType"]android:valueFrom="float | int | color"android:valueTo="float | int | color"android:startOffset="int"android:repeatCount="int"android:repeatMode=["repeat" | "reverse"]/>
示例:
//1. 创建 object_animator.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"android:duration="1800"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:propertyName="Y"android:valueType="floatType"android:valueFrom="0"android:valueTo="800"android:startOffset="0"android:repeatCount="infinite"android:repeatMode="reverse"/>//2. 在代码中使用 object_animator
ObjectAnimator mObjectAnimator = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.object_animator);
mObjectAnimator.setTarget(mTarget);
mObjectAnimator.start();
通过代码实现
语法:
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values);
objectAnimator.setDuration(long duration);
objectAnimator.setInterpolator(TimeInterpolator value);
…
objectAnimator.start();
示例:
ObjectAnimator mObjectAnimator = ObjectAnimator.ofFloat(mTarget, "y", 0, 800);
mObjectAnimator.setDuration(1800);
mObjectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
mObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
mObjectAnimator.start();
AnimatorSet
通过XML创建
语法:
<setxmlns:android="http://schemas.android.com/apk/res/android"android:ordering=["together" | "sequentially"]><objectAnimatorandroid:propertyName="string"android:duration="int"android:valueFrom="float | int | color"android:valueTo="float | int | color"android:startOffset="int"android:repeatCount="int"android:repeatMode=["repeat" | "reverse"]android:valueType=["intType" | "floatType"]/><animatorandroid:duration="int"android:valueFrom="float | int | color"android:valueTo="float | int | color"android:startOffset="int"android:repeatCount="int"android:repeatMode=["repeat" | "reverse"]android:valueType=["intType" | "floatType"]/><set>...</set>
</set>
示例:
//1. 创建 animator_set.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"android:ordering="together"><objectAnimatorandroid:duration="@integer/integer_one_thousand_and_eight_hundred"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:propertyName="Y"android:valueType="floatType"android:valueFrom="0"android:valueTo="800"android:startOffset="0"android:repeatCount="infinite"android:repeatMode="reverse"/><objectAnimatorandroid:duration="@integer/integer_one_thousand_and_eight_hundred"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:propertyName="ScaleX"android:valueType="floatType"android:valueFrom="1"android:valueTo="2"android:startOffset="0"android:repeatCount="infinite"android:repeatMode="reverse"/><objectAnimatorandroid:duration="@integer/integer_one_thousand_and_eight_hundred"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:propertyName="ScaleY"android:valueType="floatType"android:valueFrom="1"android:valueTo="2"android:startOffset="0"android:repeatCount="infinite"android:repeatMode="reverse"/>
</set>//2. 在代码中使用 animator_set
AnimatorSet mAnimatorSet = (AnimatorSet)AnimatorInflater.loadAnimator(this, R.animator.animator_set);
mAnimatorSet.setTarget(mTarget);
mAnimatorSet.start();
通过代码实现
语法:
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(Animator... items);
animatorSet.playSequentially(Animator... items);
//非必须
animatorSet.setTarget(mTarget);
…
animatorSet.start();
示例:
ObjectAnimator translateYObjectAnimator = ObjectAnimator.ofFloat(mTarget, "y", 0, 800);
translateYObjectAnimator.setDuration(1800);
translateYObjectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
translateYObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
translateYObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator scaleXObjectAnimator = ObjectAnimator.ofFloat(mTarget, "scaleX", 1, 2);
scaleXObjectAnimator.setDuration(1800);
scaleXObjectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
scaleXObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
scaleXObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator scaleYObjectAnimator = ObjectAnimator.ofFloat(mTarget, "scaleY", 1, 2);
scaleYObjectAnimator.setDuration(1800);
scaleYObjectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
scaleYObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
scaleYObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(translateYObjectAnimator, scaleXObjectAnimator, scaleYObjectAnimator);
mAnimatorSet.playSequentially();
//非必须
// mAnimatorSet.setTarget(mTarget);
mAnimatorSet.start();
监听属性动画
Property Animation 中一共有三种监听事件:
- AnimatorListener;
- AnimatorPauseListener;
- AnimatorUpdateListener;
AnimatorListener
AnimatorListener 接口主要用于监听 Property Animation 的开始、结束、取消、重复状态,需要实现的方法分别是:
@Override
public void onAnimationStart(Animator animation) {}@Override
public void onAnimationEnd(Animator animation) {}@Override
public void onAnimationCancel(Animator animation) {}@Override
public void onAnimationRepeat(Animator animation) {}
AnimatorPauseListener
AnimatorPauseListener 主要用于监听 Property Animation 的暂停、恢复状态,需要实现的方法分别是:
@Override
public void onAnimationPause(Animator animation) {}@Override
public void onAnimationResume(Animator animation) {}
AnimatorUpdateListener
AnimatorUpdateListener 是 ValueAnimator 及其子类特有的接口,主要用于监听动画中值的变化,用于手动更新界面,需要实现的方法是:
@Override
public void onAnimationUpdate(ValueAnimator animation) {}
属性动画工作原理
当 ValueAnimator 调用 start 方法之后,ValueAnimator 会根据 Property Animation 当前运行时间与总的动画持续时间计算出一个时间消耗百分数(The elapsed fraction)。紧接着,ValueAnimator 将这个时间消耗百分数交给当前 ValueAnimator 的插值器(Interpolator),不同的 Interpolator 会根据不同的算法将这个时间消耗百分数转换成插值百分数(The interpolated fraction)。紧接着,ValueAnimator 会将这个插值百分数交给当前 ValueAnimator 的估值器(TypeEvaluator),不同的 TypeEvaluator 会根据不同的算法将这个插值百分数转换最终的动画值(The final value)。
拿AccelerateDecelerateInterpolator插值器举例:
public class AccelerateDecelerateInterpolator extends BaseInterpolatorimplements NativeInterpolatorFactory {public AccelerateDecelerateInterpolator() {}@SuppressWarnings({"UnusedDeclaration"})public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {}/*** param input (The elapsed fraction)* return (The interpolated fraction)*/public float getInterpolation(float input) {return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;}/** @hide */@Overridepublic long createNativeInterpolator() {return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();}
}
上面这个属性动画的 Duration 为 40ms,Intepolator 为 AccelerateDecelerateInterpolator,Distance 为 40。
在 t = 10ms 时,The elapsed fraction 为 0.25 = 10/40,The interpolated fraction = (float)(Math.cos((0.25 + 1) * Math.PI) / 2.0f) + 0.5f = 0.14644662,The final value 为 5.8578648 = (40 - 0) * 0.14644662。
自定义插值器
自定义插值器要实现 Interpolator 接口,上篇文章已经有所说明,不做过多阐述。
public class DecelerateAccelerateInterpolator implements Interpolator {@Overridepublic float getInterpolation(float input) {return (float) ((Math.tan(Math.PI/2 * input - Math.PI/4) + 1)/2);}
}
自定义估值器
自定义估值器,只要实现 TypeEvaluator 接口,并实现其中定义的 evaluate 方法即可:
public class CustomTypeEvaluator implements TypeEvaluator {/*** param fraction 插值器最终值* param startValue 属性开始值* param endValue 属性结束值*/@Overridepublic Object evaluate(float fraction, Object startValue, Object endValue) {float startFloat = ((Number) startValue).floatValue();return 200 + fraction * (((Number) endValue).floatValue() - startFloat);}
}
ViewPropertyAnimator 使用简介
ViewPropertyAnimator、ObjectAnimator、ValueAnimator 这三种 Animator,它们其实是一种递进的关系:从左到右依次变得更加难用,也更加灵活。
它们的性能是一样的,因为 ViewPropertyAnimator 和 ObjectAnimator 的内部实现其实都是 ValueAnimator,ObjectAnimator 更是本来就是 ValueAnimator 的子类,它们三个的性能并没有差别。
它们的差别只是使用的便捷性以及功能的灵活性。所以在实际使用时候的选择,只要遵循一个原则就行:尽量用简单的。能用 View.animate() 实现就不用 ObjectAnimator,能用 ObjectAnimator 就不用 ValueAnimator。
当需要同时更改 View 的多个属性的时候,一般有三种方法:
- ObjectAnimator + AnimatorSet;
- PropertyValuesHolder + ObjectAnimator;
- ViewPropertyAnimator;
接下来,分别用三种方法分别实现同一种效果:View 的 Y 值从当前位置增到 400,Alpha 值 从 1.0f 变成 0.1f。
ObjectAnimator + AnimatorSet
ObjectAnimator alphaObjectAnimator = ObjectAnimator.ofFloat(mTarget, "alpha", 1.0f, 0.1f);
ObjectAnimator yObjectAnimator = ObjectAnimator.ofFloat(mTarget, "y", 400f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(alphaObjectAnimator, yObjectAnimator);
animatorSet.start();
PropertyValuesHolder + ObjectAnimator
PropertyValuesHolder alphaPropertyValuesHolder = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.1f);
PropertyValuesHolder yPropertyValuesHolder = PropertyValuesHolder.ofFloat("y", 400f);
ObjectAnimator.ofPropertyValuesHolder(mTarget, alphaPropertyValuesHolder, yPropertyValuesHolder).start();
ViewPropertyAnimator
ViewPropertyAnimator viewPropertyAnimator = mTarget.animate();
viewPropertyAnimator.alpha(0.1f);
viewPropertyAnimator.y(400f);//也可以写成一句:
mTarget.animate().alpha(0.1f).y(400f);
PropertyValuesHolder
细心的同学可能会注意到,ValueAnimator、ObjectAnimator除了这些创建Animator实例的方法以外,都还有一个方法:
/*** valueAnimator的*/
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values)
/*** ObjectAnimator的*/
public static ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)
PropertyValuesHolder这个类的意义就是,它其中保存了动画过程中所需要操作的属性和对应的值。我们通过ofFloat(Object target, String propertyName, float… values)构造的动画,ofFloat()的内部实现其实就是将传进来的参数封装成PropertyValuesHolder实例来保存动画状态。在封装成PropertyValuesHolder实例以后,后期的各种操作也是以PropertyValuesHolder为主的。
使用举例:
PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 60f, -60f, 40f, -40f, -20f, 20f, 10f, -10f, 0f);
PropertyValuesHolder colorHolder = PropertyValuesHolder.ofInt("BackgroundColor", 0xffffffff, 0xffff00ff, 0xffffff00, 0xffffffff);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView, rotationHolder, colorHolder);
animator.setDuration(3000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
最后
我是i猩人,总结不易,转载注明出处,喜欢本篇文章的童鞋欢迎点赞、关注哦。
参考
- https://developer.android.com/reference/android/animation/Animator
- https://carsonho.blog.csdn.net/article/details/72909894
- https://blog.csdn.net/harvic880925/article/details/50752838
- https://juejin.cn/post/6844903798687678478#heading-34
天啦噜!原来Android属性动画也不过如此相关推荐
- android动画封装,Android属性动画封装,快速构建动画
Android实现动画效果的方式主要有帧动画.补间动画.属性动画.关于安桌动画的基础知识可以查看这篇文章Android属性动画完全解析 这里我要讲的是如何快速构建出一个动画效果,如下图: 如果我们用属 ...
- Android属性动画 ObjectAnimator
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/118709616 本文出自[赵彦军的博客] 文章目录 ObjectAnimator ...
- (转)Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
版权声明:本文出自郭霖的博客,转载必须注明出处. 目录(?)[-] ValueAnimator的高级用法 ObjectAnimator的高级用法 转载请注明出处:http://blog.csdn.ne ...
- android 属性动画实例,Android属性动画完全解析 中 ,ValueAnimator和ObjectAnimator的高级用法...
大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法,当然也是最常用的一些用法,这些用法足以覆盖我们平时大多情况下的动画需求了.但是,正如上篇文章当中所说到的,属性动画对补间动画进行了 ...
- Android 系统(196)---Android 属性动画
Android 属性动画 属性动画 总结&攻略 前言 动画的使用 是 Android 开发中常用的知识 本文将详细介绍 Android 动画中 属性动画的原理 & 使用 动画类型 关于 ...
- Android 属性动画Property Animation(中)
Android 属性动画Property Animation(上)介绍了属性动画的概念以及相关的类和接口,本篇来看下具体肿么使用. ValueAnimator ValueAnimator指定整形.浮点 ...
- Android 属性动画使用(二)
首先扯点别的:晚上稍微跑了一会步,然后逛了超市,晚饭喝的南瓜粥,吃了一碗面条,今天不是太饿,现在正一边吃着葡萄一边学习,也是没谁了. 比如说,我们想要实现从0过渡到100,使用ValueAnimato ...
- android+属性动画+高度,android 自定义view+属性动画实现充电进度条
近期项目中需要使用到一种类似手机电池充电进度的动画效果,以前没学属性动画的时候,是用图片+定时器的方式来完成的,最近一直在学习动画这一块,再加上复习一下自定义view的相关知识点,所以打算用属性动画和 ...
- Android 属性动画(一)新手入门
一.属性动画简介 Android 中动画有很多种,属性动画就是其中的一种.所谓的属性动画,就是在指定的时间内,通过改变对象的属性达到变化效果的动画.在 Android 中,属性动画系统是一个强健的框架 ...
- Android 属性动画 详解
Android 属性动画 详解 Android动画类型: View Animation(即所谓的Tween Animation补间动画):View Animation相当简单,不过只能支持简单的缩放. ...
最新文章
- 神策数据陈世键:融合媒体渠道转型破局策略
- vscode怎么引用css_今天来安装一个骚气的 VS Code 主题
- 使用继承思想,去开发一款组件(element-ui collapse组件为例子)
- media player 控件播放音乐与视频 0130 winform
- SpringMVC中获得HttpRequest对象的方法
- jQuery中兄弟元素、子元素和父元素的获取
- java 下一代_Java 下一代: 混入和特征
- SQlite数据库的C编程接口(六) 返回值和错误码(Result Codes and Error Codes) ——《Using SQlite》读书笔记
- CentOS安装并设置MariaDB
- 软件设计原则(一) 单一职责原则
- Android动态生成答题卡,手机扫描答题卡改卷的最佳选择——ZipGrade
- 视频音频剪辑合并软件 免费强大 LosslessCut
- ionic 中使用 slidebox 利用angular ng-repeat 渲染后不显示问题
- C#文件和文件文件夹排序
- Android日常整理(一)---android返回键、Fragment、android分割线、button图片间距的设置
- PPT学习整理(六)从入门到放弃。
- 嵌入式了解 以及学习路线
- 【Educational Codeforces Round 61 (Rated for Div. 2)】A.B.C.D.E.F.G
- Hexo之静态+动态背景设置
- 关于RISC-V成为印度国家指令集的一些看法