欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 帧动画基础

程序员文章站 2022-03-16 14:16:09
...

帧动画实现

Android的动画分为三大类:帧动画,补间动画,属性动画。帧动画是实现原理最简单,是在短时间连续播放多张照片,模拟动态画面的效果。

帧动画由动画图形AnimationDrawable生成。

常用方法

方法 解释
addFrame 添加一幅图片帧,并指定时间
setOneShot 设置是否只播放一次
start 开始播放
stop 结束播放
isRunning 是否正在播放

使用

public class FrameAnimationActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView ivFrameAnim;
    private AnimationDrawable ad_frame;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        ivFrameAnim = findViewById(R.id.iv_frame_anim);
        ivFrameAnim.setOnClickListener(this);
        showFrameAnimByCode();
    }

    private void showFrameAnimByCode() {
        // 创建一个帧动画
        ad_frame = new AnimationDrawable();
        // 下面把每帧图片加入到帧动画的队列中
        ad_frame.addFrame(getResources().getDrawable(R.drawable.flow_p1), 50);
        ad_frame.addFrame(getResources().getDrawable(R.drawable.flow_p2), 50);
        ad_frame.addFrame(getResources().getDrawable(R.drawable.flow_p3), 50);
        ad_frame.addFrame(getResources().getDrawable(R.drawable.flow_p4), 50);
        ad_frame.addFrame(getResources().getDrawable(R.drawable.flow_p5), 50);
        ad_frame.addFrame(getResources().getDrawable(R.drawable.flow_p6), 50);
        ad_frame.addFrame(getResources().getDrawable(R.drawable.flow_p7), 50);
        ad_frame.addFrame(getResources().getDrawable(R.drawable.flow_p8), 50);
        // 设置帧动画是否只播放一次。为true表示只播放一次,为false表示循环播放
        ad_frame.setOneShot(false);
        // 设置图像视图的图形为帧动画
        ivFrameAnim.setImageDrawable(ad_frame);
        ad_frame.start(); // 开始播放帧动画
    }
    
    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.iv_frame_anim) {
            if (ad_frame.isRunning()) {  // 判断帧动画是否正在播放
                ad_frame.stop(); // 停止播放帧动画
            } else {
                ad_frame.start(); // 开始播放帧动画
            }
        }
    }
}

除了在代码中添加帧图片,还可以用xml来定义。

frame_anim

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/flow_p1"
        android:duration="50" />
    <item
        android:drawable="@drawable/flow_p2"
        android:duration="50" />
    <item
        android:drawable="@drawable/flow_p3"
        android:duration="50" />
    <item
        android:drawable="@drawable/flow_p4"
        android:duration="50" />
    <item
        android:drawable="@drawable/flow_p5"
        android:duration="50" />
    <item
        android:drawable="@drawable/flow_p6"
        android:duration="50" />
    <item
        android:drawable="@drawable/flow_p7"
        android:duration="50" />
    <item
        android:drawable="@drawable/flow_p8"
        android:duration="50" />
</animation-list>

public class FrameAnimationActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView ivFrameAnim;
    private AnimationDrawable ad_frame;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        ivFrameAnim = findViewById(R.id.iv_frame_anim);
        ivFrameAnim.setOnClickListener(this);
        showFrameAnimByXml();
    }
    
    // 从xml文件中获取帧动画并进行播放
    private void showFrameAnimByXml() {
        // 设置图像视图的图像来源为帧动画的XML定义文件
        ivFrameAnim.setImageResource(R.drawable.frame_anim);
        // 从图像视图对象中获取帧动画
        ad_frame = (AnimationDrawable) ivFrameAnim.getDrawable();
        ad_frame.start(); // 开始播放帧动画
    }


    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.iv_fra--me_anim) {
            if (ad_frame.isRunning()) {  // 判断帧动画是否正在播放
                ad_frame.stop(); /-/ 停止播放帧动画
            } else {
                ad_frame-.sta0rt(); // 开始播放帧动画
            }
        }
    }
}

GIF动画实现

Android并不能直接播放GIF动图,要想GIF动图,可以借助帧动画技术。
在Android9.0之前实现方式可以通过开源框架来实现,9.0之后出现了图像解码器ImageDecoder,搭配Animatable可以轻松实现播放GIF动图。

GifImage

public class GifImage {
    public static final int STATUS_OK = 0;
    public static final int STATUS_FORMAT_ERROR = 1;
    private static final int STATUS_OPEN_ERROR = 2;

    private int status;
    private InputStream in;

    private int width;
    private int height;
    private boolean gctFlag;
    private int gctSize;

    private int[] gct;
    private int[] lct;
    private int[] act;

    private int bgIndex;
    private int bgColor;
    private int lastBgColor;

    private boolean interlace;

    private int ix, iy, iw, ih;
    private int lrx, lry, lrw, lrh;
    private Bitmap image;
    private Bitmap lastImage;

    private byte[] block = new byte[256];
    private int blockSize = 0;

    private int dispose = 0;

    private int lastDispose = 0;
    private boolean transparency = false;
    private int delay = 0;
    private int transIndex;

    private static final int MaxStackSize = 4096;

    private short[] prefix;
    private byte[] suffix;
    private byte[] pixelStack;
    private byte[] pixels;

    private Vector<GifFrame> frames;
    private int frameCount;

    public static class GifFrame {
        public Bitmap image;
        public int delay;
        GifFrame(Bitmap im, int del) {
            image = im;
            delay = del;
        }
    }

    public GifFrame[] getFrames() {
        if (null != frames)
            return frames.toArray(new GifFrame[0]);
        return null;
    }

    public int read(InputStream is) {
        init();
        if (is != null) {
            in = is;
            readHeader();
            if (!err()) {
                readContents();
                if (frameCount < 0) {
                    status = STATUS_FORMAT_ERROR;
                }
            }
        } else {
            status = STATUS_OPEN_ERROR;
        }
        try {
            assert is != null;
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return status;
    }

    private void setPixels() {
        int[] dest = new int[width * height];
        if (lastDispose > 0) {
            if (lastDispose == 3) {
                int n = frameCount - 2;
                if (n > 0) {
                    lastImage = getFrame(n - 1);
                } else {
                    lastImage = null;
                }
            }
            if (lastImage != null) {
                lastImage.getPixels(dest, 0, width, 0, 0, width, height);
                if (lastDispose == 2) {
                    int c = 0;
                    if (!transparency) {
                        c = lastBgColor;
                    }
                    for (int i = 0; i < lrh; i++) {
                        int n1 = (lry + i) * width + lrx;
                        int n2 = n1 + lrw;
                        for (int k = n1; k < n2; k++) {
                            dest[k] = c;
                        }
                    }
                }
            }
        }
        int pass = 1;
        int inc = 8;
        int iline = 0;
        for (int i = 0; i < ih; i++) {
            int line = i;
            if (interlace) {
                if (iline >= ih) {
                    pass++;
                    switch (pass) {
                        case 2:
                            iline = 4;
                            break;
                        case 3:
                            iline = 2;
                            inc = 4;
                            break;
                        case 4:
                            iline = 1;
                            inc = 2;
                    }
                }
                line = iline;
                iline += inc;
            }
            line += iy;
            if (line < height) {
                int k = line * width;
                int dx = k + ix;
                int dlim = dx + iw;
                if ((k + width) < dlim) {
                    dlim = k + width;
                }
                int sx = i * iw;
                while (dx < dlim) {
                    int index = ((int) pixels[sx++]) & 0xff;
                    int c = act[index];
                    if (c != 0) {
                        dest[dx] = c;
                    }
                    dx++;
                }
            }
        }
        image = Bitmap.createBitmap(dest, width, height, Config.RGB_565);
    }

    private Bitmap getFrame(int n) {
        Bitmap im = null;
        if ((n >= 0) && (n < frameCount)) {
            im = frames.elementAt(n).image;
        }
        return im;
    }

    private void decodeImageData() {
        int NullCode = -1;
        int npix = iw * ih;
        int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;

        if ((pixels == null) || (pixels.length < npix)) {
            pixels = new byte[npix]; // allocate new pixel array
        }
        if (prefix == null) {
            prefix = new short[MaxStackSize];
        }
        if (suffix == null) {
            suffix = new byte[MaxStackSize];
        }
        if (pixelStack == null) {
            pixelStack = new byte[MaxStackSize + 1];
        }
        // Initialize GIF data stream decoder.
        data_size = read();
        clear = 1 << data_size;
        end_of_information = clear + 1;
        available = clear + 2;
        old_code = NullCode;
        code_size = data_size + 1;
        code_mask = (1 << code_size) - 1;
        for (code = 0; code < clear; code++) {
            prefix[code] = 0;
            suffix[code] = (byte) code;
        }

        // Decode GIF pixel stream.
        datum = bits = count = first = top = pi = bi = 0;
        for (i = 0; i < npix; ) {
            if (top == 0) {
                if (bits < code_size) {
                    // Load bytes until there are enough bits for a code.
                    if (count == 0) {
                        // Read a new data block.
                        count = readBlock();
                        if (count <= 0) {
                            break;
                        }
                        bi = 0;
                    }
                    datum += (((int) block[bi]) & 0xff) << bits;
                    bits += 8;
                    bi++;
                    count--;
                    continue;
                }
                // Get the next code.
                code = datum & code_mask;
                datum >>= code_size;
                bits -= code_size;

                // Interpret the code
                if ((code > available) || (code == end_of_information)) {
                    break;
                }
                if (code == clear) {
                    // Reset decoder.
                    code_size = data_size + 1;
                    code_mask = (1 << code_size) - 1;
                    available = clear + 2;
                    old_code = NullCode;
                    continue;
                }
                if (old_code == NullCode) {
                    pixelStack[top++] = suffix[code];
                    old_code = code;
                    first = code;
                    continue;
                }
                in_code = code;
                if (code == available) {
                    pixelStack[top++] = (byte) first;
                    code = old_code;
                }
                while (code > clear) {
                    pixelStack[top++] = suffix[code];
                    code = prefix[code];
                }
                first = ((int) suffix[code]) & 0xff;
                // Add a new string to the string table,
                if (available >= MaxStackSize) {
                    break;
                }
                pixelStack[top++] = (byte) first;
                prefix[available] = (short) old_code;
                suffix[available] = (byte) first;
                available++;
                if (((available & code_mask) == 0)
                        && (available < MaxStackSize)) {
                    code_size++;
                    code_mask += available;
                }
                old_code = in_code;
            }

            // Pop a pixel off the pixel stack.
            top--;
            pixels[pi++] = pixelStack[top];
            i++;
        }
        for (i = pi; i < npix; i++) {
            pixels[i] = 0; // clear missing pixels
        }
    }

    private boolean err() {
        return status != STATUS_OK;
    }

    private void init() {
        status = STATUS_OK;
        frameCount = 0;
        frames = new Vector<>();
        gct = null;
        lct = null;
    }

    private int read() {
        int curByte = 0;
        try {
            curByte = in.read();
        } catch (Exception e) {
            status = STATUS_FORMAT_ERROR;
        }
        return curByte;
    }

    private int readBlock() {
        blockSize = read();
        int n = 0;
        if (blockSize > 0) {
            try {
                int count;
                while (n < blockSize) {
                    count = in.read(block, n, blockSize - n);
                    if (count == -1) {
                        break;
                    }
                    n += count;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (n < blockSize) {
                status = STATUS_FORMAT_ERROR;
            }
        }
        return n;
    }

    private int[] readColorTable(int ncolors) {
        int nbytes = 3 * ncolors;
        int[] tab = null;
        byte[] c = new byte[nbytes];
        int n = 0;
        try {
            n = in.read(c);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (n < nbytes) {
            status = STATUS_FORMAT_ERROR;
        } else {
            tab = new int[256];
            int i = 0;
            int j = 0;
            while (i < ncolors) {
                int r = ((int) c[j++]) & 0xff;
                int g = ((int) c[j++]) & 0xff;
                int b = ((int) c[j++]) & 0xff;
                tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
            }
        }
        return tab;
    }

    private void readContents() {
        boolean done = false;
        while (!(done || err())) {
            int code = read();
            switch (code) {
                case 0x2C:
                    readImage();
                    break;
                case 0x21:
                    code = read();
                    switch (code) {
                        case 0xf9:
                            readGraphicControlExt();
                            break;

                        case 0xff:
                            readBlock();
                            StringBuilder app = new StringBuilder();
                            for (int i = 0; i < 11; i++) {
                                app.append((char) block[i]);
                            }
                            if (app.toString().equals("NETSCAPE2.0")) {
                                readNetscapeExt();
                            } else {
                                skip();
                            }
                            break;
                        default:
                            skip();
                    }
                    break;

                case 0x3b:
                    done = true;
                    break;

                case 0x00:
                    break;
                default:
                    status = STATUS_FORMAT_ERROR;
            }
        }
    }

    private void readGraphicControlExt() {
        read();
        int packed = read();
        dispose = (packed & 0x1c) >> 2;
        if (dispose == 0) {
            dispose = 1;
        }
        transparency = (packed & 1) != 0;
        delay = readShort() * 10;
        transIndex = read();
        read();
    }

    private void readHeader() {
        StringBuilder id = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            id.append((char) read());
        }
        if (!id.toString().toUpperCase().startsWith("GIF")) {
            status = STATUS_FORMAT_ERROR;
            return;
        }
        readLSD();
        if (gctFlag && !err()) {
            gct = readColorTable(gctSize);
            bgColor = gct[bgIndex];
        }
    }

    private void readImage() {
        ix = readShort();

        iy = readShort();

        iw = readShort();

        ih = readShort();

        int packed = read();
        boolean lctFlag = (packed & 0x80) != 0;

        interlace = (packed & 0x40) != 0;
        int lctSize = 2 << (packed & 7);
        if (lctFlag) {
            lct = readColorTable(lctSize);
            act = lct;
        } else {
            act = gct;
            if (bgIndex == transIndex) {
                bgColor = 0;
            }
        }
        int save = 0;
        if (transparency) {
            save = act[transIndex];
            act[transIndex] = 0;
        }
        if (act == null) {
            status = STATUS_FORMAT_ERROR;
        }
        if (err()) {
            return;
        }
        decodeImageData();
        skip();
        if (err()) {
            return;
        }
        frameCount++;
        image = Bitmap.createBitmap(width, height, Config.RGB_565);

        setPixels();
        frames.addElement(new GifFrame(image, delay));

        if (transparency) {
            act[transIndex] = save;
        }
        resetFrame();
    }

    private void readLSD() {
        width = readShort();
        height = readShort();
        int packed = read();
        gctFlag = (packed & 0x80) != 0;
        gctSize = 2 << (packed & 7);
        bgIndex = read();
    }

    private void readNetscapeExt() {
        do {
            readBlock();
        } while ((blockSize > 0) && !err());
    }

    private int readShort() {
        return read() | (read() << 8);
    }

    private void resetFrame() {
        lastDispose = dispose;
        lrx = ix;
        lry = iy;
        lrw = iw;
        lrh = ih;
        lastImage = image;
        lastBgColor = bgColor;
        dispose = 0;
        transparency = false;
        delay = 0;
        lct = null;
    }

    private void skip() {
        do {
            readBlock();
        } while ((blockSize > 0) && !err());
    }
}
public class GifActivity extends AppCompatActivity {

    private ImageView iv_gif;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gif);
        // 从布局文件中获取名叫iv_gif的图像视图
        iv_gif = findViewById(R.id.iv_gif);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            // 利用Android9.0新增的AnimatedImageDrawable显示GIF动画
            showGifAnimationNew();
        } else {
            // 显示GIF动画
            showGifAnimationOld();
        }
    }

    @TargetApi(Build.VERSION_CODES.P)
    private void showGifAnimationNew() {
        try {
            // 利用Android9.0新增的ImageDecoder读取gif图片
            ImageDecoder.Source source = ImageDecoder.createSource(
                    getResources(), R.drawable.welcome);
            // 从数据源中解码得到gif图形数据
            Drawable gifDrawable = ImageDecoder.decodeDrawable(source);
            // 设置图像视图的图形为gif图片
            iv_gif.setImageDrawable(gifDrawable);
            // 如果是动画图形,则开始播放动画
            if (gifDrawable instanceof Animatable) {
                ((Animatable) iv_gif.getDrawable()).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 显示GIF动画
    private void showGifAnimationOld() {
        // 从资源文件welcome.gif中获取输入流对象
        InputStream is = getResources().openRawResource(R.raw.welcome);
        // 创建一个GIF图像对象
        GifImage gifImage = new GifImage();
        // 从输入流中读取gif数据
        int code = gifImage.read(is);
        if (code == GifImage.STATUS_OK) { // 读取成功
            GifImage.GifFrame[] frameList = gifImage.getFrames();
            // 创建一个帧动画
            AnimationDrawable ad_gif = new AnimationDrawable();
            for (GifImage.GifFrame frame : frameList) {
                // 把Bitmap位图对象转换为Drawable图形格式
                BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), frame.image);
                // 给帧动画添加指定图形,以及该帧的播放延迟
                ad_gif.addFrame(bitmapDrawable, 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();
        }
    }
}