安卓动态.9图拉伸实现方案
前言
最近公司要做自定義的聊天氣泡,需要可以從服務器配置,并且有底圖和邊緣的動效
邊緣的動效到沒什么難度,直接四個角對齊就好了
但是從服務端配置的類似.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图拉伸实现方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Retrofit的方式请求Socke
- 下一篇: logback无法生成日志文件之谜