服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android使用PowerImageView实现播放强大的ImageView动画效果

Android使用PowerImageView实现播放强大的ImageView动画效果

2022-02-17 17:52guolin Android

今天我们就来编写一个PowerImageView控件,让它既能支持ImageView控件原生的所有功能,同时还可以播放GIF图片

我个人是比较喜欢逛贴吧的,贴吧里总是会有很多搞笑的动态图片,经常看一看就会感觉欢乐很多,可以释放掉不少平时的压力。确实,比起一张单调的图片,动态图片明显更加的有意思。一般动态图片都是GIF格式的,浏览器中可以直接将这种格式的图片播放成动画。

不过很可惜的是,Android的原生控件并不支持播放GIF格式的图片。我们都知道,在Android中如果想要显示一张图片,可以借助ImageView控件来完成,但是如果将一张GIF图片设置到ImageView里,它只会显示这张图片的第一帧,不会产生任何的动画效果。

那么就没有办法在Android里播放GIF图片了吗?当然不是,我们可以通过自定义控件的方式来实现这个功能。ImageView无法播放GIF图片说明它的功能还不够强大,那么今天我们就来编写一个PowerImageView控件,让它既能支持ImageView控件原生的所有功能,同时还可以播放GIF图片。

下面我们就开始吧,首先新建一个项目,起名就叫PowerImageViewTest,这里使用Android 4.0的API。

由于是要自定义控件,我们还可能会用到一些自定义的属性,因此在values目录下新建一个attrs.xml的文件,可以在这个文件中添加任何需要自定义的属性。这里我们目前只需要一个auto_play属性,代码如下所示:

?
1
2
3
4
5
6
<?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,代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
public class PowerImageView extends ImageView implements OnClickListener {
 /**
  * 播放GIF动画的关键类
  */
 private Movie mMovie;
 /**
  * 开始播放按钮图片
  */
 private Bitmap mStartButton;
 /**
  * 记录动画开始的时间
  */
 private long mMovieStart;
 /**
  * GIF图片的宽度
  */
 private int mImageWidth;
 /**
  * GIF图片的高度
  */
 private int mImageHeight;
 /**
  * 图片是否正在播放
  */
 private boolean isPlaying;
 /**
  * 是否允许自动播放
  */
 private boolean isAutoPlay;
 /**
  * PowerImageView构造函数。
  *
  * @param context
  */
 public PowerImageView(Context context) {
  super(context);
 }
 /**
  * PowerImageView构造函数。
  *
  * @param context
  */
 public PowerImageView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }
 /**
  * PowerImageView构造函数,在这里完成所有必要的初始化操作。
  *
  * @param context
  */
 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) {
   // 当资源id不等于0时,就去获取该资源的流
   InputStream is = getResources().openRawResource(resourceId);
   // 使用Movie类对流进行解码
   mMovie = Movie.decodeStream(is);
   if (mMovie != null) {
    // 如果返回值不等于null,就说明这是一个GIF图片,下面获取是否自动播放的属性
    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()) {
   // 当用户点击图片时,开始播放GIF动画
   isPlaying = true;
   invalidate();
  }
 }
 @Override
 protected void onDraw(Canvas canvas) {
  if (mMovie == null) {
   // mMovie等于null,说明是张普通的图片,则直接调用父类的onDraw()方法
   super.onDraw(canvas);
  } else {
   // mMovie不等于null,说明是张GIF图片
   if (isAutoPlay) {
    // 如果允许自动播放,就调用playMovie()方法播放GIF动画
    playMovie(canvas);
    invalidate();
   } else {
    // 不允许自动播放时,判断当前图片是否正在播放
    if (isPlaying) {
     // 正在播放就继续调用playMovie()方法,一直到动画播放结束为止
     if (playMovie(canvas)) {
      isPlaying = false;
     }
     invalidate();
    } else {
     // 还没开始播放就只绘制GIF图片的第一帧,并绘制一个开始按钮
     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) {
   // 如果是GIF图片则重写设定PowerImageView的大小
   setMeasuredDimension(mImageWidth, mImageHeight);
  }
 }
 /**
  * 开始播放GIF动画,播放完成返回true,未完成返回false。
  *
  * @param canvas
  * @return 播放完成返回true,未完成返回false。
  */
 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;
 }
 /**
  * 通过Java反射,获取到src指定图片资源所对应的id。
  *
  * @param a
  * @param context
  * @param attrs
  * @return 返回布局文件中指定图片资源所对应的id,没有指定任何图片资源就返回0。
  */
 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,代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
<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属性来完成,代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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动画了,如下图所示:

 Android使用PowerImageView实现播放强大的ImageView动画效果

然后我们还可以通过修改activity_main.xml中的代码,给它加上允许自动播放的属性,代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
<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上就不会再显示一个播放按钮,而是会循环地自动播放动画。现在重新运行一下程序,效果如下图所示: 

Android使用PowerImageView实现播放强大的ImageView动画效果

怎么样?效果还不错吧。不仅如此,PowerImageView还继承了ImageView原生的所有功能,只要指定的不是GIF图片,PowerImageView表现的结果就和ImageView完全一致,让我们来放一张普通的PNG图片看看吧,修改activity_main.xml中的代码,如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
<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图片,效果如下图所示:

 Android使用PowerImageView实现播放强大的ImageView动画效果

一张图片在布局正中央显示出来了,正是普通ImageView所具备的功能。你还可以在PowerImageView中指定android:scaleType等属性,用法和原生的ImageView完全一样。怎么样,是不是确实算得上是Power版的ImageView了?

好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

源码下载,请点击这里

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/guolin_blog/article/details/11100315

延伸 · 阅读

精彩推荐