Android 点九图机制讲解及在聊天气泡中的应用

点九图的 padding 在部分手机上面失效

这个是部分 Android 手机的 bug,解决方法见:https://stackoverflow.com/questions/11065996/ninepatchdrawable-does-not-get-padding-from-chunk

public class NinePatchChunk {

private static final String TAG = “NinePatchChunk”;

public final Rect mPaddings = new Rect();

public int mDivX[];

public int mDivY[];

public int mColor[];

private static float density = IMO.getInstance().getResources().getDisplayMetrics().density;

private static void readIntArray(final int[] data, final ByteBuffer buffer) {

for (int i = 0, n = data.length; i < n; ++i)

data[i] = buffer.getInt();

}

private static void checkDivCount(final int length) {

if (length == 0 || (length & 0x01) != 0)

throw new IllegalStateException("invalid nine-patch: " + length);

}

public static Rect getPaddingRect(final byte[] data) {

NinePatchChunk deserialize = deserialize(data);

if (deserialize == null) {

return new Rect();

}

}

public static NinePatchChunk deserialize(final byte[] data) {

final ByteBuffer byteBuffer =

ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());

if (byteBuffer.get() == 0) {

return null; // is not serialized

}

final NinePatchChunk chunk = new NinePatchChunk();

chunk.mDivX = new int[byteBuffer.get()];

chunk.mDivY = new int[byteBuffer.get()];

chunk.mColor = new int[byteBuffer.get()];

try {

checkDivCount(chunk.mDivX.length);

checkDivCount(chunk.mDivY.length);

} catch (Exception e) {

return null;

}

// skip 8 bytes

byteBuffer.getInt();

byteBuffer.getInt();

chunk.mPaddings.left = byteBuffer.getInt();

chunk.mPaddings.right = byteBuffer.getInt();

chunk.mPaddings.top = byteBuffer.getInt();

chunk.mPaddings.bottom = byteBuffer.getInt();

// skip 4 bytes

byteBuffer.getInt();

readIntArray(chunk.mDivX, byteBuffer);

readIntArray(chunk.mDivY, byteBuffer);

readIntArray(chunk.mColor, byteBuffer);

return chunk;

}

}

NinePatchDrawable patchy = new NinePatchDrawable(view.getResources(), bitmap, chunk, NinePatchChunk.getPaddingRect(chunk), null);

view.setBackground(patchy);

动态下载点九图会导致聊天气泡闪烁

  1. 这里我们采取的方案是预下载(预下载 10 个)

  2. 聊天气泡采用内存缓存,磁盘缓存,确保 RecyclerView 快速滑动的时候不会闪烁


理解点九图


以下内容参考腾讯音乐的 Android动态布局入门及NinePatchChunk解密

回顾NinePatchDrawable的构造方法第三个参数bitmap.getNinePatchChunk(),作者猜想,aapt命令其实就是在bitmap图片中,加入了NinePatchChunk的信息,那么我们是不是只要能自己构造出这个东西,就可以让任何图片按照我们想要的方式拉升了呢?

可是查了一堆官方文档,似乎并找不到相应的方法来获得这个byte[]类型的chunk参数。

既然无法知道这个chunk如何生成,那么能不能从解析的角度逆向得出这个NinePatchChunk的生成方法呢?

下面就需要从源码入手了。

NinePatchChunk.java

public static NinePatchChunk deserialize(byte[] data) {

ByteBuffer byteBuffer =

ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());

byte wasSerialized = byteBuffer.get();

if (wasSerialized == 0) return null;

NinePatchChunk chunk = new NinePatchChunk();

chunk.mDivX = new int[byteBuffer.get()];

chunk.mDivY = new int[byteBuffer.get()];

chunk.mColor = new int[byteBuffer.get()];

checkDivCount(chunk.mDivX.length);

checkDivCount(chunk.mDivY.length);

// skip 8 bytes

byteBuffer.getInt();

byteBuffer.getInt();

chunk.mPaddings.left = byteBuffer.getInt();

chunk.mPaddings.right = byteBuffer.getInt();

chunk.mPaddings.top = byteBuffer.getInt();

chunk.mPaddings.bottom = byteBuffer.getInt();

// skip 4 bytes

byteBuffer.getInt();

readIntArray(chunk.mDivX, byteBuffer);

readIntArray(chunk.mDivY, byteBuffer);

readIntArray(chunk.mColor, byteBuffer);

return chunk;

}

其实从这部分解析byte[] chunk的源码,我们已经可以反推出来大概的结构了。如下图,

按照上图中的猜想以及对.9.png的认识,直觉感受到,mDivX,mDivY,mColor这三个数组是最关键的,但是具体是什么,就要继续看源码了。

ResourceTypes.h

/**

  • 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.

  • /

正如源码中,注释的一样,这个NinePatch Chunk把图片从x轴和y轴分成若干个区域,F区域代表了固定,S区域代表了拉伸。mDivX,mDivY描述了所有S区域的位置起始,而mColor描述了,各个Segment的颜色,通常情况下,赋值为源码中定义的NO_COLOR = 0x00000001就行了。就以源码注释中的例子来说,mDivX,mDivY,mColor如下:

mDivX = [ S0.start, S0.end, S1.start, S1.end];

mDivY = [ S2.start, S2.end, S3.start, S3.end];

mColor = [c[0],c[1],…,c[11]]

对于mColor这个数组,长度等于划分的区域数,是用来描述各个区域的颜色的,而如果我们这个只是描述了一个bitmap的拉伸方式的话,是不需要颜色的,即源码中NO_COLOR = 0x00000001

说了这么多,我们还是通过一个简单例子来说明如何构造一个按中心点拉伸的 NinePatchDrawable 吧,

Bitmap bitmap = BitmapFactory.decodeFile(filepath);

int[] xRegions = new int[]{bitmap.getWidth() / 2, bitmap.getWidth() / 2 + 1};

int[] yRegions = new int[]{bitmap.getWidth() / 2, bitmap.getWidth() / 2 + 1};

int NO_COLOR = 0x00000001;

int colorSize = 9;

int bufferSize = xRegions.length * 4 + yRegions.length * 4 + colorSize * 4 + 32;

ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize).order(ByteOrder.nativeOrder());

// 第一个byte,要不等于0

byteBuffer.put((byte) 1);

//mDivX length

byteBuffer.put((byte) 2);

//mDivY length

byteBuffer.put((byte) 2);

//mColors length

byteBuffer.put((byte) colorSize);

//skip

byteBuffer.putInt(0);

byteBuffer.putInt(0);

//padding 先设为0

byteBuffer.putInt(0);

byteBuffer.putInt(0);

byteBuffer.putInt(0);

byteBuffer.putInt(0);

//skip

byteBuffer.putInt(0);

// mDivX

byteBuffer.putInt(xRegions[0]);

byteBuffer.putInt(xRegions[1]);

// mDivY

byteBuffer.putInt(yRegions[0]);

byteBuffer.putInt(yRegions[1]);

// mColors

for (int i = 0; i < colorSize; i++) {

byteBuffer.putInt(NO_COLOR);

}

return byteBuffer.array();

create-a-ninepatch-ninepatchdrawable-in-runtime

在 stackoverflow 上面也找到牛逼的类,可以动态创建点九图,并拉伸图片,啪啪打脸,刚开始说到 android 中无法想 ios 一样动态指定图片拉伸区域。

public class NinePatchBuilder {

int width, height;

Bitmap bitmap;

Resources resources;

private ArrayList xRegions = new ArrayList();

private ArrayList yRegions = new ArrayList();

public NinePatchBuilder(Resources resources, Bitmap bitmap) {

width = bitmap.getWidth();

height = bitmap.getHeight();

this.bitmap = bitmap;

this.resources = resources;

}

public NinePatchBuilder(int width, int height) {

this.width = width;

this.height = height;

}

public NinePatchBuilder addXRegion(int x, int width) {

xRegions.add(x);

xRegions.add(x + width);

return this;

}

public NinePatchBuilder addXRegionPoints(int x1, int x2) {

xRegions.add(x1);

xRegions.add(x2);

return this;

}

public NinePatchBuilder addXRegion(float xPercent, float widthPercent) {

int xtmp = (int) (xPercent * this.width);

xRegions.add(xtmp);

xRegions.add(xtmp + (int) (widthPercent * this.width));

return this;

}

public NinePatchBuilder addXRegionPoints(float x1Percent, float x2Percent) {

xRegions.add((int) (x1Percent * this.width));

xRegions.add((int) (x2Percent * this.width));

return this;

}

public NinePatchBuilder addXCenteredRegion(int width) {

int x = (int) ((this.width - width) / 2);

xRegions.add(x);

xRegions.add(x + width);

return this;

}

public NinePatchBuilder addXCenteredRegion(float widthPercent) {

int width = (int) (widthPercent * this.width);

int x = (int) ((this.width - width) / 2);

xRegions.add(x);

xRegions.add(x + width);

return this;

}

public NinePatchBuilder addYRegion(int y, int height) {

yRegions.add(y);

yRegions.add(y + height);

return this;

}

public NinePatchBuilder addYRegionPoints(int y1, int y2) {

yRegions.add(y1);

yRegions.add(y2);

return this;

}

public NinePatchBuilder addYRegion(float yPercent, float heightPercent) {

int ytmp = (int) (yPercent * this.height);

yRegions.add(ytmp);

yRegions.add(ytmp + (int) (heightPercent * this.height));

return this;

}

public NinePatchBuilder addYRegionPoints(float y1Percent, float y2Percent) {

yRegions.add((int) (y1Percent * this.height));

yRegions.add((int) (y2Percent * this.height));

return this;

}

public NinePatchBuilder addYCenteredRegion(int height) {

int y = (int) ((this.height - height) / 2);

yRegions.add(y);

yRegions.add(y + height);

return this;

}

public NinePatchBuilder addYCenteredRegion(float heightPercent) {

int height = (int) (heightPercent * this.height);

int y = (int) ((this.height - height) / 2);

yRegions.add(y);

yRegions.add(y + height);

return this;

}

public byte[] buildChunk() {

if (xRegions.size() == 0) {

xRegions.add(0);

xRegions.add(width);

}

if (yRegions.size() == 0) {

yRegions.add(0);

yRegions.add(height);

}

int NO_COLOR = 1;//0x00000001;

int COLOR_SIZE = 9;//could change, may be 2 or 6 or 15 - but has no effect on output

int arraySize = 1 + 2 + 4 + 1 + xRegions.size() + yRegions.size() + COLOR_SIZE;

ByteBuffer byteBuffer = ByteBuffer.allocate(arraySize * 4).order(ByteOrder.nativeOrder());

byteBuffer.put((byte) 1);//was translated

byteBuffer.put((byte) xRegions.size());//divisions x

byteBuffer.put((byte) yRegions.size());//divisions y

byteBuffer.put((byte) COLOR_SIZE);//color size

//skip

byteBuffer.putInt(0);

byteBuffer.putInt(0);

//padding – always 0 – left right top bottom

byteBuffer.putInt(0);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

最后

考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档

喜欢的朋友可以关注、转发、点赞 感谢!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

负担。**

[外链图片转存中…(img-d325optE-1711552308115)]

[外链图片转存中…(img-2L4QNB70-1711552308115)]

[外链图片转存中…(img-F0BaMrW4-1711552308116)]

[外链图片转存中…(img-ZXKV0FYm-1711552308116)]

[外链图片转存中…(img-38pCyiRj-1711552308117)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

最后

考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档

[外链图片转存中…(img-WOIQuiLu-1711552308117)]

[外链图片转存中…(img-G36R4SQb-1711552308118)]

喜欢的朋友可以关注、转发、点赞 感谢!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值