100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > android 字体像素转换工具类_Android点9图机制及在聊天气泡中的应用

android 字体像素转换工具类_Android点9图机制及在聊天气泡中的应用

时间:2021-12-02 07:39:01

相关推荐

android 字体像素转换工具类_Android点9图机制及在聊天气泡中的应用

点九图简介

Android为了使用同一张图作为不同数量文字的背景,设计了一种可以指定区域拉伸的图片格式“.9.png”,这种图片格式就是点九图。

注意:这种图片格式只能被使用于Android开发。在ios开发中,可以在代码中指定某个点进行拉伸,而在Android中不行,所以在Android中想要达到这个效果,只能使用点九图(下文会啪啪打脸,其实是可以的,只是很少人这样使用,兼容性不知道怎么样,点击跳转)

点九图实质

点九图的本质实际上是在图片的四周各增加了1px的像素,并使用纯黑(#FF000000)的线进行标记,其它的与原图没有任何区别。可以参考以下图片:

点九图在 Android 中的应用

点九图在 Android 中主要有三种应用方式

直接放在 res 目录中的 drawable 或者 mipmap 目录中

放在 assert 目录中

从网络下载

第一种方式是我们最常用的,直接调用setBackgroundResource或者setImageResource方法,这样的话图片及可以做到自动拉伸。

而对于第二种或者第三种方式,如果我们直接去加载 .9.png,你会发现图片或者图片背景根本无法拉伸。纳尼,这是为甚么呢。下面,且听老衲慢慢道来。

Android 并不是直接使用点九图,而是在编译时将其转换为另外一种格式,这种格式是将其四周的黑色像素保存至Bitmap类中的一个名为mNinePatchChunk的 byte[] 中,并抹除掉四周的这一个像素的宽度;接着在使用时,如果 Bitmap 的这个mNinePatchChunk不为空,且为 9patch chunk,则将其构造为NinePatchDrawable,否则将会被构造为 BitmapDrawable,最终设置给 view。

因此,在 Android 中,我们如果想动态使用网络下载的点九图,一般需要经过以下步骤:

使用 sdk 目录下的 aapt 工具将点九图转化为 png 图片

解析图片的时候,判断是否含有 NinePatchChunk,有的话,转化为 NinePatchDrawable

1publicstaticvoidsetNineImagePatch(Viewview,Filefile,Stringurl){

2if(file.exists()){

3Bitmapbitmap=BitmapFactory.decodeFile(file.getAbsolutePath());

4byte[]chunk=bitmap.getNinePatchChunk();

5if(NinePatch.isNinePatchChunk(chunk)){

6NinePatchDrawablepatchy=newNinePatchDrawable(view.getResources(),bitmap,chunk,newRect(),null);

7view.setBackground(patchy);

8}

9

10}

11}

点九图上传服务器流程

aapt 转换命令

单个图片文件转换

1./aapts-ixxx.9.png-oxxx.png

批量转换

1#批量转换

2./aaptc-SinputDir-CoutputDir

3#inputDir为原始.9图文件夹,outputDir为输出文件夹

执行成功实例

1jundeMacBook-Pro:一期气泡junxu$./aaptc-S/Users/junxu/Desktop/一期气泡/气泡需求整理-C/Users/junxu/Desktop/一期气泡/output

2CrunchingPNGFilesinsourcedir:/Users/junxu/Desktop/一期气泡/气泡需求整理

3Todestinationdir:/Users/junxu/Desktop/一期气泡/output

注意:

若不是标准的点九图,在转换的过程会报错,这时候请设计重新提供新的点九图

实际开发当中遇到的问题

小屏手机适配问题

刚开始,我们的切图是按照 2 倍图切的,这样在小屏幕手机上会手机气泡高度过大的问题。

原因分析:

该现象的本质是点九图图片的高度大于单行文本消息的高度。

解决方案一(暂时不可取):

我尝试去压缩点九图,但最终再部分手机上面显示错乱,不知道是不是压缩点九图的方法错了。

解决方案二

对于低分辨率的手机和高分辨的手机分别下发不同的图片 url,我们尝试过得方案是当density < 2的时候,采用一倍图图片,density >= 2采用二倍图图片。

解决方案三

可能有人会有这样的疑问呢,为什么要采用一倍图,两倍图的解决方案呢?直接让 UI 设计师给一套图,点九图图片的高度适中不就解决了。是啊,我们也是这样想得,但他们说对于有一些装饰的点九图,如果缩小高度,一些装饰图案他们不太好切。比如下面图片中的星星。

小结

说到底,方案二,方案三其实都是折中的一种方案,如果直接能够做到点九图缩放,那就完美解决了。而 Android 中 res 目录中的 drawable 或者 mipmap 的点九图确实能做到,去看了相关的代码,目前也没有发现什么好的解决方案,如果你有好的解决方案话,欢迎留言交流。

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

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

1publicclassNinePatchChunk{

2

3privatestaticfinalStringTAG="NinePatchChunk";

4

5publicfinalRectmPaddings=newRect();

6

7publicintmDivX[];

8publicintmDivY[];

9publicintmColor[];

10

11privatestaticfloatdensity=IMO.getInstance().getResources().getDisplayMetrics().density;

12

13privatestaticvoidreadIntArray(finalint[]data,finalByteBufferbuffer){

14for(inti=0,n=data.length;i15data[i]=buffer.getInt();

16}

17

18privatestaticvoidcheckDivCount(finalintlength){

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

20thrownewIllegalStateException("invalidnine-patch:"+length);

21}

22

23publicstaticRectgetPaddingRect(finalbyte[]data){

24NinePatchChunkdeserialize=deserialize(data);

25if(deserialize==null){

26returnnewRect();

27}

28}

29

30publicstaticNinePatchChunkdeserialize(finalbyte[]data){

31finalByteBufferbyteBuffer=

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

33

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

35returnnull;//isnotserialized

36}

37

38finalNinePatchChunkchunk=newNinePatchChunk();

39chunk.mDivX=newint[byteBuffer.get()];

40chunk.mDivY=newint[byteBuffer.get()];

41chunk.mColor=newint[byteBuffer.get()];

42

43try{

44checkDivCount(chunk.mDivX.length);

45checkDivCount(chunk.mDivY.length);

46}catch(Exceptione){

47returnnull;

48}

49

50

51//skip8bytes

52byteBuffer.getInt();

53byteBuffer.getInt();

54

55

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

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

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

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

60

61

62//skip4bytes

63byteBuffer.getInt();

64

65readIntArray(chunk.mDivX,byteBuffer);

66readIntArray(chunk.mDivY,byteBuffer);

67readIntArray(chunk.mColor,byteBuffer);

68

69returnchunk;

70}

71}

72

73NinePatchDrawablepatchy=newNinePatchDrawable(view.getResources(),bitmap,chunk,NinePatchChunk.getPaddingRect(chunk),null);

74view.setBackground(patchy);

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

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

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

理解点九图

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

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

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

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

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

NinePatchChunk.java

1publicstaticNinePatchChunkdeserialize(byte[]data){

2ByteBufferbyteBuffer=

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

4bytewasSerialized=byteBuffer.get();

5if(wasSerialized==0)returnnull;

6NinePatchChunkchunk=newNinePatchChunk();

7chunk.mDivX=newint[byteBuffer.get()];

8chunk.mDivY=newint[byteBuffer.get()];

9chunk.mColor=newint[byteBuffer.get()];

10checkDivCount(chunk.mDivX.length);

11checkDivCount(chunk.mDivY.length);

12//skip8bytes

13byteBuffer.getInt();

14byteBuffer.getInt();

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

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

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

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

19//skip4bytes

20byteBuffer.getInt();

21readIntArray(chunk.mDivX,byteBuffer);

22readIntArray(chunk.mDivY,byteBuffer);

23readIntArray(chunk.mColor,byteBuffer);

24returnchunk;

25}

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

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

ResourceTypes.h

1/**

2*Thischunkspecifieshowtosplitanimageintosegmentsfor

3*scaling.

4*

5*ThereareJhorizontalandKverticalsegments.Thesesegmentsdivide

6*theimageintoJ*Kregionsasfollows(whereJ=4andK=3):

7*

8*F0S0F1S1

9*+-----+----+------+-------+

10*S2|0|1|2|3|

11*+-----+----+------+-------+

12*|||||

13*|||||

14*F2|4|5|6|7|

15*|||||

16*|||||

17*+-----+----+------+-------+

18*S3|8|9|10|11|

19*+-----+----+------+-------+

20*

21*Eachhorizontalandverticalsegmentisconsideredtobyeither

22*stretchable(markedbytheSxlabels)orfixed(markedbytheFy

23*labels),inthehorizontalorverticalaxis,respectively.Inthe

24*aboveexample,thefirstishorizontalsegment(F0)isfixed,the

25*nextisstretchableandthentheycontinuetoalternate.Notethat

26*thesegmentlistforeachaxiscanbeginorendwithastretchable

27*orfixedsegment.

28*/

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

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

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

3mColor=[c[0],c[1],...,c[11]]

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

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

1Bitmapbitmap=BitmapFactory.decodeFile(filepath);

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

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

4intNO_COLOR=0x00000001;

5intcolorSize=9;

6intbufferSize=xRegions.length*4+yRegions.length*4+colorSize*4+32;

7

8ByteBufferbyteBuffer=ByteBuffer.allocate(bufferSize).order(ByteOrder.nativeOrder());

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

10byteBuffer.put((byte)1);

11

12//mDivXlength

13byteBuffer.put((byte)2);

14//mDivYlength

15byteBuffer.put((byte)2);

16//mColorslength

17byteBuffer.put((byte)colorSize);

18

19//skip

20byteBuffer.putInt(0);

21byteBuffer.putInt(0);

22

23//padding先设为0

24byteBuffer.putInt(0);

25byteBuffer.putInt(0);

26byteBuffer.putInt(0);

27byteBuffer.putInt(0);

28

29//skip

30byteBuffer.putInt(0);

31

32//mDivX

33byteBuffer.putInt(xRegions[0]);

34byteBuffer.putInt(xRegions[1]);

35

36//mDivY

37byteBuffer.putInt(yRegions[0]);

38byteBuffer.putInt(yRegions[1]);

39

40//mColors

41for(inti=0;i42byteBuffer.putInt(NO_COLOR);

43}

44

45returnbyteBuffer.array();

create-a-ninepatch-ninepatchdrawable-in-runtime

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

1publicclassNinePatchBuilder{

2intwidth,height;

3Bitmapbitmap;

4Resourcesresources;

5privateArrayListxRegions=newArrayList(); 6privateArrayListyRegions=newArrayList(); 7 8publicNinePatchBuilder(Resourcesresources,Bitmapbitmap){9width=bitmap.getWidth(); 10height=bitmap.getHeight(); 11this.bitmap=bitmap; 12this.resources=resources; 13} 14 15publicNinePatchBuilder(intwidth,intheight){16this.width=width; 17this.height=height; 18} 19 20publicNinePatchBuilderaddXRegion(intx,intwidth){21xRegions.add(x); 22xRegions.add(x+width); 23returnthis; 24} 25 26publicNinePatchBuilderaddXRegionPoints(intx1,intx2){27xRegions.add(x1); 28xRegions.add(x2); 29returnthis; 30} 31 32publicNinePatchBuilderaddXRegion(floatxPercent,floatwidthPercent){33intxtmp=(int)(xPercent*this.width); 34xRegions.add(xtmp); 35xRegions.add(xtmp+(int)(widthPercent*this.width)); 36returnthis; 37} 38 39publicNinePatchBuilderaddXRegionPoints(floatx1Percent,floatx2Percent){40xRegions.add((int)(x1Percent*this.width)); 41xRegions.add((int)(x2Percent*this.width)); 42returnthis; 43} 44 45publicNinePatchBuilderaddXCenteredRegion(intwidth){46intx=(int)((this.width-width)/2); 47xRegions.add(x); 48xRegions.add(x+width); 49returnthis; 50} 51 52publicNinePatchBuilderaddXCenteredRegion(floatwidthPercent){53intwidth=(int)(widthPercent*this.width); 54intx=(int)((this.width-width)/2); 55xRegions.add(x); 56xRegions.add(x+width); 57returnthis; 58} 59 60publicNinePatchBuilderaddYRegion(inty,intheight){61yRegions.add(y); 62yRegions.add(y+height); 63returnthis; 64} 65 66publicNinePatchBuilderaddYRegionPoints(inty1,inty2){67yRegions.add(y1); 68yRegions.add(y2); 69returnthis; 70} 71 72publicNinePatchBuilderaddYRegion(floatyPercent,floatheightPercent){73intytmp=(int)(yPercent*this.height); 74yRegions.add(ytmp); 75yRegions.add(ytmp+(int)(heightPercent*this.height)); 76returnthis; 77} 78 79publicNinePatchBuilderaddYRegionPoints(floaty1Percent,floaty2Percent){80yRegions.add((int)(y1Percent*this.height)); 81yRegions.add((int)(y2Percent*this.height)); 82returnthis; 83} 84 85publicNinePatchBuilderaddYCenteredRegion(intheight){86inty=(int)((this.height-height)/2); 87yRegions.add(y); 88yRegions.add(y+height); 89returnthis; 90} 91 92publicNinePatchBuilderaddYCenteredRegion(floatheightPercent){93intheight=(int)(heightPercent*this.height); 94inty=(int)((this.height-height)/2); 95yRegions.add(y); 96yRegions.add(y+height); 97returnthis; 98} 99100publicbyte[]buildChunk(){101if(xRegions.size()==0){102xRegions.add(0);103xRegions.add(width);104}105if(yRegions.size()==0){106yRegions.add(0);107yRegions.add(height);108}109110intNO_COLOR=1;//0x00000001;111intCOLOR_SIZE=9;//couldchange,maybe2or6or15-buthasnoeffectonoutput112intarraySize=1+2+4+1+xRegions.size()+yRegions.size()+COLOR_SIZE;113ByteBufferbyteBuffer=ByteBuffer.allocate(arraySize*4).order(ByteOrder.nativeOrder());114byteBuffer.put((byte)1);//wastranslated115byteBuffer.put((byte)xRegions.size());//divisionsx116byteBuffer.put((byte)yRegions.size());//divisionsy117byteBuffer.put((byte)COLOR_SIZE);//colorsize118119//skip120byteBuffer.putInt(0);121byteBuffer.putInt(0);122123//padding--always0--leftrighttopbottom124byteBuffer.putInt(0);125byteBuffer.putInt(0);126byteBuffer.putInt(0);127byteBuffer.putInt(0);128129//skip130byteBuffer.putInt(0);131132for(intrx:xRegions)133byteBuffer.putInt(rx);//regionsleftrightleftright...134for(intry:yRegions)135byteBuffer.putInt(ry);//regionstopbottomtopbottom...136137for(inti=0;i138byteBuffer.putInt(NO_COLOR);139140returnbyteBuffer.array();141}142143publicNinePatchbuildNinePatch(){144byte[]chunk=buildChunk();145if(bitmap!=null)146returnnewNinePatch(bitmap,chunk,null);147returnnull;148}149150publicNinePatchDrawablebuild(){151NinePatchninePatch=buildNinePatch();152if(ninePatch!=null)153returnnewNinePatchDrawable(resources,ninePatch);154returnnull;155}156}

运行一下测试代码

1mLlRoot=findViewById(R.id.ll_root);

2try{

3InputStreamis=getAssets().open("sea.png");

4Bitmapbitmap=BitmapFactory.decodeStream(is);

5for(inti=0;i5;i++){

6NinePatchDrawableninePatchDrawable=NinePatchHelper.buildMulti(this,bitmap);

7TextViewtextView=newTextView(this);

8textView.setTextSize(25);

9textView.setPadding(20,10,20,10);

10textView.setText(strArray[i]);

11textView.setGravity(Gravity.CENTER_VERTICAL);

12LinearLayout.LayoutParamslayoutParams=newLinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);

13layoutParams.leftMargin=20;

14layoutParams.rightMargin=20;

15textView.setLayoutParams(layoutParams);

16if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN){

17textView.setBackground(ninePatchDrawable);

18}

19mLlRoot.addView(textView);

20}

21}catch(IOExceptione){

22e.printStackTrace();

23}

可以看到,我们的图片完美拉伸

参考文章

/developer/article/1168755?

https://mp./s/rZyt9ECypfwvNnXKt5xtqg

推荐阅读早就是优势!我每年得忽悠10万程序员上车为什么说80%的程序员都缺乏基本功?

编程·思维·职场

欢迎扫码关注

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。