http://blog.csdn.net/yarkey09/article/details/13022387
网上有很多关于怎么实现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开源项目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()中通过平移画布实现。
- // <span style="background-color:#f0f0f0">yarkey@20131029</span> :
- // 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.
- // <span style="background-color:#f0f0f0">yarkey@20131029</span> : 按原图大小居中显示,超过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;
- // <span style="background-color:#f0f0f0">yarkey@20131029</span> : 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 {
- // <span style="background-color:#f0f0f0">yarkey@20131029</span> : 剩下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() 方法就会被调用。大概是这样子的:
只要调用invalidate()方法,onDraw()方法就会被调用,从而刷新界面。
我们现在关心的是,播放GIF的时候,我们手上是Movie对象,而不是Drawable对象。因此,用于Drawable的位置计算,不能适用于Movie的场合。ImageView可以通过Drawable setBounds()方法设置大小,Movie却没有这种方法,因此我们只能通过缩放画布(canvas)来实现相同的效果。
至于怎么用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() 才会生效)。