赞
踩
最近公司要做自定义的聊天气泡,需要可以从服务器配置,并且有底图和边缘的动效
边缘的动效到没什么难度,直接四个角对齐就好了
但是从服务端配置的类似.9图可拉伸的效果就有点麻烦了
所以下文尝试解决动态实现.9图
首先做安卓开发的都知道.9图的特性:四个边有四条1像素的多余像素,用来表示可拉伸区域(左,上)和可展示内容的区域(右,下)(其实就是加了padding)
最开始想着将一个服务端png转成.9特性的png,后来查了下发现项目内的.9图是会经过编译变成其他东西,所以此条pass
然后就是可以自行绘制,实现Drawable将某一个像素数据在超过图片原始大小后重复绘制,但是比较麻烦
于是获取了一个.9图的Drawable对象,发现其是NinePatchDrawable对象,系统已经实现好了,为啥不用对吧
看了下构造:
public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, String srcName)
发现除了chunk其他的都很好理解,于是去看了看源码chunk是干嘛的,最后跟到了一个native方法里......
- /**
- * Validates the 9-patch chunk and throws an exception if the chunk is invalid.
- * If validation is successful, this method returns a native Res_png_9patch*
- * object used by the renderers.
- */
- private static native long validateNinePatchChunk(byte[] chunk);
通过安卓官网提供的源码地址查看相应native代码:https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/jni/NinePatch.cpp;l=68;drc=master;bpv=0;bpt=1
- static jlong validateNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) {
- size_t chunkSize = env->GetArrayLength(obj);
- if (chunkSize < (int) (sizeof(Res_png_9patch))) {
- jniThrowRuntimeException(env, "Array too small for chunk.");
- return NULL;
- }
-
- int8_t* storage = new int8_t[chunkSize];
- // This call copies the content of the jbyteArray
- env->GetByteArrayRegion(obj, 0, chunkSize, reinterpret_cast<jbyte*>(storage));
- // Deserialize in place, return the array we just allocated
- return reinterpret_cast<jlong>(Res_png_9patch::deserialize(storage));
- }
ps:由于不是很懂c/c++,所以全靠瞎猜2333,有大佬能看懂的请指出错误!!!
可以看到这个函数是先检查了字节数组的长度,然后创建并copy了一个长度和数据相同的字节数组,并调用Res_png_9patch::deserialize方法将数组转成了long,按理说我们可以通过该方法找到字节数组的规则,继续往下看
- Res_png_9patch* Res_png_9patch::deserialize(void* inData)
- {
-
- Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(inData);
- patch->wasDeserialized = true;
- fill9patchOffsets(patch);
-
- return patch;
- }
看下fill9patchOffsets方法
- static void fill9patchOffsets(Res_png_9patch* patch) {
- patch->xDivsOffset = sizeof(Res_png_9patch);
- patch->yDivsOffset = patch->xDivsOffset + (patch->numXDivs * sizeof(int32_t));
- patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
- }
看这个代码貌似就是设置了几条数据,那在看看Res_png_9patch的类型
- /** ********************************************************************
- * PNG Extensions
- *
- * New private chunks that may be placed in PNG images.
- *
- *********************************************************************** */
-
- /**
- * This chunk specifies how to split an image into segments for
- * scaling.
- *
- * There are J horizontal and K vertical segments. These segments divide
- * the image into J*K regions as follows (where J=4 and K=3):
- *
- * F0 S0 F1 S1
- * +-----+----+------+-------+
- * S2| 0 | 1 | 2 | 3 |
- * +-----+----+------+-------+
- * | | | | |
- * | | | | |
- * F2| 4 | 5 | 6 | 7 |
- * | | | | |
- * | | | | |
- * +-----+----+------+-------+
- * S3| 8 | 9 | 10 | 11 |
- * +-----+----+------+-------+
- *
- * Each horizontal and vertical segment is considered to by either
- * stretchable (marked by the Sx labels) or fixed (marked by the Fy
- * labels), in the horizontal or vertical axis, respectively. In the
- * above example, the first is horizontal segment (F0) is fixed, the
- * next is stretchable and then they continue to alternate. Note that
- * the segment list for each axis can begin or end with a stretchable
- * or fixed segment.
- *
- * The relative sizes of the stretchy segments indicates the relative
- * amount of stretchiness of the regions bordered by the segments. For
- * example, regions 3, 7 and 11 above will take up more horizontal space
- * than regions 1, 5 and 9 since the horizontal segment associated with
- * the first set of regions is larger than the other set of regions. The
- * ratios of the amount of horizontal (or vertical) space taken by any
- * two stretchable slices is exactly the ratio of their corresponding
- * segment lengths.
- *
- * xDivs and yDivs are arrays of horizontal and vertical pixel
- * indices. The first pair of Divs (in either array) indicate the
- * starting and ending points of the first stretchable segment in that
- * axis. The next pair specifies the next stretchable segment, etc. So
- * in the above example xDiv[0] and xDiv[1] specify the horizontal
- * coordinates for the regions labeled 1, 5 and 9. xDiv[2] and
- * xDiv[3] specify the coordinates for regions 3, 7 and 11. Note that
- * the leftmost slices always start at x=0 and the rightmost slices
- * always end at the end of the image. So, for example, the regions 0,
- * 4 and 8 (which are fixed along the X axis) start at x value 0 and
- * go to xDiv[0] and slices 2, 6 and 10 start at xDiv[1] and end at
- * xDiv[2].
- *
- * The colors array contains hints for each of the regions. They are
- * ordered according left-to-right and top-to-bottom as indicated above.
- * For each segment that is a solid color the array entry will contain
- * that color value; otherwise it will contain NO_COLOR. Segments that
- * are completely transparent will always have the value TRANSPARENT_COLOR.
- *
- * The PNG chunk type is "npTc".
- */
- struct alignas(uintptr_t) Res_png_9patch
- {
- Res_png_9patch() : wasDeserialized(false), xDivsOffset(0),
- yDivsOffset(0), colorsOffset(0) { }
-
- int8_t wasDeserialized;
- uint8_t numXDivs;
- uint8_t numYDivs;
- uint8_t numColors;
-
- // The offset (from the start of this structure) to the xDivs & yDivs
- // array for this 9patch. To get a pointer to this array, call
- // getXDivs or getYDivs. Note that the serialized form for 9patches places
- // the xDivs, yDivs and colors arrays immediately after the location
- // of the Res_png_9patch struct.
- uint32_t xDivsOffset;
- uint32_t yDivsOffset;
-
- int32_t paddingLeft, paddingRight;
- int32_t paddingTop, paddingBottom;
-
- enum {
- // The 9 patch segment is not a solid color.
- NO_COLOR = 0x00000001,
-
- // The 9 patch segment is completely transparent.
- TRANSPARENT_COLOR = 0x00000000
- };
-
- // The offset (from the start of this structure) to the colors array
- // for this 9patch.
- uint32_t colorsOffset;
-
- // Convert data from device representation to PNG file representation.
- void deviceToFile();
- // Convert data from PNG file representation to device representation.
- void fileToDevice();
-
- // Serialize/Marshall the patch data into a newly malloc-ed block.
- static void* serialize(const Res_png_9patch& patchHeader, const int32_t* xDivs,
- const int32_t* yDivs, const uint32_t* colors);
- // Serialize/Marshall the patch data into |outData|.
- static void serialize(const Res_png_9patch& patchHeader, const int32_t* xDivs,
- const int32_t* yDivs, const uint32_t* colors, void* outData);
- // Deserialize/Unmarshall the patch data
- static Res_png_9patch* deserialize(void* data);
- // Compute the size of the serialized data structure
- size_t serializedSize() const;
-
- // These tell where the next section of a patch starts.
- // For example, the first patch includes the pixels from
- // 0 to xDivs[0]-1 and the second patch includes the pixels
- // from xDivs[0] to xDivs[1]-1.
- inline int32_t* getXDivs() const {
- return reinterpret_cast<int32_t*>(reinterpret_cast<uintptr_t>(this) + xDivsOffset);
- }
- inline int32_t* getYDivs() const {
- return reinterpret_cast<int32_t*>(reinterpret_cast<uintptr_t>(this) + yDivsOffset);
- }
- inline uint32_t* getColors() const {
- return reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(this) + colorsOffset);
- }
-
- } __attribute__((packed));
根据注释来瞎猜,Res_png_9patch相当于一个简单的数据结构,xDivs和yDivs字段保存了可拉伸区域的x和y轴像素的起始位置,color保存的不知道是什么(不过也不重要),也就是我们只要知道数组的哪里是保存的x和y的起始位置,就可以设置气泡的拉伸了
于是我们找到一个png转成.9图并拿到chunk数组:
bitmap.ninePatchChunk//[1,2,2,9,32,0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,52 宽开始,0,0,0,53 宽结束,0,0,0,41 高开始,0,0,0,42 高结束,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,-1,70,-88,-77,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0]
发现了这个数组我们关心的数据其实就在中间我标记的位置,所以我们可以创建一个这样的数组并修改中间的值来替换拉伸位置(只拉伸中间一个像素)
- val bs = byteArrayOf(1, 2, 2, 9, 32, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, -1, 70, -88, -77, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
- var byteArray = ByteUtils.int2Bytes(newBitmap.width / 2)
- bs[29] = byteArray[0]
- bs[30] = byteArray[1]
- bs[31] = byteArray[2]
- bs[32] = byteArray[3]
- byteArray = ByteUtils.int2Bytes(newBitmap.width / 2 + 1)
- bs[23] = byteArray[0]
- bs[34] = byteArray[1]
- bs[35] = byteArray[2]
- bs[36] = byteArray[3]
- byteArray = ByteUtils.int2Bytes(newBitmap.height / 2)
- bs[37] = byteArray[0]
- bs[38] = byteArray[1]
- bs[39] = byteArray[2]
- bs[40] = byteArray[3]
- byteArray = ByteUtils.int2Bytes(newBitmap.width / 2 + 1)
- bs[41] = byteArray[0]
- bs[42] = byteArray[1]
- bs[43] = byteArray[2]
- bs[44] = byteArray[3]
- public class ByteUtils {
-
- public static int bytes2Int(byte[] bytes) {
- int int1 = (bytes[0] & 0xff) << 24;
- int int2 = (bytes[1] & 0xff) << 16;
- int int3 = (bytes[2] & 0xff) << 8;
- int int4 = bytes[3] & 0xff;
-
- return int1 | int2 | int3 | int4;
- }
-
- public static byte[] int2Bytes(int integer) {
- byte[] bytes = new byte[4];
- bytes[0] = (byte) (integer >> 24);
- bytes[1] = (byte) (integer >> 16);
- bytes[2] = (byte) (integer >> 8);
- bytes[3] = (byte) integer;
-
- return bytes;
- }
- }
这样我们通过工具类将x范围和y范围将int转为字节(int4个字节),然后赋值到相应位置即可实现效果(其他数据没有修改,目前没有发现问题,如果发现问题请留言谢谢)
这样我们就拿到了chunk数组了,然后就可以调用NinePatchDrawable的构造来动态创建出.9图了
NinePatchDrawable(context.resources, newBitmap, bs, Rect(), null)
ps:发现了一篇更好用的解析,加上工具类使用: Android .9 图还能这么用?深入剖析一波
end
对Kotlin或KMP感兴趣的同学可以进Q群 101786950
如果这篇文章对您有帮助的话
可以扫码请我喝瓶饮料或咖啡(如果对什么比较感兴趣可以在备注里写出来)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。