100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Android点9图机制及在聊天气泡中的应用

Android点9图机制及在聊天气泡中的应用

时间:2024-07-12 22:46:56

相关推荐

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}910}11}

点九图上传服务器流程

aapt 转换命令

单个图片文件转换

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

批量转换

1#批量转换2./aaptc-SinputDir-CoutputDir3#inputDir为原始.9图文件夹,outputDir为输出文件夹

执行成功实例

1jundeMacBook-Pro:一期气泡junxu$./aaptc-S/Users/junxu/Desktop/一期气泡/气泡需求整理-C/Users/junxu/Desktop/一期气泡/output2CrunchingPNGFilesinsourcedir:/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{23privatestaticfinalStringTAG="NinePatchChunk";45publicfinalRectmPaddings=newRect();67publicintmDivX[];8publicintmDivY[];9publicintmColor[];1011privatestaticfloatdensity=IMO.getInstance().getResources().getDisplayMetrics().density;1213privatestaticvoidreadIntArray(finalint[]data,finalByteBufferbuffer){14for(inti=0,n=data.length;i<n;++i)15data[i]=buffer.getInt();16}1718privatestaticvoidcheckDivCount(finalintlength){19if(length==0||(length&0x01)!=0)20thrownewIllegalStateException("invalidnine-patch:"+length);21}2223publicstaticRectgetPaddingRect(finalbyte[]data){24NinePatchChunkdeserialize=deserialize(data);25if(deserialize==null){26returnnewRect();27}28}2930publicstaticNinePatchChunkdeserialize(finalbyte[]data){31finalByteBufferbyteBuffer=32ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());3334if(byteBuffer.get()==0){35returnnull;//isnotserialized36}3738finalNinePatchChunkchunk=newNinePatchChunk();39chunk.mDivX=newint[byteBuffer.get()];40chunk.mDivY=newint[byteBuffer.get()];41chunk.mColor=newint[byteBuffer.get()];4243try{44checkDivCount(chunk.mDivX.length);45checkDivCount(chunk.mDivY.length);46}catch(Exceptione){47returnnull;48}495051//skip8bytes52byteBuffer.getInt();53byteBuffer.getInt();545556chunk.mPaddings.left=byteBuffer.getInt();57chunk.mPaddings.right=byteBuffer.getInt();58chunk.mPaddings.top=byteBuffer.getInt();59chunk.mPaddings.bottom=byteBuffer.getInt();606162//skip4bytes63byteBuffer.getInt();6465readIntArray(chunk.mDivX,byteBuffer);66readIntArray(chunk.mDivY,byteBuffer);67readIntArray(chunk.mColor,byteBuffer);6869returnchunk;70}71}7273NinePatchDrawablepatchy=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//skip8bytes13byteBuffer.getInt();14byteBuffer.getInt();15chunk.mPaddings.left=byteBuffer.getInt();16chunk.mPaddings.right=byteBuffer.getInt();17chunk.mPaddings.top=byteBuffer.getInt();18chunk.mPaddings.bottom=byteBuffer.getInt();19//skip4bytes20byteBuffer.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*Thischunkspecifieshowtosplitanimageintosegmentsfor3*scaling.4*5*ThereareJhorizontalandKverticalsegments.Thesesegmentsdivide6*theimageintoJ*Kregionsasfollows(whereJ=4andK=3):7*8*F0S0F1S19*+-----+----+------+-------+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*Eachhorizontalandverticalsegmentisconsideredtobyeither22*stretchable(markedbytheSxlabels)orfixed(markedbytheFy23*labels),inthehorizontalorverticalaxis,respectively.Inthe24*aboveexample,thefirstishorizontalsegment(F0)isfixed,the25*nextisstretchableandthentheycontinuetoalternate.Notethat26*thesegmentlistforeachaxiscanbeginorendwithastretchable27*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;78ByteBufferbyteBuffer=ByteBuffer.allocate(bufferSize).order(ByteOrder.nativeOrder());9//第一个byte,要不等于010byteBuffer.put((byte)1);1112//mDivXlength13byteBuffer.put((byte)2);14//mDivYlength15byteBuffer.put((byte)2);16//mColorslength17byteBuffer.put((byte)colorSize);1819//skip20byteBuffer.putInt(0);21byteBuffer.putInt(0);2223//padding先设为024byteBuffer.putInt(0);25byteBuffer.putInt(0);26byteBuffer.putInt(0);27byteBuffer.putInt(0);2829//skip30byteBuffer.putInt(0);3132//mDivX33byteBuffer.putInt(xRegions[0]);34byteBuffer.putInt(xRegions[1]);3536//mDivY37byteBuffer.putInt(yRegions[0]);38byteBuffer.putInt(yRegions[1]);3940//mColors41for(inti=0;i<colorSize;i++){42byteBuffer.putInt(NO_COLOR);43}4445returnbyteBuffer.array();

create-a-ninepatch-ninepatchdrawable-in-runtime

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

1publicclassNinePatchBuilder{2intwidth,height;3Bitmapbitmap;4Resourcesresources;5privateArrayList<Integer>xRegions=newArrayList<Integer>();6privateArrayList<Integer>yRegions=newArrayList<Integer>();78publicNinePatchBuilder(Resourcesresources,Bitmapbitmap){9width=bitmap.getWidth();10height=bitmap.getHeight();11this.bitmap=bitmap;12this.resources=resources;13}1415publicNinePatchBuilder(intwidth,intheight){16this.width=width;17this.height=height;18}1920publicNinePatchBuilderaddXRegion(intx,intwidth){21xRegions.add(x);22xRegions.add(x+width);23returnthis;24}2526publicNinePatchBuilderaddXRegionPoints(intx1,intx2){27xRegions.add(x1);28xRegions.add(x2);29returnthis;30}3132publicNinePatchBuilderaddXRegion(floatxPercent,floatwidthPercent){33intxtmp=(int)(xPercent*this.width);34xRegions.add(xtmp);35xRegions.add(xtmp+(int)(widthPercent*this.width));36returnthis;37}3839publicNinePatchBuilderaddXRegionPoints(floatx1Percent,floatx2Percent){40xRegions.add((int)(x1Percent*this.width));41xRegions.add((int)(x2Percent*this.width));42returnthis;43}4445publicNinePatchBuilderaddXCenteredRegion(intwidth){46intx=(int)((this.width-width)/2);47xRegions.add(x);48xRegions.add(x+width);49returnthis;50}5152publicNinePatchBuilderaddXCenteredRegion(floatwidthPercent){53intwidth=(int)(widthPercent*this.width);54intx=(int)((this.width-width)/2);55xRegions.add(x);56xRegions.add(x+width);57returnthis;58}5960publicNinePatchBuilderaddYRegion(inty,intheight){61yRegions.add(y);62yRegions.add(y+height);63returnthis;64}6566publicNinePatchBuilderaddYRegionPoints(inty1,inty2){67yRegions.add(y1);68yRegions.add(y2);69returnthis;70}7172publicNinePatchBuilderaddYRegion(floatyPercent,floatheightPercent){73intytmp=(int)(yPercent*this.height);74yRegions.add(ytmp);75yRegions.add(ytmp+(int)(heightPercent*this.height));76returnthis;77}7879publicNinePatchBuilderaddYRegionPoints(floaty1Percent,floaty2Percent){80yRegions.add((int)(y1Percent*this.height));81yRegions.add((int)(y2Percent*this.height));82returnthis;83}8485publicNinePatchBuilderaddYCenteredRegion(intheight){86inty=(int)((this.height-height)/2);87yRegions.add(y);88yRegions.add(y+height);89returnthis;90}9192publicNinePatchBuilderaddYCenteredRegion(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;i<COLOR_SIZE;i++)138byteBuffer.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;i<5;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

推荐阅读

编程·思维·职场

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