如何实现代码自动加载.9图

146 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

Android系统使用.9图

如何区分是否是.9图

从上面的讲解总我们可以得出一个结论:源.9图和普通图片的区分是观察PNG图片上下作用四个边界是否都有一个像素

如果有就是源.9图 ,没有就是普通图片。

如何区分是否是源类型还是以编译类型

从刚刚的讲解中我们可以看到 .9图和普通图片的区别就是附加的npTc TAG的辅助数据块。因此我们只要能够读出这种附属数据块的内容来就说明他是编译后的.9图

识别.9图成功后如何使用.9图

从上面的讲解中我们得知Android只能使用编译处理后的.9图。因此如果我们希望模拟AAPT的操作该怎么处理呢?

Android原生如何使用.9图的

首先来看下我们是如何创建一个Drawable的:

    public static Drawable createFromResourceStream(@Nullable Resources res,
            @Nullable TypedValue value, @Nullable InputStream is, @Nullable String srcName,
            @Nullable BitmapFactory.Options opts) {
        ...
        Rect pad = new Rect();
        ...
        Bitmap  bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
        if (bm != null) {
            //1.通过Bitmap获取.9图的CHUNK数据块
            byte[] np = bm.getNinePatchChunk();
            //2.判断对应数据块中是否有内容
            if (np == null || !NinePatch.isNinePatchChunk(np)) {
                np = null;
                pad = null;
            }

            final Rect opticalInsets = new Rect();
            bm.getOpticalInsets(opticalInsets);
            //3.通过Bitmap创建Drawable
            return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
        }
        return null;
    }

通过Bitmap获取.9图的CHUNK数据块

    public byte[] getNinePatchChunk() {
        //返回该变量代表的是.9图的附加数据块
        return mNinePatchChunk;
    }

判断对应数据块中是否有内容

    public native static boolean isNinePatchChunk(byte[] chunk);

native层对应实现:

    static jboolean isNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) {
        if (NULL == obj) {
            return JNI_FALSE;
        }
        //如果数组长度小于.9图规格的附加数据块长度直接返回说明不是。9图
        if (env->GetArrayLength(obj) < (int)sizeof(Res_png_9patch)) {
            return JNI_FALSE;
        }
        //获取字节数组
        const jbyte* array = env->GetByteArrayElements(obj, 0);
        if (array != NULL) {
            //将获取后的字节数组转换为对应的数据实体类CHUNK
            //也就是对CHUNK数据转换成实体类Res_png_9patch
            const Res_png_9patch* chunk = reinterpret_cast<const Res_png_9patch*>(array);
            //该实体类的该项属性代表是否为.9图(可以看到不为-1才是.9图)
            int8_t wasDeserialized = chunk->wasDeserialized;
            env->ReleaseByteArrayElements(obj, const_cast<jbyte*>(array), JNI_ABORT);
            return (wasDeserialized != -1) ? JNI_TRUE : JNI_FALSE;
        }
        return JNI_FALSE;
    }

接下来来看下该数据结构的定义

Res_png_9patch数据结构

这些数据代表的就是PNG图片的上下左右那一个像素的数据信息。

wasDeserialized属性的值如果为-1代表不是.9图

通过Bitmap创建Drawable

    private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
            Rect pad, Rect layoutBounds, String srcName) {

        if (np != null) {
            //如果该块数组有数据则代表是.9图,创建对应实例将数据传入对象即可
            return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
        }
        	//普通Bitmap
        return new BitmapDrawable(res, bm);
    }

如何在代码中处理.9图

分两种情况:

  1. 一种是已经编译后的.9图,数据是存储在CHUNK中的
  2. 第二种情况是源.9图。这种未经处理的图片其实就是上下左右各有一条1像素的边的图片。

对于第一种情况来说简单,判断的依据和原生一样,通过getNinePatchChunk方法和isNinePatchChunk方法两个的返回值共同判断是不是处理后的.9图,如果都返回true,直接通过drawableFromBitmap携带getNinePatchChunk返回的字节数组构建出NinePatchDrawable对象即可

对于第二种情况来说,比较复杂。因为信息是在PNG上的,我们判断这种未处理的依据就是根据他的特点:四个1像素的黑边来判定是否属于未处理的.9图

判断完成后接下来我们需要手动的将这些信息用和原生一样的方法把信息转换为数组 , 接下来还原成一个数据结构填充关键信息后将这部分数组信息携带到drawableFromBitmap方法中从而构建出NinePatchDrawable对象

可以看到我们最后得到的都是NinePatchDrawable对象,其构造方法中携带的数组信息就是.9图对应需要缩放扩大到多少的一个信息。

现成的方案来说已经有开发者实现了处理,可以参考这个链接:

github.com/Anatolii/Ni…

总结

经过上面的介绍,我们可以得知:PNG中还有很多附加CHUNK,我们可以利用利用这些附加CHUNK来对一张普通的PNG图片设置个性化不一样的功能。

.9图的使用只是个开头,我相信后面图片不再仅仅是静态图片,其携带的大量CHUNK信息可以给开发者提供丰富多样的功能~~~