当前位置:   article > 正文

安卓动态.9图拉伸实现方案_projectlh像素安卓动态

projectlh像素安卓动态

前言

最近公司要做自定义的聊天气泡,需要可以从服务器配置,并且有底图和边缘的动效

边缘的动效到没什么难度,直接四个角对齐就好了

但是从服务端配置的类似.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方法里......

  1. /**
  2. * Validates the 9-patch chunk and throws an exception if the chunk is invalid.
  3. * If validation is successful, this method returns a native Res_png_9patch*
  4. * object used by the renderers.
  5. */
  6. 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

  1. static jlong validateNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) {
  2. size_t chunkSize = env->GetArrayLength(obj);
  3. if (chunkSize < (int) (sizeof(Res_png_9patch))) {
  4. jniThrowRuntimeException(env, "Array too small for chunk.");
  5. return NULL;
  6. }
  7. int8_t* storage = new int8_t[chunkSize];
  8. // This call copies the content of the jbyteArray
  9. env->GetByteArrayRegion(obj, 0, chunkSize, reinterpret_cast<jbyte*>(storage));
  10. // Deserialize in place, return the array we just allocated
  11. return reinterpret_cast<jlong>(Res_png_9patch::deserialize(storage));
  12. }

ps:由于不是很懂c/c++,所以全靠瞎猜2333,有大佬能看懂的请指出错误!!!

可以看到这个函数是先检查了字节数组的长度,然后创建并copy了一个长度和数据相同的字节数组,并调用Res_png_9patch::deserialize方法将数组转成了long,按理说我们可以通过该方法找到字节数组的规则,继续往下看

  1. Res_png_9patch* Res_png_9patch::deserialize(void* inData)
  2. {
  3. Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(inData);
  4. patch->wasDeserialized = true;
  5. fill9patchOffsets(patch);
  6. return patch;
  7. }

看下fill9patchOffsets方法

  1. static void fill9patchOffsets(Res_png_9patch* patch) {
  2. patch->xDivsOffset = sizeof(Res_png_9patch);
  3. patch->yDivsOffset = patch->xDivsOffset + (patch->numXDivs * sizeof(int32_t));
  4. patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
  5. }

看这个代码貌似就是设置了几条数据,那在看看Res_png_9patch的类型

  1. /** ********************************************************************
  2. * PNG Extensions
  3. *
  4. * New private chunks that may be placed in PNG images.
  5. *
  6. *********************************************************************** */
  7. /**
  8. * This chunk specifies how to split an image into segments for
  9. * scaling.
  10. *
  11. * There are J horizontal and K vertical segments. These segments divide
  12. * the image into J*K regions as follows (where J=4 and K=3):
  13. *
  14. * F0 S0 F1 S1
  15. * +-----+----+------+-------+
  16. * S2| 0 | 1 | 2 | 3 |
  17. * +-----+----+------+-------+
  18. * | | | | |
  19. * | | | | |
  20. * F2| 4 | 5 | 6 | 7 |
  21. * | | | | |
  22. * | | | | |
  23. * +-----+----+------+-------+
  24. * S3| 8 | 9 | 10 | 11 |
  25. * +-----+----+------+-------+
  26. *
  27. * Each horizontal and vertical segment is considered to by either
  28. * stretchable (marked by the Sx labels) or fixed (marked by the Fy
  29. * labels), in the horizontal or vertical axis, respectively. In the
  30. * above example, the first is horizontal segment (F0) is fixed, the
  31. * next is stretchable and then they continue to alternate. Note that
  32. * the segment list for each axis can begin or end with a stretchable
  33. * or fixed segment.
  34. *
  35. * The relative sizes of the stretchy segments indicates the relative
  36. * amount of stretchiness of the regions bordered by the segments. For
  37. * example, regions 3, 7 and 11 above will take up more horizontal space
  38. * than regions 1, 5 and 9 since the horizontal segment associated with
  39. * the first set of regions is larger than the other set of regions. The
  40. * ratios of the amount of horizontal (or vertical) space taken by any
  41. * two stretchable slices is exactly the ratio of their corresponding
  42. * segment lengths.
  43. *
  44. * xDivs and yDivs are arrays of horizontal and vertical pixel
  45. * indices. The first pair of Divs (in either array) indicate the
  46. * starting and ending points of the first stretchable segment in that
  47. * axis. The next pair specifies the next stretchable segment, etc. So
  48. * in the above example xDiv[0] and xDiv[1] specify the horizontal
  49. * coordinates for the regions labeled 1, 5 and 9. xDiv[2] and
  50. * xDiv[3] specify the coordinates for regions 3, 7 and 11. Note that
  51. * the leftmost slices always start at x=0 and the rightmost slices
  52. * always end at the end of the image. So, for example, the regions 0,
  53. * 4 and 8 (which are fixed along the X axis) start at x value 0 and
  54. * go to xDiv[0] and slices 2, 6 and 10 start at xDiv[1] and end at
  55. * xDiv[2].
  56. *
  57. * The colors array contains hints for each of the regions. They are
  58. * ordered according left-to-right and top-to-bottom as indicated above.
  59. * For each segment that is a solid color the array entry will contain
  60. * that color value; otherwise it will contain NO_COLOR. Segments that
  61. * are completely transparent will always have the value TRANSPARENT_COLOR.
  62. *
  63. * The PNG chunk type is "npTc".
  64. */
  65. struct alignas(uintptr_t) Res_png_9patch
  66. {
  67. Res_png_9patch() : wasDeserialized(false), xDivsOffset(0),
  68. yDivsOffset(0), colorsOffset(0) { }
  69. int8_t wasDeserialized;
  70. uint8_t numXDivs;
  71. uint8_t numYDivs;
  72. uint8_t numColors;
  73. // The offset (from the start of this structure) to the xDivs & yDivs
  74. // array for this 9patch. To get a pointer to this array, call
  75. // getXDivs or getYDivs. Note that the serialized form for 9patches places
  76. // the xDivs, yDivs and colors arrays immediately after the location
  77. // of the Res_png_9patch struct.
  78. uint32_t xDivsOffset;
  79. uint32_t yDivsOffset;
  80. int32_t paddingLeft, paddingRight;
  81. int32_t paddingTop, paddingBottom;
  82. enum {
  83. // The 9 patch segment is not a solid color.
  84. NO_COLOR = 0x00000001,
  85. // The 9 patch segment is completely transparent.
  86. TRANSPARENT_COLOR = 0x00000000
  87. };
  88. // The offset (from the start of this structure) to the colors array
  89. // for this 9patch.
  90. uint32_t colorsOffset;
  91. // Convert data from device representation to PNG file representation.
  92. void deviceToFile();
  93. // Convert data from PNG file representation to device representation.
  94. void fileToDevice();
  95. // Serialize/Marshall the patch data into a newly malloc-ed block.
  96. static void* serialize(const Res_png_9patch& patchHeader, const int32_t* xDivs,
  97. const int32_t* yDivs, const uint32_t* colors);
  98. // Serialize/Marshall the patch data into |outData|.
  99. static void serialize(const Res_png_9patch& patchHeader, const int32_t* xDivs,
  100. const int32_t* yDivs, const uint32_t* colors, void* outData);
  101. // Deserialize/Unmarshall the patch data
  102. static Res_png_9patch* deserialize(void* data);
  103. // Compute the size of the serialized data structure
  104. size_t serializedSize() const;
  105. // These tell where the next section of a patch starts.
  106. // For example, the first patch includes the pixels from
  107. // 0 to xDivs[0]-1 and the second patch includes the pixels
  108. // from xDivs[0] to xDivs[1]-1.
  109. inline int32_t* getXDivs() const {
  110. return reinterpret_cast<int32_t*>(reinterpret_cast<uintptr_t>(this) + xDivsOffset);
  111. }
  112. inline int32_t* getYDivs() const {
  113. return reinterpret_cast<int32_t*>(reinterpret_cast<uintptr_t>(this) + yDivsOffset);
  114. }
  115. inline uint32_t* getColors() const {
  116. return reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(this) + colorsOffset);
  117. }
  118. } __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]

发现了这个数组我们关心的数据其实就在中间我标记的位置,所以我们可以创建一个这样的数组并修改中间的值来替换拉伸位置(只拉伸中间一个像素)

  1. 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)
  2. var byteArray = ByteUtils.int2Bytes(newBitmap.width / 2)
  3. bs[29] = byteArray[0]
  4. bs[30] = byteArray[1]
  5. bs[31] = byteArray[2]
  6. bs[32] = byteArray[3]
  7. byteArray = ByteUtils.int2Bytes(newBitmap.width / 2 + 1)
  8. bs[23] = byteArray[0]
  9. bs[34] = byteArray[1]
  10. bs[35] = byteArray[2]
  11. bs[36] = byteArray[3]
  12. byteArray = ByteUtils.int2Bytes(newBitmap.height / 2)
  13. bs[37] = byteArray[0]
  14. bs[38] = byteArray[1]
  15. bs[39] = byteArray[2]
  16. bs[40] = byteArray[3]
  17. byteArray = ByteUtils.int2Bytes(newBitmap.width / 2 + 1)
  18. bs[41] = byteArray[0]
  19. bs[42] = byteArray[1]
  20. bs[43] = byteArray[2]
  21. bs[44] = byteArray[3]
  1. public class ByteUtils {
  2. public static int bytes2Int(byte[] bytes) {
  3. int int1 = (bytes[0] & 0xff) << 24;
  4. int int2 = (bytes[1] & 0xff) << 16;
  5. int int3 = (bytes[2] & 0xff) << 8;
  6. int int4 = bytes[3] & 0xff;
  7. return int1 | int2 | int3 | int4;
  8. }
  9. public static byte[] int2Bytes(int integer) {
  10. byte[] bytes = new byte[4];
  11. bytes[0] = (byte) (integer >> 24);
  12. bytes[1] = (byte) (integer >> 16);
  13. bytes[2] = (byte) (integer >> 8);
  14. bytes[3] = (byte) integer;
  15. return bytes;
  16. }
  17. }

这样我们通过工具类将x范围和y范围将int转为字节(int4个字节),然后赋值到相应位置即可实现效果(其他数据没有修改,目前没有发现问题,如果发现问题请留言谢谢)

这样我们就拿到了chunk数组了,然后就可以调用NinePatchDrawable的构造来动态创建出.9图了

NinePatchDrawable(context.resources, newBitmap, bs, Rect(), null)

ps:发现了一篇更好用的解析,加上工具类使用:  Android .9 图还能这么用?深入剖析一波

end

对Kotlin或KMP感兴趣的同学可以进Q群 101786950

如果这篇文章对您有帮助的话

可以扫码请我喝瓶饮料或咖啡(如果对什么比较感兴趣可以在备注里写出来)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/980761
推荐阅读
相关标签
  

闽ICP备14008679号