今日计划:
1.学习帧动画实现一机显示动图效果和淡入淡出动画
2.学习补间动画种类与原理
3.学习并实现集合动画
12.1.2 显示动图特效
GIF是Windows常见的图片格式,主要用来播放短小的动画。Android不支持直接播放GIF动图,如果在图像视图中加载一个GIF文件,那么只会显示GIF文件的第一帧图片
手机上显示GIF动图的具体实现方式:
1.借助帧动画播放拆解后的组图
2.利用Movie类结合自定义控件播放动图
3.利用ImageDecoder结合动画图形播放动图
1.借助帧动画播放拆解后的组图:
这个需要在代码中将GIF文件分解为一系列图片数据,获取每帧的持续时间,然后通过动画图形动态加载每帧图片
从GIF文件中分解帧图片的现成开源代码:
这个开源代码源码630行,而且看不懂,所以知道这个功能是分解GIF文件即可
详细显示GIF动图的示例代码:
// 显示GIF动画
private void showGifAnimationOld(int imageId) {
tv_info.setText("");
// 从资源文件中获取输入流对象
InputStream is = getResources().openRawResource(imageId);
GifImage gifImage = new GifImage(); // 创建一个GIF图像对象
int code = gifImage.read(is); // 从输入流中读取gif数据
if (code == GifImage.STATUS_OK) { // 读取成功
GifImage.GifFrame[] frameList = gifImage.getFrames();
// 创建一个帧动画
AnimationDrawable ad_gif = new AnimationDrawable();
for (GifImage.GifFrame frame : frameList) {
// 把Bitmap位图对象转换为Drawable图形格式
BitmapDrawable drawable = new BitmapDrawable(getResources(), frame.image);
// 给帧动画添加指定图形,以及该帧的播放延迟
ad_gif.addFrame(drawable, frame.delay);
}
// 设置帧动画是否只播放一次。为true表示只播放一次,为false表示循环播放
ad_gif.setOneShot(false);
iv_gif.setImageDrawable(ad_gif); // 设置图像视图的图形为帧动画
ad_gif.start(); // 开始播放帧动画
} else if (code == GifImage.STATUS_FORMAT_ERROR) {
Toast.makeText(this, "该图片不是gif格式", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "gif图片读取失败:" + code, Toast.LENGTH_LONG).show();
}
}
2.利用Movie类结合自定义控件播放动图
借助原生的Movie工具,先加载动图的资源图片,再将每帧图像绘制到视图画布上,使之成为能够播放动图的自定义控件
@Override
protected void onDraw(Canvas canvas) {
long now = SystemClock.uptimeMillis();
if (beginTime == 0){
beginTime = now;
}
if (movie != null){
//获取电影动图的播放时长
int duration = movie.duration() == 0 ? 1000 :movie.duration();
//计算当前要显示第几帧图片
int currentTime = (int) ((now - beginTime) % duration);
movie.setTime(currentTime);//设置当前帧的相对时间
canvas.scale(scaleRatio,scaleRatio);//将画布缩放到指定比例
movie.draw(canvas,0,0);//把当前帧绘制到画布上
postInvalidate();//立即刷新视图(线程安全)
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int width = movie.width();
int height = movie.height();
float widthRatio = 1.0f * getMeasuredWidth() / width;
float heightRatio = 1.0f * getMeasuredHeight() / height;
scaleRatio = Math.min(widthRatio,heightRatio);
}
3.利用ImageDecoder结合动画图形播放动图
ImageDecoder解码器支持直接读取GIF文件的图像数据,通过搭配具备动画特征的图形工具Animatable即可实现在App中播放GIF动图。利用图像解码器加载并显示图片的步骤分为4步:
1.调用ImageDecoder的createSource方法。从指定地方获得数据源
2. 调用ImageDecoder的decodeDrawable方法,从数据源解码得到Drawable类型的图形信息
3.调用图像视图的setImageDrawable方法,设置图像视图的图形对象
4.判断解码得到的图形对象是否为Animatable类型,如果是的话,就调用start方法播放动画
ImageDecoder将构造方法封装到了native的C和C++层
private static native ImageDecoder nCreate(FileDescriptor fd, long length,
boolean preferAnimation, Source src) throws IOException;
其中步骤一的createSource方法允许从多种来源读取图像信息,包括但不限于下列来源:
(1)来自存储卡的File对象
(2)来自系统相册的URI对象
(3)来自资源图片的图形编号(图片放到res/raw目录下,图形编号形如R.raw.***)
(4)判断解码得到的图形对象是否为Animatable类型,如果是的话,就调用start方法播放动画
稍微回忆总结一下存储卡存储读取对象以及系统相册存储读取对象:
存储卡:
//外部存储的私有空间:
//app删除后就没有了
//Path: /storage/emulated/0/Android/data/com.example.day06__androidstudy/files/Download/1690336191443.txt
// directory = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
//外部存储的公共空间:
//app删除后还有
// Path: /storage/emulated/0/Download/1690336826568.txt
directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
//内部存储私有空间:
//Path: /data/user/0/com.example.day06__androidstudy/files/1690337211790.txt
//删除后包没有了
//directory = getFilesDir().toString();
drawable instanceof AnimationDrawable
drawable instanceof Animatable)
Animatable和AnimationDrawable区别:
前者是具备动画特征的图形工具。后者是动画图形
//存储文件并打印路径:
try {
path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString()+ File.separatorChar+"test.webp";
ImageDecoder.Source source = ImageDecoder.createSource(getResources(), R.raw.happy);
Bitmap bitmap = ImageDecoder.decodeBitmap(source);
OutputStream fos = new FileOutputStream(path);
bitmap.compress(Bitmap.CompressFormat.WEBP,80,fos);
Log.d(TAG, "onCreate: "+ path);
} catch (IOException e) {
throw new RuntimeException(e);
}
imageView = findViewById(R.id.image_view);
//读取图片
imageView.setImageBitmap(BitmapFactory.decodeFile(path));
从相册读取见昨天的代码,保存至相册:
path = String.format("%s/%s.webp",
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
"test");
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.picture_internet);
OutputStream os = null;
try {
os = new FileOutputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
bitmap.compress(Bitmap.CompressFormat.WEBP,80,os);
try {
String fileName = path.substring(path.lastIndexOf("/")+1);
MediaStore.Images.Media.insertImage(getContentResolver(),
path, fileName, null);
Uri uri = Uri.parse("file://" + path);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
sendBroadcast(intent);
} catch (Exception e) {
e.printStackTrace();
}
Toast.makeText(this, "已保存抠好的图片 "+path, Toast.LENGTH_SHORT).show();
从相册中读取:
if (item.getItemId() == R.id.open){
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intentActivityResultLauncher.launch(intent);
Log.d(TAG, "onOptionsItemSelected: 执行到了lanuch");
}
实现淡入淡出动画:
过度图形同样需要宿主视图显示该图形,即调用图像视图的setImageDrawable方法进行图形加载操作
TransitionDrawable的常见用法:
构造方法:指定过渡图形的图形数组。该图形数组大小为2,包括前后两张图片:
startTransition:开始过渡操作,这里需要先设置宿主视图再进行渐变显示
resetTransition:重置过渡操作
reverseTransition:倒过来执行过渡操作
public void startAction(){
Drawable[] drawableArray = {
new BitmapDrawable(getResources(),BitmapFactory.decodeFile(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()+
File.separatorChar+"download.png")), drawable
};
TransitionDrawable transitionDrawable = new TransitionDrawable(drawableArray);
transitionDrawable.setCrossFadeEnabled(true);
imageView.setImageDrawable(transitionDrawable);
transitionDrawable.startTransition(3000);
}
补间动画:补间动画的原理与用法
1.4种补间动画及其基本用法
2.补间动画的原理和基于旋转动画的思想实现摇摆动画
3.学习通过集合动画同时展示多种动画效果
种类:
4种动画效果:
1.灰度动画
2.平移动画
3.缩放动画
4.旋转动画
4种补间动画都来自于共同的动画类Animation,因此同时拥有Animation的属性与方法
Animation的常见方法:
setFillAfter:设置是否维持结束画面,true表示结束后停留在结束画面,false表示动画结束后恢复到开始画面
setRepeatMode:设置动画的重播模式。Animation.RESTART表示从头开始,Animation .REVERSE表示倒过来播放
默认为RESTART
setRepeatCount:设置动画的重播次数。默认值为0,表示只播放一次;值为ValueAnimator.INFINITE表示持续重播
setDuration:设置动画的持续时间,单位为毫秒
setInterpolator:设置动画的插值器
setAnimationListener:设置动画的监听器。需实现接口的3个方法:
onAnimationStart:在动画开始时触发
onAnimationEnd:在动画结束时触发
onAnimationRepeat:在动画重播时触发
public void initTweenAnim(){
//灰度动画
animationCorlor = new AlphaAnimation(1.0f,0.1f);
animationCorlor.setDuration(3000);
animationCorlor.setFillAfter(true);
animationCorlor.setRepeatCount(Animation.INFINITE);
//平移动画
animationPosition = new TranslateAnimation(1.0f,1.0f,-100,1.0f);
animationPosition.setDuration(3000);
animationPosition.setFillAfter(true);
animationPosition.setRepeatCount(Animation.INFINITE);
//旋转动画
animationAngel = new RotateAnimation(0f,360f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animationAngel.setDuration(3000);
animationAngel.setFillAfter(true);
animationAngel.setRepeatCount(Animation.INFINITE);
//缩放动画
animationSize = new ScaleAnimation(0f,1.0f,0f,1.0f);
animationSize.setDuration(3000);
animationSize.setFillAfter(true);
animationSize.setRepeatCount(Animation.INFINITE);
}
之后用imageView.startAnimation播放即可
补间动画的原理:
产看RotateAnimation源码:
发现除了一堆构造方法之外剩下的代码之后3个方法:
private void initializePivotPoint() {
if (mPivotXType == ABSOLUTE) {
mPivotX = mPivotXValue;
}
if (mPivotYType == ABSOLUTE) {
mPivotY = mPivotYValue;
}
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();
if (mPivotX == 0.0f && mPivotY == 0.0f) {
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
}
两个初始化方法都在处理圆心的坐标,与动画播放有关的方法之后applyTransformation。该方法很简单,提供了两个参数。第一个参数为插值时间,即逝去的时间所占的百分比;第二个参数为转换器。方法内部根据差值时间计算当前所处的角度数值,最后使用转换器把视图旋转到该角度
集合动画:AnimationSet把几个补间动画组装起来,实现某视图同时展现多种动画的效果
public void initAnimationSet(){
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(animationCorlor);
animationSet.addAnimation(animationPosition);
animationSet.addAnimation(animationAngel);
animationSet.addAnimation(animationSize);
animationSet.setFillAfter(true);
animationSet.setRepeatCount(-1);
imageView.startAnimation(animationSet);
}
属性动画:介绍属性动画的应用场合与进阶用法
1.学习为何属性动画是补间动画的升级版以及属性动画的基本用法
2.学习运用属性动画组合实现多个属性动画的同时播放与顺序播放效果
3.对动画技术中的插值器和估值器进行分析,并演示不同插值器的动画效果
4.学习如何利用股指器实现直播网站的常见打赏动画
Android属性动画ObjectAnimator常用方法:
ofInt:定义整型属性的属性动画
ofFloat:定义浮点型属性的属性动画
ofArgb:定义颜色属性的属性动画
ofObject:定义对象属性的属性动画,用于不是上述三种类型的属性,例如Rect对象
以上四个of方法的第一个参数为宿主视图对象,第二个参数为需要变化的属性名称,第三个参数以及后面的参数为属性变化的各个状态值。of方法后面的参数个数是变化的。如果第三个参数是状态A,第四个参数是状态B,属性动画就从A状态变为B状态;如果第三个参数是状态A、第四个参数是状态B、第五个参数是状态C,属性动画就先从A状态变为B状态,再从B状态变为C状态
插值器和估值器:
插值器用来控制属性的变化速率,可以理解为动画播放的速度,默认是先加速再减速
若要给动画播放指定 某中速率形式,比如匀速播放,调用setInterpolator方法设置对应的插值器实现类即可。补间动画,集合动画,属性动画或者是属性动画集合,都可以设置插值器
今日总结:
1.学习了实现了帧动画的两种实现方式(AnimationDrawable逐帧添加动画/xml文件中animation- list逐个item添加)
2.学习使用三种方式(借助帧动画播放拆解后的组图/利用Movie类结合自定义控件播放动图/利用ImageDecoder结合动画图形播放动图)来实现动图特效(播放GIF图片)
3.学习复习了对存储卡以及系统相册读取操作实现
4.学习了使用TransitionDrawable实现淡入淡出动画
5.复习了Bitmap转Drawable
6.学习了4种补间动画及其基本用法
7.学习补间动画的原理以及底层实现逻辑(initialize方法根据插值时间与转换器实现动画)
8.学习通过集合动画同时展示多种动画效果(AnimationSet)
明日计划:
1.学习常见的属性动画以及属性动画组合
2.学习插值器与估值器
3.学习使用估值器实现打赏动画
4.学习遮罩动画与滚动器并据此实现相应的百叶窗动画和平滑翻页动画
5.如果上述均完成,总结复习第十二章动画特效所学到的知识以及相应的方法