前言

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

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

但是从服务端配置的类似.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 jbyteArrayenv->GetByteArrayRegion(obj, 0, chunkSize, reinterpret_cast<jbyte*>(storage));// Deserialize in place, return the array we just allocatedreturn 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 datastatic Res_png_9patch* deserialize(void* data);// Compute the size of the serialized data structuresize_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:发现了一篇更好用的解析,加上工具类使用:  https://mp.weixin.qq.com/s/1xkcyoL3OdQ62wrKUDs-pQ

end

安卓动态.9图拉伸实现方案相关推荐

  1. 特约专栏丨陈纯院士:大规模动态时序图实时处理技术、平台及应用

    2021-02-25 18:51:53 0 引言 随着大数据人工智能技术及其应用的不断发展,大规模动态时序图作为一种更完备.更自然.更能反映行业生产活动的数据结构,日益成为工业界和学术界共同关注的焦点 ...

  2. 安卓动态调试七种武器之离别钩 – Hooking(下)

    蒸米 · 2015/11/03 10:30 0x00 序 随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的.另外工具是死的,人是活的,如果能搞懂工具的原理再结 ...

  3. android壁纸制作,安卓动态壁纸制做壁纸的方法教程

    现在很 多人 都喜欢为自己心爱的手机加上手机套和手机里的独特 壁纸 .如果我们已经不满住与壁纸软件里的那些常见壁纸,想要自己制作独一无二的壁纸时,该怎么操作呢?下面小编来为大家介绍 一下使用安卓动态壁 ...

  4. Heatmap.js 一个强大简易的web动态热图

    Heatmap.js 一个强大简易的web动态热图 最近在做热力图效果,背景图上绘制热力图,最开始使用的事Echarts,但是Echarts绘制是基于map,还其他一些第三方的库也很多基于map,还要 ...

  5. 安卓动态调试七种武器之长生剑 - Smali Instrumentation

    转:http://drops.wooyun.org/papers/6045 0x00 序 随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的.另外工具是死的,人 ...

  6. android动态调试七种武器,安卓动态调试七种武器之长生剑 - Smali Instrumentation

    安卓动态调试七种武器之长生剑 - Smali Instrumentation 0x00 序 随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的.另外工具是死的, ...

  7. Heatmap.js – 最强大的 Web 动态热图

    Heatmap.js  – 最强大的 Web 动态热图 最新公司项目需要用到热力图,在百度上搜下,了解到heatmap.js这款神器.然后搜了下例子,却很难搜到马上出效果的例子,特此写一篇heatma ...

  8. centos 8 的图形操作界面在哪_商显首款基于安卓系统的图形化调用API软件—智微智能SPIRIT测评...

    出色优美的数字显示可谓是商业展示的灵魂所在,可迅速吸引人们的目光,智能多样化地进行信息展示.随着各种数字显示产品与技术的进步,新型数字商用显示在生活中随处可见.各类数字标牌.POS设备.自助服务终端. ...

  9. 深夜暗坑 - iOS启动图异常修复方案

    Python实战社群 Java实战社群 长按识别下方二维码,按需求添加 扫码关注添加客服 进Python社群▲ 扫码关注添加客服 进Java社群▲ 作者:shishu 审核:gj,zsb,gbn,zj ...

最新文章

  1. 运维基础-文件权限管理
  2. Twiiter,下一个热点
  3. 架构师之路 — 软件架构 — 软件质量模型
  4. 参数(parameter)和属性(attribute)的区别
  5. EL:集成学习(Ensemble Learning)的概念讲解、问题应用、算法分类、关键步骤、代码实现等相关配图详细攻略
  6. 不吹不黑,今天我们来聊一聊 Kubernetes 落地的三种方式
  7. 框架开发与编程模式编程思想的结合
  8. OpenCV学习笔记(十七):图像修补:inpaint()
  9. windows开发——配置pthread.h头文件
  10. 《数字图像处理》题库5:计算题 ①
  11. 全球及中国装配式建筑行业深度研究与发展模式咨询报告2022版
  12. java代码打出一只狗_牛逼!这位程序员开发出一“舔狗”必备神器(代码已开源)!...
  13. 用python画小猪佩奇的编码_如何用Python代码画小猪佩奇
  14. vmware模板机创建及标题快照克隆(非常详细)
  15. Ubuntu18.04安装美化工具tweak 和 dash to dock
  16. bcedit双系统更改启动项名称_如何修复双系统启动项
  17. Java GifDecode类分解gif图片
  18. css导航栏(二级菜单)
  19. Android之使用SurfaceView制作简易写字板
  20. 用Python爬去今日头条美女图片

热门文章

  1. geyser学习记录(day2):我们在命令行当中怎么使用这个架构?
  2. vscode和anaconda结合的环境配置
  3. pytorch笔记:torch.nn.functional.pad
  4. 关于如何在matlab中导入并翻译Hypemesh导出的大型刚度矩阵txt文本
  5. tableau可视化数据分析60讲(十九)-tableau仪表板布局
  6. python和java对比并发_Python并发编程之从性能角度来初探并发编程(一)
  7. 手写一个迷你版的 Tomcat 喵【转】
  8. spring mvc DispatcherServlet详解之前传---FrameworkServlet
  9. Realm Configuration HOW-TO--官方
  10. jquery学习手记(5)对象