安卓核心技术高级——动画与图形
动画类型
参考博客:https://www.cnblogs.com/ldq2016/p/5407061.html
Android系统提供了很多丰富的API去实现UI的2D与3D动画,最主要的划分可以分为如下几类:
-
View Animation: 视图动画在古老的Android版本系统中就已经提供了,只能被用来设置View的动画。
-
Drawable Animation: 这种动画(也叫Frame动画、帧动画)其实可以划分到视图动画的类别,专门用来一个一个的显示Drawable的resources,就像放幻灯片一样。
-
Property Animation: 属性动画只对Android 3.0(API 11)以上版本的Android系统才有效,这种动画可以设置给任何Object,包括那些还没有渲染到屏幕上的对象。这种动画是可扩展的,可以让你自定义任何类型和属性的动画。
属性动画(Property)
上下翻转:
public void click(View view){
ObjectAnimator.ofFloat(view,"rotationX",0.0f,360f).setDuration(500).start();
}
组合多个动画
public void click(View view){
PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("alpha",1f,0f,1f);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("scaleX",1f,0f,1f);
PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("scaleY",1f,0f,1f);
ObjectAnimator.ofPropertyValuesHolder(view,p1,p2,p3).setDuration(3000).start();
}
下落动画
public void musicStackClick(View view){
final View v = view;
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
ValueAnimator va = ValueAnimator.ofFloat(view.getY(),dm.heightPixels,view.getY()).setDuration(500);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
v.setTranslationY((Float) animation.getAnimatedValue());
}
});
va.start();
}
补间动画(View Animation)
Tween动画
Tween动画可以对对象进行缩小,放大,旋转,渐变,位移等操作,针对这不同的操作,Android提供了相应的接口。
有四种:
- AlphaAnimation(透明度动画)
- TranslateAnimation(平移动画)
- ScaleAnimation(缩放动画)
- RotateAnimation(旋转动画)
在res/anim目录下创建xml。
渐变:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:fillEnabled="true">
<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="3000"/>
</set>
public void alphaClick(View view){
Animation alpha = AnimationUtils.loadAnimation(this,
R.anim.alpha_anim);
imageView.startAnimation(alpha);
}
旋转:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:fillEnabled="true">
<rotate
android:duration="2000"
android:fromDegrees="0"
android:interpolator="@android:anim/accelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="720"/>
<rotate
android:duration="2000"
android:fromDegrees="360"
android:interpolator="@android:anim/accelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="2000"
android:toDegrees="0"/>
</set>
public void rotateClick(View view){
Animation alpha = AnimationUtils.loadAnimation(this,
R.anim.anim_rotate);
imageView.startAnimation(alpha);
}
缩放:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:fillEnabled="true">
<scale
android:duration="2000"
android:fillAfter="true"
android:fromXScale="1"
android:fromYScale="1"
android:interpolator="@android:anim/decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="1"
android:repeatMode="reverse"
android:toXScale="2.0"
android:toYScale="2.0" />
</set>
public void scaleClick(View view){
Animation alpha = AnimationUtils.loadAnimation(this,
R.anim.anim_scale);
imageView.startAnimation(alpha);
}
平移:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:fillEnabled="true">
<translate
android:duration="2000"
android:fillAfter="true"
android:fromXDelta="0"
android:fromYDelta="0"
android:repeatCount="1"
android:repeatMode="reverse"
android:toXDelta="860"
android:toYDelta="0"/>
</set>
public void translateClick(View view){
Animation alpha = AnimationUtils.loadAnimation(this,
R.anim.translate_anim);
imageView.startAnimation(alpha);
}
Frame动画
Frame动画是一系列图片按照顺序展示的,像gif图一样,也被称作逐帧动画,它的实现方式也非常简单,可以定义在xml中。
帧动画(Drawable Animation)
import android.graphics.drawable.AnimationDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.widget.ImageView;
public class Main2Activity extends AppCompatActivity {
private ImageView image_frame;
private AnimationDrawable anim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
image_frame = (ImageView)findViewById(R.id.image_frame);
}
//开始帧动画
public void startClick(View view){
if (anim == null) {
image_frame.setBackgroundResource(R.drawable.frame_anim);
anim = (AnimationDrawable) image_frame.getBackground();
}
anim.start();
}
//停止帧动画
public void stopClick(View view){
if (anim != null && anim.isRunning()) { //如果正在运行,就停止
anim.stop();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/b" android:duration="500" />
<item android:drawable="@drawable/c" android:duration="500" />
<item android:drawable="@drawable/d" android:duration="500" />
<item android:drawable="@drawable/e" android:duration="500" />
<item android:drawable="@drawable/e" android:duration="500" />
<item android:drawable="@drawable/d" android:duration="500" />
<item android:drawable="@drawable/c" android:duration="500" />
<item android:drawable="@drawable/b" android:duration="500" />
</animation-list>
监听动画的事件
public void click(View view){
final View v = view;
ObjectAnimator oa = ObjectAnimator.ofFloat(view,"alpha",1f,0f).setDuration(1000);
// oa.addListener(new Animator.AnimatorListener() {
// @Override
// public void onAnimationStart(Animator animation) {
// //动画开始`
// }
//
// @Override
// public void onAnimationEnd(Animator animation) {
// //动画结束
// ViewGroup viewGroup = (ViewGroup) v.getParent();
// if (viewGroup!=null){
// viewGroup.removeView(v);
// System.out.println("removed.");
// }
// }
//
// @Override
// public void onAnimationCancel(Animator animation) {
// //取消动画
//
// }
//
// @Override
// public void onAnimationRepeat(Animator animation) {
// //重复
//
// }
// });
oa.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
ViewGroup viewGroup = (ViewGroup) v.getParent();
if (viewGroup!=null){
viewGroup.removeView(v);
System.out.println("removed.");
}
}
});
oa.start();
}
AnimatorSet
public void click(View view){
ObjectAnimator a1 = ObjectAnimator.ofFloat(view,"translationX",0f,200f);
ObjectAnimator a2 = ObjectAnimator.ofFloat(view,"translationY",0f,200f);
ObjectAnimator a3 = ObjectAnimator.ofFloat(view,"rotation",0f,200f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
// set.playTogether(a1,a2,a3);//同时执行
// set.setStartDelay(300);//延迟
set.playSequentially(a1,a2,a3); //顺序执行
//自定义组合执行
// set.play(a1).with(a2);
// set.play(a3).after(a2);
set.start();
}
同时执行:
顺序执行:
自定义组合执行
插值器
参考博客:https://blog.****.net/qq_33706840/article/details/81475879
- AccelerateDecelerateInterpolator 变化率开始和结束缓慢但在中间加速。
- AccelerateInterpolator 变化率开始缓慢然后加速。
- AnticipateInterpolator 变化开始向后然后向前。
- AnticipateOvershootInterpolator 变化开始向后,向前晃动并超过目标值,然后最终返回到最终值。
- BounceInterpolator 变化在结束时反弹。
- CycleInterpolator 动画重复指定的周期数。
- DecelerateInterpolator 变化率快速开始然后减速。
- LinearInterpolator 变化率恒定。
- OvershootInterpolator 变化向前晃动并超过一个值,然后返回。
- TimeInterpolator 一个允许您实现自己的插补器的接口。
反弹效果
public void click(View view){
ObjectAnimator a1 = ObjectAnimator.ofFloat(view,"translationX",0f,200f);
ObjectAnimator a2 = ObjectAnimator.ofFloat(view,"translationY",0f,200f);
ObjectAnimator a3 = ObjectAnimator.ofFloat(view,"rotation",0f,200f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.setInterpolator(new BounceInterpolator());//反弹效果
set.play(a1).with(a3);
set.play(a2).after(a3);
set.start();
}
动画菜单案例
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.widget.ImageView;
import java.util.ArrayList;
public class Main3Activity extends AppCompatActivity implements View.OnClickListener{
private int[] res = {
R.id.image1,
R.id.image2,
R.id.image3,
R.id.image4,
R.id.image5,
R.id.image6,
R.id.image7,
R.id.image8
};
private ArrayList<ImageView> list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
for (int i = 0 ;i<res.length;i++){
ImageView imageView = (ImageView)findViewById(res[i]);
imageView.setOnClickListener(this);
list.add(imageView);
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.image1:
openMenu();
break;
}
}
//打开菜单
private void openMenu() {
for (int i = 1; i < res.length; i++) {
ObjectAnimator a1 = ObjectAnimator.ofFloat(list.get(i),"translationX",0,150*i).setDuration(300);
ObjectAnimator a2 = ObjectAnimator.ofFloat(list.get(i),"translationY",0,100*i).setDuration(300);
a1.setInterpolator(new BounceInterpolator());
a2.setInterpolator(new BounceInterpolator());
a1.start();
a2.start();
}
}
}
画布绘制
绘制图形
public class Main4Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
}
package com.example.animatorapplication.graphics;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.graphics.*;
public class MyView extends View {
public MyView(Context context) {
super(context);
}
//会在组件加载时调用
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();//创建画笔
//去锯齿
paint.setAntiAlias(true);
//设置样式,空心STROKE,实心FILL
paint.setStyle(Paint.Style.STROKE);
//画笔颜色
paint.setColor(Color.RED);
//设置画笔宽度
paint.setStrokeWidth(3);
//画空心圆(参数:圆心x,圆心y,半径,画笔)
canvas.drawCircle(40,40,30,paint);
//画空心正方形
canvas.drawRect(10,90,70,150,paint);
//画空心长方形
canvas.drawRect(10,170,70,200,paint);
//画空心椭圆
RectF re = new RectF(10,220,70,250);
canvas.drawOval(re,paint);
//画一个空心三角形
Path path = new Path();
path.moveTo(10,330);
path.lineTo(70,330);
path.lineTo(40,270);
path.close();
canvas.drawPath(path,paint);
//画一个空心梯形
Path path1 = new Path();
path1.moveTo(10,410);
path1.lineTo(40,410);
path1.lineTo(55,350);
path1.lineTo(25,350);
path1.close();
canvas.drawPath(path1,paint);
//设置样式,空心STROKE,实心FILL
paint.setStyle(Paint.Style.FILL);
//画笔颜色
paint.setColor(Color.BLUE);
//设置画笔宽度
paint.setStrokeWidth(3);
//画圆(参数:圆心x,圆心y,半径,画笔)
canvas.drawCircle(120,40,30,paint);
//画正方形
canvas.drawRect(90,90,150,150,paint);
//画长方形
canvas.drawRect(90,170,150,200,paint);
//画椭圆
RectF re2 = new RectF(90,220,150,250);
canvas.drawOval(re2,paint);
//画一个三角形
Path path2 = new Path();
path2.moveTo(90,330);
path2.lineTo(150,330);
path2.lineTo(120,270);
path2.close();
canvas.drawPath(path2,paint);
//画一个梯形
Path path3 = new Path();
path3.moveTo(90,410);
path3.lineTo(120,410);
path3.lineTo(135,350);
path3.lineTo(105,350);
path3.close();
canvas.drawPath(path3,paint);
/**
* 绘制渐变色
* 参数(前四个值,渐变的初始位置,起始x,y轴,结束x,y轴,
* 渐变的颜色组合new int[]{},
* 定义每个颜色的占比,
* 平铺方式:镜像MIRROR)
*/
Shader mShader = new LinearGradient(0,0,100,100,new int[]{
Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW},null,
Shader.TileMode.REPEAT);
paint.setShader(mShader);
paint.setStrokeWidth(3);
//画圆(参数:圆心x,圆心y,半径,画笔)
canvas.drawCircle(200,40,30,paint);
//画正方形
canvas.drawRect(170,90,230,150,paint);
//画长方形
canvas.drawRect(170,170,230,200,paint);
//画椭圆
RectF re3 = new RectF(170,220,230,250);
canvas.drawOval(re3,paint);
//画一个三角形
Path path4 = new Path();
path4.moveTo(170,330);
path4.lineTo(230,330);
path4.lineTo(200,270);
path4.close();
canvas.drawPath(path4,paint);
//画一个梯形
Path path5 = new Path();
path5.moveTo(170,410);
path5.lineTo(200,410);
path5.lineTo(215,350);
path5.lineTo(185,350);
path5.close();
canvas.drawPath(path5,paint);
canvas.drawText("圆形",240,50,paint);
canvas.drawText("正方形",240,120,paint);
canvas.drawText("长方形",240,190,paint);
canvas.drawText("椭圆形",240,250,paint);
canvas.drawText("三角形",240,320,paint);
canvas.drawText("梯形",240,390,paint);
}
}
绘制图片
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
import com.example.animatorapplication.R;
public class MyImageView extends View {
public MyImageView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p = new Paint();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a);
canvas.drawBitmap(bitmap,0,0,p);
}
}
SurfaceView
SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface。你可以控制这个Surface的格式和尺寸。Surfaceview控制这个Surface的绘制位置。可以直接从内存或者DMA等硬件接口取得图像数据,因此是个非常重要的绘图容器。
传统View及其派生类的更新只能在UI线程,然而UI线程还同时处理其他交互逻辑,这就无法保证View更新的速度和帧率了,而SurfaceView可以用独立的线程进行绘制,因此可以提供更高的帧率。
例如游戏,摄像头取景等场景就比较适合SurfaceView来实现。
写入到Surface的内容可以直接复制到显存从而显示出来,这使得显示速度会非常快。
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder;
private MyThread thread;
public MySurfaceView(Context context) {
super(context);
holder = this.getHolder();
holder.addCallback(this);
}
class MyThread implements Runnable{
private SurfaceHolder holder;
public boolean isRun = true;
public MyThread(SurfaceHolder holder){
this.holder = holder;
isRun = true;
}
@Override
public void run() {
int count = 0;
Canvas canvas= null;
while (isRun){
try {
synchronized (holder){
canvas = holder.lockCanvas();
canvas.drawColor(Color.WHITE);
Paint p = new Paint();
p.setColor(Color.RED);
p.setStyle(Paint.Style.FILL);
p.setAntiAlias(true);
canvas.drawRect(60,60,360,360,p);
p.setTextSize(50);
canvas.drawText("当前是第"+(count++)+"秒",60,450,p);
Thread.sleep(1000);
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
holder.unlockCanvasAndPost(canvas);
}
}
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new MyThread(holder);
thread.isRun = true;
new Thread(thread).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
thread.isRun = false;
}
}
SurfaceView视频
先将文件放入sdcard
加权限
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEM" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import com.example.animatorapplication.R;
import java.io.IOException;
public class VideoActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private SurfaceView surfaceView;
private SurfaceHolder holder;
private MediaPlayer mp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video);
surfaceView = (SurfaceView)findViewById(R.id.surfaceView);
holder = surfaceView.getHolder();
holder.addCallback(this);
holder.setFixedSize(320,220);
}
public void startClick(View view) {
mp.start();
}
public void pauseClick(View view) {
mp.pause();
}
public void stopClick(View view) {
mp.stop();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mp = new MediaPlayer();
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
mp.setDisplay(holder);
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"/jj.mp4";
// raw:/storage/emulated/0/Download/jj.mp4
System.out.println(path);
try {
mp.setDataSource(path);//播放视频源
mp.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mp!=null){
if (mp.isPlaying()){
mp.stop();
mp.release();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mp!=null){
if (mp.isPlaying()){
mp.stop();
mp.release();
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".graphics.VideoActivity"
android:padding="16dp">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="485dp"
android:layout_height="300dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="播放"
android:onClick="startClick"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/button2"
app:layout_constraintTop_toBottomOf="@+id/surfaceView" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="暂停"
android:onClick="pauseClick"
app:layout_constraintLeft_toRightOf="@+id/button1"
app:layout_constraintRight_toLeftOf="@+id/button3"
app:layout_constraintTop_toBottomOf="@+id/surfaceView" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止"
android:onClick="stopClick"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toRightOf="@+id/button2"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/surfaceView"
tools:layout_editor_absoluteY="324dp" />
</android.support.constraint.ConstraintLayout>
Draw 9-patch
参考博客:http://www.cnblogs.com/qianxudetianxia/archive/2011/04/17/2017591.html
与传统的png 格式图片相比, 9.png 格式图片在图片四周有一圈一个像素点组成的边沿,该边沿用于对图片的可扩展区和内容显示区进行定义。 这种格式的图片在android 环境下具有自适应调节大小的能力。