android自定义控件---GifImageView
2017-02-25 02:15
381 查看
今天我用的逐帧动画做的按钮的点击效果,但是感觉太麻烦了,如果能用gif图不是更好么,于是看了一下,发现可以src传gif的资源但是只显示第一帧的画面。于是我们只能换个思路,重写imageview了。
说下思路吧首先,我们调用了getResourceId()方法去获取图片资源对应的id值,在getResourceId()方法内部是通过Java的反射机制来进行获取的。
得到了图片资源的id后,我们将它转换成InputStream,然后传入到Movie.decodeStream()方法中以解码出Movie对象。如果不为空,就在onMeasure方法中定义合适的大小
再往后就会进入到onDraw()方法中。在这个方法里同样先判断当前是一张普通的图片还是GIF图片,如果是普通的图片就直接调用super.onDraw()方法交给ImageView去处理就好了。如果是GIF图片,则先判断该图是否允许自动播放,允许的话就调用playMovie()方法去播放GIF图片就好,不允许的话则会先在PowerImageView中绘制该GIF图片的第一帧,并在图片上绘制一个播放按钮,当用户点击了播放按钮时,再去调用playMovie()方法去播放GIF图片。
最后是播放方法playMovie()使用当前的时间减去动画开始的时间,得到的时间就是此时PowerImageView应该显示的那一帧,然后借助Movie对象将这一帧绘制到屏幕上即可。
注意,这个方法是有返回值的,如果当前时间减去动画开始时间大于了动画持续时间,那就说明动画播放完成了,返回true,否则返回false
好了直接给出代码
但是这里有不少坑
第一:需要在AndroidManifest.xml中去禁用硬件加速功能不然在一些机型上不行
坑点二:位置放错会爆 Caused by: java.lang.RuntimeException: Cannot make calls to a recycled instance!的错误
这个坑也恶心到我了,估计是这个属性不能处于可能被获取状态。(大神看到了 求教)
不过这个还是蛮好用的,也不影响原来的使用()
说下思路吧首先,我们调用了getResourceId()方法去获取图片资源对应的id值,在getResourceId()方法内部是通过Java的反射机制来进行获取的。
int resourceId = getResourceId(tArray, context, attrs); if (resourceId != 0) { // 当资源id不等于0时,就去获取该资源的流 InputStream is = getResources().openRawResource(resourceId); // 使用Movie类对流进行解码 mMovie = Movie.decodeStream(is);
得到了图片资源的id后,我们将它转换成InputStream,然后传入到Movie.decodeStream()方法中以解码出Movie对象。如果不为空,就在onMeasure方法中定义合适的大小
Bitmap bitmap = BitmapFactory.decodeStream(is); mImageWidth = bitmap.getWidth(); mImageHeight = bitmap.getHeight(); bitmap.recycle();
setMeasuredDimension(mImageWidth, mImageHeight);
再往后就会进入到onDraw()方法中。在这个方法里同样先判断当前是一张普通的图片还是GIF图片,如果是普通的图片就直接调用super.onDraw()方法交给ImageView去处理就好了。如果是GIF图片,则先判断该图是否允许自动播放,允许的话就调用playMovie()方法去播放GIF图片就好,不允许的话则会先在PowerImageView中绘制该GIF图片的第一帧,并在图片上绘制一个播放按钮,当用户点击了播放按钮时,再去调用playMovie()方法去播放GIF图片。
@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); } } } }
最后是播放方法playMovie()使用当前的时间减去动画开始的时间,得到的时间就是此时PowerImageView应该显示的那一帧,然后借助Movie对象将这一帧绘制到屏幕上即可。
注意,这个方法是有返回值的,如果当前时间减去动画开始时间大于了动画持续时间,那就说明动画播放完成了,返回true,否则返回false
好了直接给出代码
package com.example.admin.imageviewgif; 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.view.View; import android.widget.ImageView; import java.io.InputStream; import java.lang.reflect.Field; /** * Created by admin on 2017/2/25. */ public class GifImageView extends ImageView implements View.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; public GifImageView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray tArray = context.obtainStyledAttributes(attrs, R.styleable.GifImageView); isAutoPlay = tArray.getBoolean(R.styleable.GifImageView_auto_play, false); int resourceId = getResourceId(tArray, context, attrs); if (resourceId != 0) { // 当资源id不等于0时,就去获取该资源的流 InputStream is = getResources().openRawResource(resourceId); // 使用Movie类对流进行解码 mMovie = Movie.decodeStream(is); if (mMovie != null) { // 如果返回值不等于null,就说明这是一个GIF图片,下面获取是否自动播放的属性 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); } } } // tArray.recycle(); 注意这里我们不能回收 } @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; } }
<com.example.admin.imageviewgif.GifImageView android:layout_marginTop="200dp" android:layout_width="wrap_content" android:layout_height="wrap_content" app:auto_play="true" android:src="@drawable/start_play"/>
但是这里有不少坑
第一:需要在AndroidManifest.xml中去禁用硬件加速功能不然在一些机型上不行
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:hardwareAccelerated="false" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
坑点二:位置放错会爆 Caused by: java.lang.RuntimeException: Cannot make calls to a recycled instance!的错误
这个坑也恶心到我了,估计是这个属性不能处于可能被获取状态。(大神看到了 求教)
不过这个还是蛮好用的,也不影响原来的使用()
相关文章推荐
- android中使用imageview显示Gif图片
- Android应用系列:完美运行GIF格式的ImageView(附源码)
- Android实现播放GIF动画的强大ImageView
- Android加载Gif和ImageView的通用解决方案:android-gif-drawable
- Android疑难杂症之(ImageView播放gif遇到的坑)
- android自定义控件--图片拖拽DragImageView
- Android ImageView扩展 支持本地gif图片 支持网络gif图片
- Android自定义控件:imageview重写onMeasure方法实现图片按指定比例显示,拉伸永不变形,解决屏幕适配问题
- Android加载Gif和ImageView的通用解决方案:android-gif-drawable(1)
- android自定义控件(7)-获取自定义ImageView的src属性
- Android扩展ImageView播放gif动画
- Android应用系列:完美运行GIF格式的ImageView(附源码
- 基于开源框架Glide加载Gif资源图到Android ImageView中
- Android-自定义View实现ImageView播放gif
- android自定义控件:可旋转View:可作为ImageView、ImageButton
- android 拓展ImageView播放GIF动画
- android自定义控件:可旋转View:可作为ImageView、ImageButton
- Android ImageView实现类似Gif动图的效果
- 基于开源框架Glide加载Gif资源图到Android ImageView中
- Android 中使用ImageView实现播放Gif图片功能