Android 帧动画基础

帧动画实现

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();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/m0_48440239/article/details/115047718