一、帧动画

1.1 什么是帧动画?

帧动画非常容易理解,其实就是简单的由N张静态图片收集起来,然后我们通过控制依次显示 这些图片,因为人眼"视觉残留"的原因,会让我们造成动画的"错觉",跟放电影的原理一样!

而Android中实现帧动画,一般我们会用到前面讲解到的一个Drawable:AnimationDrawable先编写好Drawable,然后代码中调用start()以及stop()开始或停止播放动画

当然我们也可以在Java代码中创建逐帧动画,创建AnimationDrawable对象,然后调用 addFrame(Drawable frame,int duration)向动画中添加帧,接着调用start()和stop()而已

1.2 帧动画使用与效果

案例一:

运行效果:


帧动画案例一


点击开始按钮让坤坤开始打球
点击停止按钮让坤坤停下来

运行及创建流程:

1.首先把gif的每一帧变成图片导入mipmap目录下,可以用gif单帧提取工具来提取gif的每一帧

android帧动画优化 android 帧动画_xml


2.在drawble下创建ikun_gif动画文件

2.1.创建xml文档

android帧动画优化 android 帧动画_帧动画_02

2.2ikun_gif根元素设置为animation_list

android帧动画优化 android 帧动画_xml_03

2.3编写动画

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
			<!--这里的android:oneshot是设置动画是否只是播放一次,true只播放一次,false循环播放!-->
    android:oneshot="false"
    <item android:drawable="@mipmap/a1" android:duration="80" />
    <item android:drawable="@mipmap/a2" android:duration="80" />
    <item android:drawable="@mipmap/a3" android:duration="80" />
    <item android:drawable="@mipmap/a4" android:duration="80" />
    <item android:drawable="@mipmap/a5" android:duration="80" />
    <item android:drawable="@mipmap/a6" android:duration="80" />
    <item android:drawable="@mipmap/a7" android:duration="80" />
    <item android:drawable="@mipmap/a8" android:duration="80" />
    <item android:drawable="@mipmap/a9" android:duration="80" />
    <item android:drawable="@mipmap/a10" android:duration="80" />
    <item android:drawable="@mipmap/a11" android:duration="80" />
    <item android:drawable="@mipmap/a12" android:duration="80" />
    <item android:drawable="@mipmap/a13" android:duration="80" />
    <item android:drawable="@mipmap/a14" android:duration="80" />
    <item android:drawable="@mipmap/a15" android:duration="80" />
</animation-list>

2.4编辑activity_main.xml文档:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    android:padding="20dp"
    tools:context=".MainActivity">
<Button
    android:id="@+id/btn_open"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="开始"
    />
    <Button
        android:id="@+id/btn_stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止"
        />
    <ImageView
        android:id="@+id/img_gif"
        android:layout_gravity="center"
        android:layout_width="300dp"
        android:layout_height="300dp"
android:background="@drawable/ikun_gif"
        />
</LinearLayout>

2.5编辑MainActivity.java文档

package com.houpu.day33work;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {
//设置容器
private Button btn_open;
private Button btn_stop;
private ImageView img_gif;
private AnimationDrawable anim;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取页面控件
        btn_open=findViewById(R.id.btn_open);
        btn_stop=findViewById(R.id.btn_stop);
        img_gif=findViewById(R.id.img_gif);
        anim= (AnimationDrawable) img_gif.getBackground();
        //设置btn_open按钮点击事件
        btn_open.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            //调用Activity生命周期start启动
                anim.start();
            }
        });
        //设置btn_stop按钮点击事件
        btn_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            //调用Activity生命周期stop停止
                anim.stop();
            }
        });
    }
}

案例二:

使用帧动画在指定地方播放帧动画
运行效果:


帧动画案例二



代码实现

1.创建activity_main2.xml页面布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity2">
</androidx.constraintlayout.widget.ConstraintLayout>

2.实现动画文件

详见帧布局案例一2.3编写动画

3.自定义一个ImageView:FrameView.java,这里通过反射获得当前播放的帧, 然后是否为最后一帧,是的话隐藏控件!
上代码:

package com.houpu.day33work;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.lang.reflect.Field;

public class FrameView extends androidx.appcompat.widget.AppCompatImageView {
    AnimationDrawable anim;

    public FrameView(@NonNull Context context) {
        super(context);
    }

    public FrameView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public FrameView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setAnim(AnimationDrawable anim) {
        this.anim = anim;
    }

    public void setLocation(int top, int left){
        setFrame(left, top, left+200, top+200);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        try {
            Field field = AnimationDrawable.class.getDeclaredField("mCurFrame");
            field.setAccessible(true);
                int curframe=field.getInt(anim);
                //            执行到最后一帧 隐藏起来
                if (curframe == anim.getNumberOfFrames()-1) {
                    setVisibility(View.VISIBLE);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
        } catch (NoSuchFieldException e) {
                e.printStackTrace();
        }
        super.onDraw(canvas);
    }

}

4.处理MainActivity2.java文档,创建一个自定义帧布局 对触摸事件做处理,给控件设置背景等
上代码:

package com.houpu.day33work;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

public class MainActivity2 extends AppCompatActivity {
private AnimationDrawable anim;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        自定义一个帧布局
        FrameLayout fly=new FrameLayout(MainActivity2.this);
        setContentView(fly);

//        创建自定义控件
        FrameView fv=new FrameView(MainActivity2.this);
//        给控件设置背景
        fv.setBackgroundResource(R.drawable.ikun_gif);
//        图片不可见
        fv.setVisibility(View.INVISIBLE);
//        将背景图转为动画
        anim= (AnimationDrawable) fv.getBackground();
//        将动画传递给fv,就是为了动画执行完毕后 图片隐藏起来
        fv.setAnim(anim);
//        向布局中添加控件
        fly.addView(fv);

        fly.setOnTouchListener(new View.OnTouchListener() {
             @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            anim.stop();
            float x=event.getX();
            float y=event.getY();
            fv.setLocation((int) y - 40, (int) x - 20);
            fv.setVisibility(View.VISIBLE);
            anim.start();
        }
        return false;
    }
});
    }
}