本文转载自:http://blog.csdn.net/guolin_blog/article/details/11100315html
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11100315java
我我的是比较喜欢逛贴吧的,贴吧里老是会有不少搞笑的动态图片,常常看一看就会感受欢乐不少,能够释放掉很多平时的压力。确实,比起一张单调的图片,动态图片明显更加的有意思。通常动态图片都是GIF格式的,浏览器中能够直接将这种格式的图片播放成动画。
android
不过很惋惜的是,Android的原生控件并不支持播放GIF格式的图片。咱们都知道,在Android中若是想要显示一张图片,能够借助ImageView控件来完成,可是若是将一张GIF图片设置到ImageView里,它只会显示这张图片的第一帧,不会产生任何的动画效果。
canvas
那么就没有办法在Android里播放GIF图片了吗?固然不是,咱们能够经过自定义控件的方式来实现这个功能。ImageView没法播放GIF图片说明它的功能还不够强大,那么今天咱们就来编写一个PowerImageView控件,让它既能支持ImageView控件原生的全部功能,同时还能够播放GIF图片。
浏览器
下面咱们就开始吧,首先新建一个项目,起名就叫PowerImageViewTest,这里使用Android 4.0的API。
app
因为是要自定义控件,咱们还可能会用到一些自定义的属性,所以在values目录下新建一个attrs.xml的文件,能够在这个文件中添加任何须要自定义的属性。这里咱们目前只须要一个auto_play属性,代码以下所示:ide
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
-
- <declare-styleable name="PowerImageView">
- <attr name="auto_play" format="boolean"></attr>
- </declare-styleable>
-
- </resources>
完成了这个文件以后,下面咱们来开始编写最最重要的PowerImageView类,因为这个类要支持ImageView的全部功能,所以须要让PowerImageView继承自ImageView,代码以下所示:
- public class PowerImageView extends ImageView implements OnClickListener {
-
-
-
-
- private Movie mMovie;
-
-
-
-
- private Bitmap mStartButton;
-
-
-
-
- private long mMovieStart;
-
-
-
-
- private int mImageWidth;
-
-
-
-
- private int mImageHeight;
-
-
-
-
- private boolean isPlaying;
-
-
-
-
- private boolean isAutoPlay;
-
-
-
-
-
-
- public PowerImageView(Context context) {
- super(context);
- }
-
-
-
-
-
-
- public PowerImageView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
-
-
-
-
-
- public PowerImageView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PowerImageView);
- int resourceId = getResourceId(a, context, attrs);
- if (resourceId != 0) {
-
- InputStream is = getResources().openRawResource(resourceId);
-
- mMovie = Movie.decodeStream(is);
- if (mMovie != null) {
-
- isAutoPlay = a.getBoolean(R.styleable.PowerImageView_auto_play, false);
- Bitmap bitmap = BitmapFactory.decodeStream(is);
- mImageWidth = bitmap.getWidth();
- mImageHeight = bitmap.getHeight();
- bitmap.recycle();
- if (!isAutoPlay) {
-
- mStartButton = BitmapFactory.decodeResource(getResources(),
- R.drawable.start_play);
- setOnClickListener(this);
- }
- }
- }
- }
-
- @Override
- public void onClick(View v) {
- if (v.getId() == getId()) {
-
- isPlaying = true;
- invalidate();
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (mMovie == null) {
-
- super.onDraw(canvas);
- } else {
-
- if (isAutoPlay) {
-
- playMovie(canvas);
- invalidate();
- } else {
-
- if (isPlaying) {
-
- if (playMovie(canvas)) {
- isPlaying = false;
- }
- invalidate();
- } else {
-
- mMovie.setTime(0);
- mMovie.draw(canvas, 0, 0);
- int offsetW = (mImageWidth - mStartButton.getWidth()) / 2;
- int offsetH = (mImageHeight - mStartButton.getHeight()) / 2;
- canvas.drawBitmap(mStartButton, offsetW, offsetH, null);
- }
- }
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mMovie != null) {
-
- setMeasuredDimension(mImageWidth, mImageHeight);
- }
- }
-
-
-
-
-
-
-
- private boolean playMovie(Canvas canvas) {
- long now = SystemClock.uptimeMillis();
- if (mMovieStart == 0) {
- mMovieStart = now;
- }
- int duration = mMovie.duration();
- if (duration == 0) {
- duration = 1000;
- }
- int relTime = (int) ((now - mMovieStart) % duration);
- mMovie.setTime(relTime);
- mMovie.draw(canvas, 0, 0);
- if ((now - mMovieStart) >= duration) {
- mMovieStart = 0;
- return true;
- }
- return false;
- }
-
-
-
-
-
-
-
-
-
- private int getResourceId(TypedArray a, Context context, AttributeSet attrs) {
- try {
- Field field = TypedArray.class.getDeclaredField("mValue");
- field.setAccessible(true);
- TypedValue typedValueObject = (TypedValue) field.get(a);
- return typedValueObject.resourceId;
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (a != null) {
- a.recycle();
- }
- }
- return 0;
- }
-
- }
这个类的代码注释已经很是详细了,我再来简单地解释一下。能够看到,咱们重写了ImageView中全部的构建函数,使得PowerImageView的用法能够和ImageView彻底相同。在构造函数中,则是对全部必要的数据进行了初始化操做。首先,咱们调用了getResourceId()方法去获取图片资源对应的id值,在getResourceId()方法内部是经过Java的反射机制来进行获取的。获得了图片资源的id后,咱们将它转换成InputStream,而后传入到Movie.decodeStream()方法中以解码出Movie对象。若是获得的Movie对象等于null,说明这是一张普通的图片资源,就再也不进行任何特殊处理,由于父类ImageView都帮咱们处理好了。若是获得的Movie对象不等于null,则说明这是一张GIF图片,接着就要去获取是否容许自动播放、图片的宽高等属性的值。若是不容许自动播放,还要给播放按钮注册点击事件,默认是不容许自动播放的。
接下来会进入到onMeasure()方法中。在这个方法中咱们进行判断,若是这是一张GIF图片,则须要将PowerImageView的宽高重定义,使得控件的大小恰好能够放得下这张GIF图片。
函数
再日后就会进入到onDraw()方法中。在这个方法里一样先判断当前是一张普通的图片仍是GIF图片,若是是普通的图片就直接调用super.onDraw()方法交给ImageView去处理就行了。若是是GIF图片,则先判断该图是否容许自动播放,容许的话就调用playMovie()方法去播放GIF图片就好,不容许的话则会先在PowerImageView中绘制该GIF图片的第一帧,并在图片上绘制一个播放按钮,当用户点击了播放按钮时,再去调用playMovie()方法去播放GIF图片。
布局
下面咱们来看看playMovie()方法中是怎样播放GIF图片的吧。能够看到,首先会对动画开始的时间作下记录,而后对动画持续的时间作下记录,接着使用当前的时间减去动画开始的时间,获得的时间就是此时PowerImageView应该显示的那一帧,而后借助Movie对象将这一帧绘制到屏幕上便可。以后每次调用playMovie()方法都会绘制一帧图片,连贯起来也就造成了GIF动画。注意,这个方法是有返回值的,若是当前时间减去动画开始时间大于了动画持续时间,那就说明动画播放完成了,返回true,不然返回false。
动画
完成了PowerImageView的编写,下面咱们就来看一看如何使用它吧,其实很是简单,打开或新建activity_main.xml,代码以下所示:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <com.example.powerimageviewtest.PowerImageView
- android:id="@+id/image_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:src="@drawable/anim"
- />
-
- </RelativeLayout>
能够看到,PowerImageView的用法和ImageView几乎彻底同样,使用android:src属性来指定一张图片便可,这里指定的anim就是一张GIF图片。而后咱们让PowerImageView在布局里居中显示。
MainActivity中的代码都是自动生成的,这里就再也不贴出来了。在AndroidManifest.xml中还有一点须要注意,有些4.0以上系统的手机启动了硬件加速功能以后会致使GIF动画播放不出来,所以咱们须要在AndroidManifest.xml中去禁用硬件加速功能,能够经过指定android:hardwareAccelerated属性来完成,代码以下所示:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.powerimageviewtest"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-sdk
- android:minSdkVersion="14"
- android:targetSdkVersion="17" />
-
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme"
- android:hardwareAccelerated="false"
- >
- <activity
- android:name="com.example.powerimageviewtest.MainActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
- </manifest>
如今能够来运行一下代码了,一打开程序你就会看到GIF图片的第一帧,点击图片以后就能够播放GIF动画了,以下图所示:

而后咱们还能够经过修改activity_main.xml中的代码,给它加上容许自动播放的属性,代码以下所示:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:attr="http://schemas.android.com/apk/res/com.example.powerimageviewtest"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <com.example.powerimageviewtest.PowerImageView
- android:id="@+id/image_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:src="@drawable/anim"
- attr:auto_play="true"
- />
-
- </RelativeLayout>
这里使用了刚才咱们自定义的属性,经过attr:auto_play来启用和禁用自动播放功能。如今将auto_play属性指定成true后,PowerImageView上就不会再显示一个播放按钮,而是会循环地自动播放动画。如今从新运行一下程序,效果以下图所示:

怎么样?效果还不错吧。不只如此,PowerImageView还继承了ImageView原生的全部功能,只要指定的不是GIF图片,PowerImageView表现的结果就和ImageView彻底一致,让咱们来放一张普通的PNG图片看看吧,修改activity_main.xml中的代码,以下所示:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <com.example.powerimageviewtest.PowerImageView
- android:id="@+id/image_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:src="@drawable/conan"
- />
-
- </RelativeLayout>
这里在src属性里面指定了一张名字为conan的图片,这是一张PNG图片,效果以下图所示:

一张图片在布局正中央显示出来了,正是普通ImageView所具有的功能。你还能够在PowerImageView中指定android:scaleType等属性,用法和原生的ImageView彻底同样。怎么样,是否是确实算得上是Power版的ImageView了?
好了,今天的讲解到此结束,有疑问的朋友请在下面留言。