支持GIF动画的ImageView

    网上有很多关于怎么实现android播放GIF的帖子。但是本人发现,其中多多少少都有些不如人意的地方。因此,花了几天时间,重写了ImageView以实现GIF图片的播放。在此小结一下,也希望可以给后来者一点参考。

    大致我们会在网上搜到下面四种解决方法:

    【方案一】用外部工具拆分GIF

    【方案二】用Android开源项目GifView包

    【方案三】手动解码GIF

    【方案四】用系统自带的类Movie

    本文采用方案四,继承ImageView实现GIF动画播放,支持ImageView的命名空间属性设置,支持ImageView通用接口。

 

    项目源码下载地址:

    http://download.csdn.net/detail/yarkey09/6499717


【什么是GIF】 

    GIF,就我的理解,就是很多张位图图片的集合,然后使用了某种编码方式,使得它可以体积很小但是又够清晰。由于体积小,不依赖特别的平台,所以GIF很流行。

好吧,知道的就这么多,各位看官想了解清楚的话还是请自行百度吧。不过了解了大概概念,我们就可以知道,其实让GIF播放,实际就是显示多张图片而已。

 

【方案一】用外部工具拆分GIF

    大概情况是这样:

    1,首先我们得有一张GIF (提示:选择赏心悦目的动画,可以提高学习兴趣哦^_^)

    2,然后使用工具,千刀万剐将GIF分成多张图片 => pic0.png,pic1.png,pic2.png,pic3.png,pic4.png,pic5.png

    3,接着编写android xml资源文件放在drawable目录下,说明各个帧图片以及时间duration

    4,然后代码里面使用AnimationDrawable类即可实现

    四张图片按照xml定义的时间,一张张切换,看起来就是动画了!

    动画资源文件格式是这样:(drawable/anim_gif.xml)

<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:duration="150" android:drawable="@drawable/pic0" />
<item android:duration="150" android:drawable="@drawable/pic1" />
<item android:duration="150" android:drawable="@drawable/pic2" />
<item android:duration="150" android:drawable="@drawable/pic3" />
<item android:duration="150" android:drawable="@drawable/pic4" />
<item android:duration="150" android:drawable="@drawable/pic5" />
</animation-list>

    布局文件可以是这样:

<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/anim_gif"
android:id="@+id/imgGif"></ImageView>

    代码是这样:

ImageView imageView = (ImageView) findViewById(R.id.imgGif);
Object ob = imageView.getBackground();
AnimationDrawable anim = (AnimationDrawable) ob;
anim.start();

    如果我们需要在界面上显示一个简单且固定的动画,单纯用于点缀画面,增强动感,这种方法比较方便。当然,这种方法在某些场合下显得很不灵活,不能满足要求效果,那么请继续参考后面的方法吧。

参考帖子:Android开发:教您如何让Gif动画动起来

 

【方案二】用Android开源项目GifView包

    我们同样可以在网上搜到这个开源项目的相关应用。有了这个包,我们要让GIF播放这个事情就变得非常轻松。看看它那强大的接口就知道了!使用GifView几乎就跟ImageView是一样的。方便!开源项目确实有很多代码都有非常好的学习价值,表示有空应该好好拜读一番!

// 从xml中得到GifView的句柄 
gif1 = (GifView) findViewById(R.id.gif1); 
// 设置Gif图片源 
gif1.setGifImage(R.drawable.gif1); 
// 添加监听器 
gif1.setOnClickListener(this); 
// 设置显示的大小,拉伸或者压缩 
gif1.setShowDimension(300, 300); 
// 设置加载方式:先加载后显示、边加载边显示、只显示第一帧再显示
gif1.setGifImageType(GifImageType.COVER); 

参考帖子:介绍一个Android开源项目:GifView——Android显示GIF动画

 

【方案三】手动解码GIF

    用java来解码,很多人会觉得效率比较低。但是我们目的是学习,完全可以尝试一下!当然,也可以用native代码完成解码,在java用JNI调用。

    将GIF文件解码后,我们可以得到所有想要的信息。比如Gif版本GIF87a, GIF89a等等,关键是我们可以得到几张Bitmap图片,还有各张图片的显示延续时间。其实,这里解码工作也就大致等同于上面的方案一。不同的是,我们的app可以直接播放GIF,而不需要外部的工具!

    有了各帧图片以及显示延续时间,我们便可以开始了!新建一个线程用于计时,时间一到就刷新View切换图片。这就是GIF了!

    注意一下在非主线程让View刷新,应该调用postInvalidate() 而不是invalidate()。

    下面参考帖子附有解码源程序,然后按照参考文档来阅读,很快可以看明白^_^

参考帖子: Android 解码播放GIF图像

参考文档: GIF文件格式

 

【方案四】用系统自带的类Movie

    接下来说说具体要讲的基于Movie的实现方法吧!

    使用Movie类播放GIF很简单。但是我们的目的是,继承ImageView,保留它显示图片的基本功能,尽量使得接口函数能够通用简便。这样,原先使用ImageView的项目代码只要经过少量的修改,即可支持GIF动画。根据要求,我们至少要重载下面四个通用接口以支持GIF动画:

public void setImageResource( int resID )
public void setImageURI( Uri uri )
public void setScaleType( ScaleType scaleType )
public void setPadding( int left, int top, int right, int bottom )

     说明一下:

// 我们设置了图片,那么跟ImageView一样显示出图片
setImageResource( R.drawable.pngtest ); 
// 我们这次设置了GIF动画,那么应该显示动画
setImageResource( R.drawable.giftest ); 
// 支持SD卡中的GIF动画
setImageURI( Uri.parse( "file://" + Environment.getExternalStorageDirectory().getPath() + "/sdcard_giftest.gif" ); 

    为了实现以上要求,其中遇到很多问题,我们慢慢说吧。

 

-1- Movie 是啥东西

    android.graphics.Movie 在SDK文档中没有说明,翻看源代码,发现它只是一个java壳,实际上直接调用native代码。这样导致我们没能快速学习掌握它的用法。

    不过幸亏有APIDemo!这真的是一个好东西!打开其中BitmapDecode我们可以发现代码中就用了Movie类!

    直接安装APIDemo到手机中,运行... 发现旗子飘动起来了!

    它的源代码简单清晰,大概是这样。Movie对象管理着时间轴上对应的GIF各帧图片,我们通过传入时间,便可以取出对应的帧,然后再用draw()方法,将当前的帧画到画布canvas上面。如果我们的View不停的刷新,时间不停地跑,Movie的帧就不停的切换,那么画出来的View就动起来了!

 

-2- Copy APIDemo 源码

    那么好了,按照它的代码,我们可以很快copy一份出来,然后编译安装到手机,我们想GIF似乎就这样完成了。关键代码如下。

	private static class MovieGifView extends View {

		private Movie mMovie;
		private long mMovieStart;

		public MovieGifView(Context context) {
			super(context);
			java.io.InputStream is;
			is = context.getResources().openRawResource(R.drawable.animated_gif);
			mMovie = Movie.decodeStream(is);
		}

		@Override
		protected void onDraw(Canvas canvas) {

			long now = android.os.SystemClock.uptimeMillis();
			if (mMovieStart == 0) { // first time
				mMovieStart = now;
			}
			if (mMovie != null) {
				int dur = mMovie.duration();
				if (dur == 0) {
					dur = 1000;
				}
				int relTime = (int) ((now - mMovieStart) % dur);
				mMovie.setTime(relTime);
				mMovie.draw(canvas, getWidth() - mMovie.width(), getHeight() - mMovie.height());
				invalidate();
			}
		}
	}

    但是结果却是那么不如人意,自己写的app在一部平板(android 4.3)上运行时,GIF没有动起来,美女并没有向我眨眼!

    我第一反应便是拿去我的屌丝神机I589(I5830电信版 android 2.3) 上试试。结果反而动起来了!这么神马回事?!

    难道是android 4.3 版本太新,Movie方法不支持?后来我又找到了一部android 4.1 的手机,安装发现,GIF同样没有动!

    奇怪!头疼!

 

-3- hardwareAccelerated 惹的祸

    为什么APIDemo的代码可以,我的代码直接copy,却不行了?我翻看了很久代码,最后找到了唯一不同点,在这里 -> AndroidManifest.xml

        <activity android:hardwareAccelerated="false"
                  android:name=".graphics.BitmapDecode" android:label="Graphics/BitmapDecode">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.SAMPLE_CODE" />
            </intent-filter>
        </activity>

    BitmapDecode Activity 属性设置中,有个东东不曾相识→ → android:hardwareAccelerated="false",不使能硬件加速?什么概念?

    于是便开始查看各种说明,大概意思我是这么理解的:硬件加速并不是什么新鲜的东西,已经运用于windows composition 或 OpenGL games等等。而android 在 3.0之后的版本开始支持。但是它现在暂时只支持standard widgets and drawables。一旦使能硬件加速的特性,所有的画图工作都交给GPU来做。

    但是,我们现在是自定义View类,使用Movie类的draw()方法画图,这个方法并没有在硬件加速支持列表(如下)中找到踪影。

    The following table describes the support level of various operations across API levels:

         
  API level
  < 16 16 17 18
Canvas
drawBitmapMesh() (colors array)
drawPicture()
drawPosText()
drawTextOnPath()
drawVertices()
setDrawFilter()
clipPath()
clipRegion()
clipRect(Region.Op.XOR)
clipRect(Region.Op.Difference)
clipRect(Region.Op.ReverseDifference)
clipRect() with rotation/perspective

详见官方说明: Android 3.0 (API level 11) Hardware Acceleration

    所以,我们认为硬件加速不支持Movie draw()方法。而I589(android 2.3)本身没有这个特性,所以不出现问题。而android 4.3平板具备硬件加速并默认开启,而我们没有关掉,所以出了问题。我准备尝试关了这个特性再试试,再不行就死给你看!

    关闭Hardware Acceleeration可以有几种方法,针对不同的级别(Application, Activity, Window, View )。具体请详见官方说明。

    为了影响最小,可以使用View级别的 setLayerType(View.LAYER_TYPE_SOFTWARE, null);

    关掉了硬件加速,我的GIF终于动起来了!美女开始眨眼,多么好看的GIF动画!欢呼吧!\(^o^)/

 

-4- 继承ImageView

    GIF已经动起来了,感觉事情已经搞定了一大半。但是后面发现,实际上不是这样的!

    再次说说我们的设计目标:设计一个类可以通过设置 Resource ID或者URI 播放图片和GIF动画。

    我们自然想到继承ImageView,然后加入GIF功能代码。这样可以节省很多代码。但是查看ImageView源码,很多private成员变量, private成员函数,真是让人望而却步呀。但是,耐心的啃一啃ImageView的源码,大概还是可以看出思路的。

 

-5- 解读ImageView源码

源码链接:Android ImageView.java 源码在线阅读

    个人认为ImageView源码最关键的部分,也是我们继承它最需要考虑的问题有两点:

    - 控件的大小 (onMeasure()回调方法)

    - 图片的大小与位置 (configureBounds()私有方法)

 

    a) 图片的大小与位置

    先谈谈图片的大小与位置吧,因为待会它在onMeasure方法中会用到。

    两个关键的成员变量 mDrawable, mDrawMatrix。一个是"图片",一个是矩阵。mDrawable调节自身大小颜色透明度等等。mDrawMatrix则定义了画布的缩放平移旋转等等。在onDraw()方法调用之前,我们必须设置好这两个变量,才能画出正确的图形。ImageView对这两个变量的配置,大部分工作在configureBounds()方法中完成。

    configureBounds()私有方法,根据当前View的大小(除去Padding部分)、Drawable实际大小、以及ScaleType参数,设置了图片最终要显示的大小以及对齐等属性。而padding参数的平移效果,在onDraw()中通过平移画布实现。

    // yarkey@20131029 : 
    // configureBounds()方法的产物:mDrawable,mDrawMatrix


    // onDraw()方法将使用这里的mDrawable,mDrawMatrix作画



    private void configureBounds() {
        if (mDrawable == null || !mHaveFrame) {
            return;
        }

        int dwidth = mDrawableWidth;
        int dheight = mDrawableHeight;

        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        boolean fits = (dwidth < 0 || vwidth == dwidth) &&
                       (dheight < 0 || vheight == dheight);
        
        // yarkey@20131029 : 以下根据ScaleType设置mDrawMatrix
        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* If the drawable has no intrinsic size, or we're told to
                scaletofit, then we just fill our entire view.
            */
            // yarkey@20131029 : fitXY的情况较简单,直接将Drawable缩放至View的大小即可(除去padding)
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            // We need to do the scaling ourself, so have the drawable
            // use its native size.



            // yarkey@20131029 : 获取图片固有的大小
              // dwidth = mDrawable.getIntrinsicWidth()
            // dheight = mDrawable.getIntrinsicHeight()
            // 涉及设备像素密度(density),图片存放目录(drawable-mdpi/drawable-hdpi)
            mDrawable.setBounds(0, 0, dwidth, dheight);

            if (ScaleType.MATRIX == mScaleType) {
                // Use the specified matrix as-is.
                if (mMatrix.isIdentity()) {
                    mDrawMatrix = null;
                } else {
                    mDrawMatrix = mMatrix;
                }
            } else if (fits) {
                // The bitmap fits exactly, no transform needed.
                mDrawMatrix = null;
            } else if (ScaleType.CENTER == mScaleType) {
                // Center bitmap in view, no scaling.
                // yarkey@20131029 : 按原图大小居中显示,超过View长宽则截取中间部分
                mDrawMatrix = mMatrix;
                mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
                                         (int) ((vheight - dheight) * 0.5f + 0.5f));
            } else if (ScaleType.CENTER_CROP == mScaleType) {
                mDrawMatrix = mMatrix;

                float scale;
                float dx = 0, dy = 0;
                // yarkey@20131029 : centerCrop,长宽算出比例,然后取比例“大”的
                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight; 
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
                mDrawMatrix = mMatrix;
                float scale;
                float dx;
                float dy;
                // yarkey@20131029 : centerCrop,长宽算出比例,然后取比例“小”的                
                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                    scale = Math.min((float) vwidth / (float) dwidth,
                            (float) vheight / (float) dheight);
                }
                
                dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
                dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(dx, dy);
            } else {
                // yarkey@20131029 : 剩下fitCenter,fitStart,fitEnd三种
                   // Generate the required transform.
                mTempSrc.set(0, 0, dwidth, dheight);
                mTempDst.set(0, 0, vwidth, vheight);
                
                mDrawMatrix = mMatrix;
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
            }
        }
    }

    一旦mDrawable或者mDrawMatrix需要改变的时候,configureBounds() 方法就会被调用。大概是这样子的:

    支持GIF动画的ImageView_第1张图片

    只要调用invalidate()方法,onDraw()方法就会被调用,从而刷新界面。

    我们现在关心的是,播放GIF的时候,我们手上是Movie对象,而不是Drawable对象。因此,用于Drawable的位置计算,不能适用于Movie的场合。ImageView可以通过Drawable setBounds()方法设置大小,Movie却没有这种方法,因此我们只能通过缩放画布(canvas)来实现相同的效果。

    至于怎么用Matrix矩阵来变换画图,请移步:

参考帖子:Android Matrix理论与应用详解

 

    b) 控件的大小
    控件的大小需要在onMeasure()方法中设置,使用setMeasuredDimension()方法。根据输入参数int widthMeasureSpec, int heightMeasureSpec 可以得到父容器(Layout)提供的Measure模式Mode以及参考大小Size。MeasureSpec.UNSPECIFIED, MeasureSpec.AT_MOST, MeasureSpec.EXACTLY。

    对应这三种模式的不同设置方法,ImageView源码注释得很清楚(resolveAdjustedSize()方法中)。我们到时copy它的代码就可以(因为是private方法,子类不可以调用)。

    重写onMeasure()方法,我们归为一点:把里面的mDrawable替换为mMovie即可。

    但是,执行完onMeasure()后,如果立即去getWidth(), getHeight(),我们只会得到旧值!如果想要onMeasure()后,立即计算图片的缩放移动旋转参数,那么需要用getMeasuredWidth()和getMeasuredHeight()代替。

    调用requestLayout()方法,可以触发onMeasure()方法。

 

-6- MovieImageView

    代码比较长,这里直接贴上一个测试没有问题的版本。

    这里只实现新增方法setMovie(),如果想实现setImageResource(), setImageUri(),只要将以下的代码稍作修改,将Movie对象的初始化部分放到MovieImageView类里头完成,即可实现!

package com.yarkey.giftest2;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;


public class MovieImageView extends ImageView {

	private static final boolean DB = true;
	private static final boolean DB_DETAIL = false;
	private static final String DB_TAG = "MovieImageView";

	/** Feature Support GIF */
	private static final boolean FEATURE_IS_GIF_SUPPORTED = true;

	/** @see #syncParentParameter() */
	private int mSuperPaddingTop;
	private int mSuperPaddingLeft;
	private int mSuperPaddingRight;
	private int mSuperPaddingBottom;
	private ScaleType mSuperScaleType;
	private Matrix mSuperDrawMatrix;

	/** mMovie==null means we work the same as parent(ImageView) */
	private Movie mMovie = null;

	private Matrix mMatrix;
	private Matrix mDrawMatrix;

	private long mMovieStartTime = 0;
	private long mMovieDuration = 0;

	private int mDefLayerType;

	// AdjustViewBounds behavior will be in compatibility mode for older apps.
	private boolean mAdjustViewBoundsCompat = false;

	public MovieImageView(Context context) {
		super(context);
		initGifAndImageView();
	}

	public MovieImageView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public MovieImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initGifAndImageView();
	}

	private void prepareForMovie(boolean isToDo) {
		if (FEATURE_IS_GIF_SUPPORTED && isToDo) {
			if (getLayerType() != View.LAYER_TYPE_SOFTWARE) {
				setLayerType(View.LAYER_TYPE_SOFTWARE, null);
			}
			setWillNotCacheDrawing(false);
			mMovieStartTime = 0;
		} else if (mDefLayerType != 0 && mDefLayerType != getLayerType()) {
			setLayerType(mDefLayerType, null);
			mMovie = null;
		}
	}

	/**
	 * You may open an inputstream of certain GIF file, and then decode by
	 * Movie.decodeStream.
	 * 
	 * @param movie
	 */
	public void setMovie(Movie movie) {
		Log("setMovie");
		if (FEATURE_IS_GIF_SUPPORTED && mMovie != movie) {
			mMovie = movie;
			if (mMovie != null) {
				prepareForMovie(true);
				mMovieDuration = mMovie.duration();
				requestLayout();
				// configureDrawMatrix();//will get called after onMeasures
			} else {
				prepareForMovie(false);
			}
			invalidate();
		}
	}

	private void syncParentParameter() {
		Log("syncParentParameter");
		mSuperPaddingTop = getPaddingTop();
		mSuperPaddingLeft = getPaddingLeft();
		mSuperPaddingRight = getPaddingRight();
		mSuperPaddingBottom = getPaddingBottom();
		mSuperScaleType = getScaleType();
		mSuperDrawMatrix = getImageMatrix();
	}

	@Override
	public void setImageBitmap(Bitmap bm) {
		prepareForMovie(false);
		super.setImageBitmap(bm);
	}

	@Override
	public void setImageDrawable(Drawable drawable) {
		prepareForMovie(false);
		super.setImageDrawable(drawable);
	}

	@Override
	public void setImageResource(int resId) {
		prepareForMovie(false);
		super.setImageResource(resId);
	}

	@Override
	public void setImageURI(Uri uri) {
		prepareForMovie(false);
		super.setImageURI(uri);
	}

	@Override
	public void setScaleType(ScaleType scaleType) {
		super.setScaleType(scaleType);
		configureDrawMatrix();
	}

	@Override
	public void setImageMatrix(Matrix matrix) {
		super.setImageMatrix(matrix);
		// We should do the following whether we are in "MovieMode" or not,
		// because we can not get the matrix from
		// parent later.
		if (matrix != null && matrix.isIdentity()) {
			matrix = null;
		}
		if (matrix == null && !mMatrix.isIdentity() || matrix != null && !mMatrix.equals(matrix)) {
			mMatrix.set(matrix);
			configureDrawMatrix();
			invalidate();
		}
	}

	@Override
	public void setPadding(int left, int top, int right, int bottom) {
		super.setPadding(left, top, right, bottom);
		configureDrawMatrix();
	}

	private void initGifAndImageView() {
		Log("initGifAndImageView");
		if (FEATURE_IS_GIF_SUPPORTED) {
			mMatrix = new Matrix();
			mDefLayerType = getLayerType();
		}
		mAdjustViewBoundsCompat = this.getContext().getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
	}

	private void configureDrawMatrix() {
		Log("configureDrawMatrix");
		if (!FEATURE_IS_GIF_SUPPORTED || mMovie == null) {
			return;
		}

		// getWidth/Height() aren't valid until after a layout
		if (getMeasuredHeight() == 0 || getMeasuredWidth() == 0)
			return;

		syncParentParameter();

		int movieWidth = mMovie.width();// 实际像素
		int movieHeight = mMovie.height();
		Log("movieWidth = " + movieWidth + ", movieHeight = " + movieHeight);

		// in pixels
		// int vWidth = getWidth() - mSuperPaddingLeft - mSuperPaddingRight;
		// int vHeight = getHeight() - mSuperPaddingTop - mSuperPaddingBottom;

		int vWidth = getMeasuredWidth() - mSuperPaddingLeft - mSuperPaddingRight;
		int vHeight = getMeasuredHeight() - mSuperPaddingTop - mSuperPaddingBottom;

		Log("vWidth = " + vWidth + ", vHeight = " + vHeight);

		if (ScaleType.CENTER == mSuperScaleType) {
			mDrawMatrix = mMatrix;
			mDrawMatrix.setTranslate((int) ((vWidth - movieWidth) * 0.5f + 0.5f),
					(int) ((vHeight - movieHeight) * 0.5f + 0.5f));

		} else if (ScaleType.CENTER_CROP == mSuperScaleType) {

			mDrawMatrix = mMatrix;
			float scale = Math.max((float) vHeight / (float) movieHeight, (float) vWidth / (float) movieWidth);
			float dx = (vWidth - movieWidth * scale) * 0.5f;
			float dy = (vHeight - movieHeight * scale) * 0.5f;
			mDrawMatrix.setScale(scale, scale);
			mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));

		} else if (ScaleType.CENTER_INSIDE == mSuperScaleType) {

			mDrawMatrix = mMatrix;
			float scale;
			if (movieWidth <= vWidth && movieHeight <= vHeight) {
				scale = 1.0f;
			} else {
				scale = Math.min((float) vWidth / (float) movieWidth, (float) vHeight / (float) movieHeight);
			}
			float dx = (int) ((vWidth - movieWidth * scale) * 0.5f + 0.5f);
			float dy = (int) ((vHeight - movieHeight * scale) * 0.5f + 0.5f);
			mDrawMatrix.setScale(scale, scale);
			mDrawMatrix.postTranslate(dx, dy);

		} else if (ScaleType.FIT_XY == mSuperScaleType) {

			mDrawMatrix = mMatrix;
			float scaleX = (float) vWidth / (float) movieWidth;
			float scaleY = (float) vHeight / (float) movieHeight;
			Log("ScaleType.FIT_XY, scaleX = " + scaleX + ", scaleY = " + scaleY);
			mDrawMatrix.setScale(scaleX, scaleY);
			// mDrawMatrix.postTranslate(mSuperPaddingLeft, mSuperPaddingTop);

		} else if (ScaleType.MATRIX == mSuperScaleType) {

			mDrawMatrix = mSuperDrawMatrix;

		} else { /* fit */
			mDrawMatrix = mMatrix;
			float scale = Math.min((float) vHeight / (float) movieHeight, (float) vWidth / (float) movieWidth);
			float dx = 0.0f;
			float dy = 0.0f;
			if (ScaleType.FIT_START == mSuperScaleType) {
				// dx = 0.0f;
				// dy = 0.0f;
			} else if (ScaleType.FIT_CENTER == mSuperScaleType) {
				dx = (vWidth - movieWidth * scale) * 0.5f + 0.5f;
				dy = (vHeight - movieHeight * scale) * 0.5f + 0.5f;
			} else {/* ScaleType.FIT_END == mSuperScaleType */
				dx = vWidth - movieWidth * scale;
				dy = vHeight - movieHeight * scale;
			}
			mDrawMatrix.setScale(scale, scale);
			mDrawMatrix.postTranslate((int) dx, (int) dy);
		}
	}

	private int resolveAdjustedSize(int desiredSize, int maxSize, int measureSpec) {
		Log("resolveAdjustedSize, desiredSize=" + desiredSize + ",maxSize=" + maxSize + ",measureSpec=" + measureSpec);
		int result = desiredSize;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);
		switch (specMode) {
		case MeasureSpec.UNSPECIFIED:
			/*
			 * Parent says we can be as big as we want. Just don't be larger
			 * than max size imposed on ourselves.
			 */
			result = Math.min(desiredSize, maxSize);
			break;
		case MeasureSpec.AT_MOST:
			// Parent says we can be as big as we want, up to specSize.
			// Don't be larger than specSize, and don't be larger than
			// the max size imposed on ourselves.
			result = Math.min(Math.min(desiredSize, specSize), maxSize);
			break;
		case MeasureSpec.EXACTLY:
			// "60dp"
			// No choice. Do what we are told.
			result = specSize;
			break;
		}
		return result;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

		Log("onMeasure");

		if (!FEATURE_IS_GIF_SUPPORTED || mMovie == null) {
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
			return;
		}

		syncParentParameter();

		int w;
		int h;

		// Desired aspect ratio of the view's contents (not including padding)
		float desiredAspect = 0.0f;

		// We are allowed to change the view's width
		boolean resizeWidth = false;

		// We are allowed to change the view's height
		boolean resizeHeight = false;

		final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
		final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

		if (mMovie == null) {
			w = h = 0;
		} else {
			w = mMovie.width();
			h = mMovie.height();
			Log("onMeasure, w = " + w + ", h = " + h);
			if (w <= 0)
				w = 1;
			if (h <= 0)
				h = 1;

			// We are supposed to adjust view bounds to match the aspect
			// ratio of our drawable. See if that is possible.
			if (getAdjustViewBounds()) {
				resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
				resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
				desiredAspect = (float) w / (float) h;
			}
		}

		int widthSize, heightSize;

		Log("onMeasure, resizeWidth=" + resizeWidth + ", resizeHeight=" + resizeHeight);
		if (resizeWidth || resizeHeight) {

			int maxWidth = getMaxWidth();
			int maxHeight = getMaxHeight();
			/*
			 * If we get here, it means we want to resize to match the drawables
			 * aspect ratio, and we have the freedom to change at least one
			 * dimension.
			 */

			// Get the max possible width given our constraints
			widthSize = resolveAdjustedSize(w + mSuperPaddingLeft + mSuperPaddingRight, maxWidth, widthMeasureSpec);

			// Get the max possible height given our constraints
			heightSize = resolveAdjustedSize(h + mSuperPaddingTop + mSuperPaddingBottom, maxHeight, heightMeasureSpec);

			if (desiredAspect != 0.0f) {
				// See what our actual aspect ratio is
				float actualAspect = (float) (widthSize - mSuperPaddingLeft - mSuperPaddingRight)
						/ (heightSize - mSuperPaddingTop - mSuperPaddingBottom);

				if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {

					boolean done = false;

					// Try adjusting width to be proportional to height
					if (resizeWidth) {
						int newWidth = (int) (desiredAspect * (heightSize - mSuperPaddingTop - mSuperPaddingBottom))
								+ mSuperPaddingLeft + mSuperPaddingRight;

						// Allow the width to outgrow its original estimate if
						// height is fixed.
						if (!resizeHeight && !mAdjustViewBoundsCompat) {
							widthSize = resolveAdjustedSize(newWidth, maxWidth, widthMeasureSpec);
						}

						if (newWidth <= widthSize) {
							widthSize = newWidth;
							done = true;
						}
					}

					// Try adjusting height to be proportional to width
					if (!done && resizeHeight) {
						int newHeight = (int) ((widthSize - mSuperPaddingLeft - mSuperPaddingRight) / desiredAspect)
								+ mSuperPaddingTop + mSuperPaddingBottom;

						// Allow the height to outgrow its original estimate if
						// width is fixed.
						if (!resizeWidth && !mAdjustViewBoundsCompat) {
							heightSize = resolveAdjustedSize(newHeight, maxHeight, heightMeasureSpec);
						}

						if (newHeight <= heightSize) {
							heightSize = newHeight;
						}
					}
				}
			}
		} else {
			/*
			 * We are either don't want to preserve the drawables aspect ratio,
			 * or we are not allowed to change view dimensions. Just measure in
			 * the normal way.
			 */
			w += mSuperPaddingLeft + mSuperPaddingRight;
			h += mSuperPaddingTop + mSuperPaddingBottom;

			w = Math.max(w, getSuggestedMinimumWidth());
			h = Math.max(h, getSuggestedMinimumHeight());

			widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
			heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
		}

		Log("onMeasure, widthSize=" + widthSize + ", heightSize=" + heightSize);
		setMeasuredDimension(widthSize, heightSize);

		configureDrawMatrix();
	}

	@Override
	protected void onDraw(Canvas canvas) {

		if (!FEATURE_IS_GIF_SUPPORTED || mMovie == null) {
			super.onDraw(canvas);
			return;
		}

		// Movie set time
		if (mMovieDuration == 0) {
			mMovie.setTime(0);
		} else {
			long now = android.os.SystemClock.uptimeMillis();
			if (mMovieStartTime == 0) {
				mMovieStartTime = now;// first time
			}
			mMovie.setTime((int) ((now - mMovieStartTime) % mMovieDuration));
		}

		// save the current matrix and clip of canvas
		int saveCount = canvas.getSaveCount();
		canvas.save();

		boolean superCropToPadding = getCropToPadding();
		Log("superCropToPadding = " + superCropToPadding, DB_DETAIL);
		if (superCropToPadding) {
			int superScrollX = getScrollX();
			int superScrollY = getScrollY();
			int superRight = getRight();
			int superLeft = getLeft();
			int superBottom = getBottom();
			int superTop = getTop();
			canvas.clipRect(superScrollX + mSuperPaddingLeft, superScrollY + mSuperPaddingTop, superScrollX
					+ superRight - superLeft - mSuperPaddingRight, superScrollY + superBottom - superTop
					- mSuperPaddingBottom);
		}

		if (mDrawMatrix != null && !mDrawMatrix.isIdentity()) {
			canvas.concat(mDrawMatrix);
		}
		mMovie.draw(canvas, mSuperPaddingLeft, mSuperPaddingTop);

		canvas.restoreToCount(saveCount);
		invalidate();
	}

	@Override
	public void setWillNotCacheDrawing(boolean willNotCacheDrawing) {
		if (FEATURE_IS_GIF_SUPPORTED && mMovie != null) {
			super.setWillNotCacheDrawing(false);
		} else {
			super.setWillNotCacheDrawing(willNotCacheDrawing);
		}
	}

	private static void Log(String log) {
		if (DB) {
			Log.i(DB_TAG, log);
		}
	}

	private static void Log(String log, boolean enable) {
		if (enable) {
			Log.i(DB_TAG, log);
		}
	}
}

    初始化Movie对象以及测试用代码:

package com.yarkey.giftest2;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

import android.app.Activity;
import android.graphics.Movie;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

public class MainActivity extends Activity implements OnClickListener {

	private static final String TAG = "MainActivity";

	ImageView image2;
	MovieImageView mView;
	Button mBtnA, mBtnB, mBtnC, mBtnD;

	static final String ExternalPath = Environment.getExternalStorageDirectory().getPath();

	// setVisibility test
	private static int sIndexA = 0;
	private static int[] sVisibles = new int[] { View.INVISIBLE, View.GONE, View.VISIBLE };
	private static String[] sDescrA = new String[] { "INVISIBLE", "GONE", "VISIBLE" };

	// setScaleType test
	private static int sIndexB = 0;
	private static ScaleType[] sScaleTypes = new ScaleType[] { ScaleType.CENTER, ScaleType.CENTER_CROP,
			ScaleType.CENTER_INSIDE, ScaleType.FIT_CENTER, ScaleType.FIT_END, ScaleType.FIT_START, ScaleType.FIT_XY };
	private static String[] sDescrB = new String[] { "CENTER", "CENTER_CROP", "CENTER_INSIDE", "FIT_CENTER", "FIT_END",
			"FIT_START", "FIT_XY" };

	// setImageResource test
	private static int sIndexC = 0;
	private static int[] sResources = new int[] { R.drawable.pngtest1, R.drawable.giftest1, R.drawable.giftest2,
			R.drawable.giftest3 };

	// setImageUri test
	private static int sIndexD = 0;
	private static Uri[] sImageUris = new Uri[] { Uri.parse("file://" + ExternalPath + "/giftest1.gif"),
			Uri.parse("file://" + ExternalPath + "/giftest2.gif"),
			Uri.parse("file://" + ExternalPath + "/giftest3.gif") };

	// setMovie test
	private static int sIndexE = 0;
	private static Movie[] sMovies = new Movie[4];
	private static String[] sDescrE = new String[] { "Movie1", "Movie2", "Movie3", "Movie4" };

	private static int sIndexF = 0;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// setContentView(R.layout.activity_main);
		setContentView(R.layout.activity_main);

		image2 = (ImageView) this.findViewById(R.id.image2);
		mView = (MovieImageView) this.findViewById(R.id.gifView);

		mBtnA = (Button) this.findViewById(R.id.btnA);
		mBtnB = (Button) this.findViewById(R.id.btnB);
		mBtnC = (Button) this.findViewById(R.id.btnC);
		mBtnD = (Button) this.findViewById(R.id.btnD);

		mBtnA.setOnClickListener(this);
		mBtnB.setOnClickListener(this);
		mBtnC.setOnClickListener(this);
		mBtnD.setOnClickListener(this);

		image2 = (ImageView) this.findViewById(R.id.image2);

		InputStream uriInputStream = null;
		try {
			uriInputStream = new BufferedInputStream(this.getContentResolver().openInputStream(sImageUris[0]));
			uriInputStream.mark(uriInputStream.available());
			sMovies[0] = Movie.decodeStream(uriInputStream);

			uriInputStream = new BufferedInputStream(this.getContentResolver().openInputStream(sImageUris[1]));
			uriInputStream.mark(uriInputStream.available());
			sMovies[1] = Movie.decodeStream(uriInputStream);

			uriInputStream = new BufferedInputStream(this.getContentResolver().openInputStream(sImageUris[2]));
			uriInputStream.mark(uriInputStream.available());
			sMovies[2] = Movie.decodeStream(uriInputStream);

			uriInputStream = new BufferedInputStream(this.getContentResolver().openInputStream(
					Uri.parse("android.resource://com.yarkey.giftest2/" + R.raw.largegif)));
			uriInputStream.mark(uriInputStream.available());
			sMovies[3] = Movie.decodeStream(uriInputStream);

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void onClick(View arg0) {
		Log.i("MainActivity", "onClick");
		switch (arg0.getId()) {
		case R.id.btnA:
			image2.setVisibility(sVisibles[sIndexA]);
			mView.setVisibility(sVisibles[sIndexA]);
			mBtnA.setText("Visible: " + sDescrA[sIndexA]);
			sIndexA++;
			if (sIndexA >= sVisibles.length) {
				sIndexA = 0;
			}
			break;
		case R.id.btnB:
			image2.setScaleType(sScaleTypes[sIndexB]);
			mView.setScaleType(sScaleTypes[sIndexB]);
			mBtnB.setText("ScaleType: " + sDescrB[sIndexB]);
			sIndexB++;
			if (sIndexB >= sScaleTypes.length) {
				sIndexB = 0;
			}
			break;
		case R.id.btnC:
			image2.setImageResource(R.drawable.ic_launcher);
			mView.setMovie(sMovies[sIndexE]);
			mBtnC.setText("setMovie: " + sDescrE[sIndexE]);
			sIndexE++;
			if (sIndexE >= sMovies.length) {
				sIndexE = 0;
			}
			break;
		case R.id.btnD:
			switch (sIndexF) {
			case 0:
				image2.setPadding(100, 0, 0, 0);
				mView.setPadding(100, 0, 0, 0);
				mBtnD.setText("setPadding: 100,0,0,0");
				break;
			case 1:
				image2.setPadding(0, 100, 0, 0);
				mView.setPadding(0, 100, 0, 0);
				mBtnD.setText("setPadding: 0,100,0,0");
				break;
			case 2:
				image2.setPadding(0, 0, 100, 0);
				mView.setPadding(0, 0, 100, 0);
				mBtnD.setText("setPadding: 0,0,100,0");
				break;
			case 3:
				image2.setPadding(0, 0, 0, 100);
				mView.setPadding(0, 0, 0, 100);
				mBtnD.setText("setPadding: 0,0,0,100");
				break;
			case 4:
				image2.setPadding(100, 100, 100, 100);
				mView.setPadding(100, 100, 100, 100);
				mBtnD.setText("setPadding: 100,100,100,100");
				break;
			case 5:
				image2.setPadding(0, 0, 0, 0);
				mView.setPadding(0, 0, 0, 0);
				mBtnD.setText("setPadding: no");
				break;
			}
			sIndexF++;
			if (sIndexF == 6) {
				sIndexF = 0;
			}
			break;
		}
	}


}

    布局文件:

<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollView1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#FFBBBBBB"
            android:gravity="center"
            android:padding="10dp"
            android:text="InCallScreen"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <ImageView
            android:id="@+id/image2"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="#AACCFF"
            android:scaleType="center" />

        <com.yarkey.giftest2.MovieImageView
            android:id="@+id/gifView"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="#88AADD"
            android:scaleType="center" />

        <Button
            android:id="@+id/btnA"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="Visible:VISIBLE" />

        <Button
            android:id="@+id/btnB"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="ScaleType:default" />

        <Button
            android:id="@+id/btnC"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="setMovie:null" />

        <Button
            android:id="@+id/btnD"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="setPadding:no" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >
        </LinearLayout>
    </LinearLayout>

</ScrollView>


    好吧,整个帖子写得我自己都觉得非常乱了。

    不过,记住一下ImageView流程也好: setImageDrawable -> configureBounds -> requestLayout -> onMeasure -> setFrame -> onDraw

    我们计算Movie在View中显示的位置大小平移等效果,必须在setFrame函数调用后,才能执行(此时getWidth(), getHeight() 才会生效)。


    写得太乱了,我的天!就此断了吧。有空再整理。

    Best regards !

 

你可能感兴趣的:(动画,android,imageview,gif,解码)