当前位置: 代码迷 >> Android >> 【Android实战】记要自学自定义GifView过程,详解属性那些事!【学习篇】
  详细解决方案

【Android实战】记要自学自定义GifView过程,详解属性那些事!【学习篇】

热度:90   发布时间:2016-04-28 00:08:49.0
【Android实战】记录自学自定义GifView过程,详解属性那些事!【学习篇】

我现在要自定义一个ImageView,用来显示Gif图片

自定义View,是肯定需要重写构造方法的。

public class MyGifView extends ImageView {    public MyGifView(final Context context, final AttributeSet attrs, final int defStyle) {        super(context, attrs, defStyle);    }    public MyGifView(final Context context, final AttributeSet attrs) {        super(context, attrs);    }    public MyGifView(final Context context) {        super(context);    }}

虽然是自定义view,但现在等于什么都没写,等于还是原来那个ImageView。然后在main.xml中简单引入,用法和ImageView一样。

<com.azz.mygifview.MyGifView    android:layout_width = "wrap_content"    android:layout_height = "wrap_content"    android:src = "@drawable/coffee"/>

coffee是一张gif动图,此时运行,就显示coffee的第一帧图片。
这里写图片描述

AttributeSet

构造方法参数列表里有个AttributeSet attrs属性我很在意,不明白它的具体含义,看源码之后查到了两个方法

int getAttributeCount() //得到属性个数
String getAttributeName(int index)  //得到相应下标的属性名

通过这两个方法简单结合,我得到了答案

    public MyGifView(final Context context, final AttributeSet attrs) {        super(context, attrs);        int count = attrs.getAttributeCount();        for (int i = 0; i < count; i++) {            Log.d(TAG, "attrs = " + attrs.getAttributeName(i));        }    }
//输出结果attrs = layout_widthattrs = layout_heightattrs = src

发现了什么?这些值刚好是我在xml里引入时设置的初值!如果我在xml去掉src引用,打印显示的结果也会去掉。

由此可以推断,attrs代表的是已设置的属性集合。

TypedArray(1)

看网上的自定义View,第一步就是在重写的构造函数里获取TypedArray属性,如下

    public MyGifView(final Context context, final AttributeSet attrs) {        super(context, attrs);        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GifView);    }

就目前代码量而言,这句话会报错,因为根本没有R.styleable.GifView

原因:
首先这个方法 obtainStyledAttributes(AttributeSet set, int attrs[])的含义大概是从设置的属性里获取自定义样式属性,第一个参数,刚刚验证了是xml里设置了的属性;第二个参数是我们自己自定义的所有属性集合。而现在我们并没有自定义属性,所以直接R.styleable是“点”不出来的。

如何自定义属性

自定义属性不难,就是在工程的res/values下新建attrs.xml,然后在里面加入如下标签组定义

<resources>    <declare-styleable name = "GifView">        <attr name = "gif_src" format = "reference"/>    </declare-styleable></resources>

可以看到这里就有关键字“styleable”的出现。

简单介绍一下,要注意的地方(自己可以自定义的地方),有三个地方。
1.declare-styleable name 代表的是自定义的属性组名
2.attr name 代表的是具体能在xml中定义的属性名
3.format 代表的是属性格式。

format的值可以有:

reference:参考某一资源ID
color:颜色值
boolean:布尔值
dimension:尺寸值。
float:浮点值。
integer:整型值。
string:字符串
fraction:百分数。
enum:枚举值
flag:位或运算

注意:属性定义格式可以指定多种类型
比如:
<attr name = "background" format = "reference|color"/>
调用时既可以用drawable资源又可以直接用颜色
android:background = [email protected]/图片ID | #00FF00”

如何在xml中加上自定义属性

在根部结点加入自己的命名空间xmlns:my = "http://schemas.android.com/apk/res/com.azz.mygifview",其中“my”是自定义标签,可以修改,用my:xxxx = ""的形式来定义自己的属性;“com.azz.mygifview”是程序包名,以AndroidManifest里的package值为准。

放置位置如下:main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/andorid"xmlns:my="http://schemas.android.com/apk/res/com.azz.mygifview"android:layout_width = "match_parent"android:layout_height = "match_parent">    <com.azz.mygifview.MyGifView         android:layout_width = "wrap_content"        android:layout_height = "wrap_content"        my:gif_src = "@drawable/coffee"/></RelativeLayout>

TypedArray(2)

好,现在回到开始获取TypedArray的地方。

通过查阅源码,我又查到两个方法:

int getIndexCount() //得到属性个数
String getText(int index)   //得到对应下标的数据的字符串名称

什么意思呢?简单组合一下就知道答案了:

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GifView); //获取已设置的自定义属性组int count = typedArray.getIndexCount();for (int i = 0; i < count; i++) {    Log.d(TAG, "typedArray = " + typedArray.getText(i));}typedArray.recycle();   //TypedArray是共享资源,用完一定要回收
//输出结果typedArray = res/drawable-mdpi/coffee.gif

注意:TypedArray是共享资源,用完一定要用typedArray.recycle();回收

如果自定义属性是color类型,xml设置属性值为#000000,打印出来的也是#000000。可以根据打印猜到些什么了,打印的是属性的值。

那么,如何获取这些属性的具体值呢?

TypedArray提供非常多的get方法,比如

boolean getBoolean(int index, boolean defValue) //获取boolean类型的值float getFloat(int index, float defValue) //获取浮点类型的值int getInt(int index, int defValue) //获取整数类型的值String getString(int index) //获取字符串类型的值int getResourcesId(int index, int defValue) //获取资源文件idDrawable getDrawable(int index) //获取图片类型的值

defValue 看字面意思就知道是 default value-缺省值,当获取不到值时则返回缺省值。

index 是这个属性的索引,用R.styleable.XXX获得属性索引,其实这个索引就是0、1、2…按着attrs.xml里面GifView组里面定义顺序排的(第一行定义的属性(比如gif_src)索引就是0,第二行1…),这个索引值当你保存xml文件的时候,自动在R文件中生成。

int resId = typedArray.getResourceId(R.styleable.GifView_gif_src, 0); //GifView为属性组名,gif_src为具体属性名,他们之间用下划线连接得到索引名(eclipse“点”的时候会出来提示的),其实点击进去发现,GifView_gif_src就等于0

上面是获取图片资源文件id,还可以直接获取图片Drawable类型,如下

Drawable drawable = typedArray.getDrawable(R.styleable.GifView_gif_src); 

开始自定义显示Gif

总体而言:我们需要借助 Android SDK 自带的 Movie 类进行播放 gif 动画帧。

之前我们已经获取到了图片的资源文件resId,现在需要先把资源文件转换成movie

InputStream iStream = getResources().openRawResource(resId); //此方法能通过资源文件id查找到资源文件并转化为输入流mMovie = Movie.decodeStream(iStream); //输入流转化为Movie (mMovie 为全局变量,类型 Movie)

因为gif_src是自定义属性,当宽高(layout_width | height)设置为wrap_content的时候,需要我们重新设定尺寸(重写onMeasure()),不然是不会显示出gif图片的。

重定义宽高onMeasure

所以紧接着获取图片宽高

if (mMovie != null) {    Bitmap bitmap = BitmapFactory.decodeStream(iStream); //将输入流转换为Bitmap    mWidth = bitmap.getWidth(); //宽,全局变量,int类型    mHeight = bitmap.getHeight(); //高,全局变量,int类型    bitmap.recycle(); //已经不需要图片资源了,释放它}

接着重写onMeasure(),自定义view的尺寸

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    if (mMovie != null) {        setMeasuredDimension(mWidth, mHeight); //重新设定宽高    }}

这个时候如果xml是这样写

<com.azz.mygifview.MyGifView    android:layout_width = "wrap_content"    android:layout_height = "wrap_content"    android:gif_src = "@drawable/coffee"    android:background = "#0000000"/>

可以看到view是宽高等于gif图片的黑色区域

重定义绘图onDraw

剩下最后一步,也是最重要的一步,重写onDraw来实现播放gif动图

@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    if (mMovie != null) {        playMovie(canvas); //播放gif        invalidate(); //刷新界面    }}/** * @Description 开始播放gif * @param canvas 画布 */private void playMovie(Canvas canvas) {    if (mMovie == null) {        return false;    }    long now = SystemClock.uptimeMillis(); //得到当前时间    //第一次播放    if (mMovieStart == 0) { //gif播放开始时间,全局变量,long类型        mMovieStart = now; //记录播放开始时间    }    int duration = mMovie.duration(); //帧间隔时间    int relTime = (int)((now - mMovieStart) % duration); //计算出当前播放的时间点    mMovie.setTime(relTime); //设置播放时间点    mMovie.draw(canvas, 0, 0); //在画布上画出当前帧}

注释代码里已经比较详细了。

这个时候再运行!~

可能出现的问题-黑屏、不显示gif

很糟糕,view还是一片黑!但是我自己加了很多打印,能看出来是在循环播放的,只是不显示!

原来跟Android 4.0有关

解决方法一:

有些4.0以上系统的手机启动了硬件加速功能之后会导致GIF动画播放不出来,因此我们需要在 AndroidManifest.xml 中去禁用硬件加速功能,可以通过指定android:hardwareAccelerated属性来完成

所以只要在 AndroidManifest.xml 中的<application>标签或者<activity>标签里加上android:hardwareAccelerated="false"

如下在 AndroidManifest.xml

<application          android:allowBackup="true"          android:icon="@drawable/ic_launcher"          android:label="@string/app_name"          android:theme="@style/AppTheme"           android:hardwareAccelerated="false"          >          ...</application>

解决方法二

<uses-sdk />标签移到文件的最后就可以了。
如下在 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.azz.mygifview"    android:versionCode="1"    android:versionName="1.0" >    <application>        <activity>        </activity>    </application>    <uses-sdk        android:minSdkVersion="8"        android:targetSdkVersion="18" /></manifest>

两种方法都亲测有效!

现在再运行,gif终于出来了!
这里写图片描述
直到做完我才发现,原来是可乐,不是咖啡=。=


源码地址:https://github.com/Xieyupeng520/MyGifView_V1.0.git

本项目仍有一些不成熟的地方,比如只支持gif,不支持其他格式图片,建议只做学习用。

下一篇《【Android实战】记录自学自定义GifView过程,能同时支持gif和其他图片!【实用篇】》会讲解如何完美解决该问题!


References:
《Android PowerImageView实现,可以播放动画的强大ImageView》
《说说Android中的style和theme》
《[Android实例] 根据好些例子用movie类显示动的gif,但总是连图片都看不到》

本文有些关于理解自定义view属性不清楚的地方,可以看看《Android 深入理解Android中的自定义属性》


如果你有任何问题,欢迎留言告诉我!

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案