Android的开发框架为我们的开发提供了不少很棒的控件,我们在开发的时候不需要太多的编码就能轻松方便的使用这些控件,不过有些时候这些系统自带的控件并不能够完全满足我们的需求。这时就需要我们发挥自己的想象力来实现我们特定需求的控件。
今天为大家带来一款可以展示Gif图片的控件,实现播放Gif图片的方法不止这一种,你也可以选择其他的方式来实现(比如自己写一个库利用JNI调用或者直接用WebView来做展示),不过那就比较麻烦了,Android提供了可以展示类似这样的文件的方法,利用这些方法可以降低实现难度而且效果不会变差。
展示图片的过程实际上就是我们把图片解码到屏幕上的过程。Android为我们提供了一个叫做Movie的类可以帮我们实现解码。这个控件不但可以用作展示Gif图片也可以当做普通的ImageView控件来使用。下面我们通过代码来详细解释如何实现的
首先看一下Demo的目录结构
目录结构非常简单,主要用到的类就只有GIFView一个。下面贴出其中的代码为大家详细解释
package com.example.gifview;
import java.io.InputStream;
import java.lang.reflect.Field;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.ImageView;
public class GifView extends ImageView {
/**
* Movie对象用来解析gif图片
*/
private Movie gifMovie;
/**
* 开始时间,用来和当前时间比较,得出什么时间播放gif
*/
private long mMovieStart;
/**
* gif图片的宽
*/
private int gifImageWidth;
/**
* gif图片的高
*/
private int gifImageHeight;
/**
* 访问attrs
*/
private AttributeSet attrs;
/**
* 复写GifView的构造方法
*
* @param context
*/
public GifView(Context context) {
super(context);
}
/**
* 复写的GifView的构造方法
*
* @param context
* @param attrs
*/
public GifView(Context context, AttributeSet attrs) {
super(context, attrs);
this.attrs = attrs;
loadGifImage();
}
private void loadGifImage(){
AttributeSet attrs = getContext().getResources().getXml(R.id.gifView);
//生成一个TypedArray对象
TypedArray array = getContext().obtainStyledAttributes(attrs,
R.styleable.weatherView);
//得到资源id
int resourceId = getResourceId(array);
//检查资源id
if(resourceId == -1){
System.out.println("没有获取到图片Id,请检查是否在xml文件里设置了src属性");
}
// 以流的方式得到gif文件
InputStream is = getResources().openRawResource(resourceId);
// 用Movie的decodeStream方法解码文件
gifMovie = Movie.decodeStream(is);
if (gifMovie != null) {
Bitmap bitmap = BitmapFactory.decodeStream(is);
gifImageWidth = bitmap.getWidth();
gifImageHeight = bitmap.getHeight();
bitmap.recycle();
}
}
/**
* 复写onDraw方法用于绘制View
*/
@Override
protected void onDraw(Canvas canvas) {
if (gifMovie == null) {
// 如果Movie为空说明不是一个gif图像,不是则调用父类方法,此时该控件等同于ImageView
super.onDraw(canvas);
} else {
// 如果Movie不为空则说明是一个gif图像则展示gif图像
showGifImage(canvas);
// 刷新
invalidate();
}
}
/**
* 复写onMeasure方法,用来设置Gif宽高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (gifMovie != null) {
// 设置尺寸
setMeasuredDimension(gifImageWidth, gifImageHeight);
}
}
// 封装的展示gif的方法
private boolean showGifImage(Canvas canvas) {
//得到系统时间
long now = SystemClock.uptimeMillis();
if (mMovieStart == 0) {
// 把开始时间设置为当前时间
mMovieStart = now;
}
int duration = gifMovie.duration();
if (duration == 0) {
// 如果没有持续时间就设置为100
duration = 100;
}
// 设置间隔时间
int relTime = (int) ((now - mMovieStart) % duration);
gifMovie.setTime(relTime);
//在指定的位置进行绘制,这里是左上角
gifMovie.draw(canvas, 0, 0);
if ((now - mMovieStart) >= duration) {
mMovieStart = 0;
return true;
}
return false;
}
/**
* 解析xml文件里的src传来的参数,需要传入一个TypeArray,Context,和参数数组AttributeSet
*
* @param array
* @param context
* @param attrs
* @return
*/
private int getResourceId(TypedArray array
) {
try {
// 得到一个TypedArray里的域
Field field = TypedArray.class.getDeclaredField("mValue");
// 设置可访问性为true
field.setAccessible(true);
// 从类中取得域值
TypedValue typeValueObject = (TypedValue) field.get(array);
return typeValueObject.resourceId;
} catch (Exception e) {
e.printStackTrace();
} finally {
array.recycle();
}
return -1;
}
}
我们先声明一个Movie的对象,准备用来解析Gif图片,然后我们需要一个变量来记录开始播放的时间,用两个整形变量来记录需要播放的Gif图片的宽高,最后我们实现播放Gif的基类是ImageView,相当于ImageView+Movie来实现的这个功能。需要顺便一提的是,这里用到一点关于Java反射的内容,为的是拿到配置文件里的Gif图片ID。代码如下:
//生成一个TypedArray对象
TypedArray array = getContext().obtainStyledAttributes(attrs,
R.styleable.weatherView);
//得到资源id
int resourceId = getResourceId(array);
首先用loadImage()来载入Gif图片,载入的时候需要得到图片的宽高。
自己实现控件除了继承之外实现onMeasure和onDraw方法也是必要的。在onMeasure里可以设置尺寸,同时可以判断Movie对象是否为空,来决定这次展示是作为普通图片还是Gif图片来展示。
在showGifImage方法里就是实现展示Gif图片的关键部分,这里需要对Movie的duration属性进行设置。用来控制播放周期。
之后的过程就比较容易了,像普通的控件一样来使用就可以了。下面是布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.gifview.GifView
android:id="@+id/gifView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/weather_gif"/>
<TextView
android:id="@+id/textView1"
android:layout_below="@id/gifView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="演示gif控件"/>
</RelativeLayout>
这里<com.example.gifview.GifView>就是我们需要的Gif控件。
然后是在Activity里设置布局文件
package com.example.gifview;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
顺便需要提一下,在使用这个空间时可能需要设置硬件加速为false
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.gifview"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<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.gifview.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>