在實現(xiàn)需求過程中谣旁,產(chǎn)品喜歡上了分享大圖卡片。但是發(fā)現(xiàn)每個地方分享的卡片大圖還不一樣滋早。經(jīng)過分析發(fā)現(xiàn)還是能夠找到一點共性榄审。先上圖看看最基礎(chǔ)的卡片樣子。由于分享的卡片在四張圖片上下都需要自定義一些其他控件杆麸,主要有文字商品信息等等搁进。
在實現(xiàn)之前先思考了一下,需要解決的問題有以下幾個:
- 因為每個地方的分享大圖是不一樣的昔头,需要找出共性部分饼问,并將不同之處方便替換;
- 在卡片中的幾張圖片還沒下載完就吊起分享肯定是不行的揭斧。這就需要監(jiān)聽圖片下載了莱革;
- 分享到的App對大圖都是有大小限制的,其中微博限制最大2M讹开,這樣就需要進行圖片壓縮盅视。
公共部分
在實現(xiàn)的過程中,需要一個layout文件萧吠,先將公共部分設(shè)置好左冬,像上半部分和下半部分都是共有的桐筏,所以直接放在布局中纸型。在布局的最外層需要使用到ScrollView,因為卡片可能會很長梅忌,只有ScrollView能夠達(dá)到這種長圖效果狰腌。此時在布局中需要預(yù)留出一個位置來放置下載好的圖片,預(yù)留的包裹圖片的控件叫做mShareImgContainer牧氮,在mShareImgContainer的上下也需要各預(yù)留一個ViewGroup琼腔,放置會變化的東西。公共的部分都很好做踱葛,下面仔細(xì)說一下下載圖和放置圖的處理部分丹莲。
非公共部分
如果沒有非公共部分光坝,可以不使用此方法。非公共部分由于分享的內(nèi)容不同會經(jīng)常變動ui效果甥材,因此這里采用了策略模式盯另,將分享圖片占位的頂部和底部留出,作為自定義部分洲赵,要使用此方法的需要自定義上下兩部分的可以自制一個策略類鸳惯,并將此方法設(shè)置到ShareImgManager中。具體代碼如下:
ShareImgManager shareImgManager = new ShareImgManager(getContext());
shareImgManager.setShareImgStrategy(new ShareUserCommentImg(getContext(), goodsComment, mShowGoodsComment.getGoods()));//自定義ui部分
shareImgManager.addImgBottomPart();
其中的ShareUserCommentImg類實現(xiàn)了ShareImgManager.ShareImgStrategy接口叠萍,接口包含了兩個方法芝发,分別是分享大圖的上下兩個部分,可以將相應(yīng)的view指定進去苛谷。只要返回相應(yīng)的view辅鲸,在ShareImgManager里面會將這兩部分加入進去,看下這個類的具體代碼:
public class ShareUserCommentImg implements ShareImgManager.ShareImgStrategy {
private Context mContext;
private GoodsComment mGoodsComment;
private CommentGoods mCommentGoods;
public ShareUserCommentImg(Context context, GoodsComment goodsComment, CommentGoods commentGoods) {
this.mContext = context;
mGoodsComment = goodsComment;
mCommentGoods = commentGoods;
}
@Override
public View imgTopPart() {
return null;
}
@Override
public View imgBottomPart() {
if (mCommentGoods == null) return null;
View mShareView = LayoutInflater.from(mContext).inflate(R.layout.share_user_comment_img_bottom_part, null);
TextView commentGoodsTitle = (TextView) mShareView.findViewById(R.id.comment_goods_title);
KaolaImageView belongGoodsIv = (KaolaImageView) mShareView.findViewById(R.id.belong_goods_iv);
TextView commentGoodsPrice = (TextView) mShareView.findViewById(R.id.comment_goods_price);
TextView shareCommentContent = (TextView) mShareView.findViewById(R.id.share_comment_content);
ImageLoaderManager.startLoad(new ImageLoaderBuilder().setKaolaImageView(belongGoodsIv)
.setImgUrl(mCommentGoods.getImageUrl()).setWidthHeight(35, 35));
commentGoodsPrice.setText("¥" + StringUtils.formatFloat(mCommentGoods.getActualCurrentPriceForApp()));
commentGoodsTitle.setText(mCommentGoods.getTitle());
shareCommentContent.setText(Html.fromHtml("<font color=\"#D22147\">@" + mGoodsComment.getNicknameKaola() + ": </font>" +
mGoodsComment.getCommentContent()));
return mShareView;
}
}
顯示圖片的部分
創(chuàng)建好加載圖片的ImageView控件之后抄腔,要add進去瓢湃。此時我在做的時候發(fā)現(xiàn)了一個問題就是控件雖然add進去了,也看到占了很大的位置赫蛇,但是只能顯示一片空白绵患,并沒有圖片顯示。后來發(fā)現(xiàn)悟耘,是因為我們使用的是Facebook的Fresco下載圖片框架問題落蝙,fresco在控件未attach到當(dāng)前視圖的時候是不會去下載圖片的,要在ondraw之后才會去下載圖片暂幼,因此在塞完控件之后只要draw一下筏勒,fresco就會異步去下載圖片⊥遥看一下這個方法的代碼:
public static Bitmap getBitmap(ScrollView scrollView) {
int width = scrollView.getWidth();
int height = scrollView.getHeight();
scrollView.setBackgroundColor(Color.WHITE);
if (0 == width || 0 == height) {
scrollView.measure(View.MeasureSpec.makeMeasureSpec(750, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
scrollView.layout(0, 0, scrollView.getMeasuredWidth(), scrollView.getMeasuredHeight());
width = scrollView.getWidth();
height = scrollView.getHeight();
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
final Canvas canvas = new Canvas(bitmap);
scrollView.draw(canvas);
return bitmap;
}
此時雖然進行了measure等一系列操作管行,但是只是占了位置,圖片還沒顯示上去邪媳,所以要等圖片下載完成之后再執(zhí)行一遍才可以看到圖片捐顷。
接下來就是下載圖片了,在下載圖片的時候需要一個計數(shù)器雨效,只有圖片全部下載完成之后才吊起分享功能迅涮。不然會出現(xiàn)某一個白色的情況。
計數(shù)器到達(dá)規(guī)定數(shù)量的時候徽龟,意味著圖片已經(jīng)下載完叮姑,此時再次調(diào)用上面的方法,重新繪制一遍据悔,就會生成一個可分享的Bitmap传透,但是此時還并不能作為分享圖片耘沼,因為圖片可能過大,所以需要壓縮朱盐。
壓縮圖片的部分
圖片壓縮方法主要有兩種:
1.質(zhì)量壓縮
質(zhì)量壓縮耕拷,方法如下:
image.compress(Bitmap.CompressFormat.JPEG, 50, baos);
其中第一個參數(shù)為所需要壓縮的圖片格式,可選三個格式JPEG托享,PNG和WEBP骚烧,試過這三個格式,發(fā)現(xiàn)JPEG的壓縮方式是最好的闰围。PNG是無損壓縮赃绊,WEBP如果需要壓縮的話,耗時是JPEG的好幾倍羡榴,而且壓縮效果也不好碧查。所以不是推薦的壓縮格式。第二個參數(shù)是需要壓縮到的圖片質(zhì)量校仑,第三個參數(shù)是圖片輸入流忠售。
關(guān)于壓縮的原理,看了一下源碼迄沫,發(fā)現(xiàn)谷歌使用的也是libjpeg這個被廣泛使用的開源JPEG圖像處理庫稻扬,android并沒有直接使用libjpeg,而是使用了一個封裝庫Skia羊瘩,通過Skia來使用libjpeg的泰佳。Skia對libjpeg進行了良好的封裝,基于這個引擎可以很方便為操作系統(tǒng)尘吗、瀏覽器等開發(fā)圖像處理功能逝她。libjpeg在壓縮圖像的時候,有一個參數(shù)叫optimize_coding睬捶,官方文檔中是這樣寫的:
boolean optimize_coding
TRUE causes the compressor to compute optimal Huffman coding tables
for the image. This requires an extra pass over the data and
therefore costs a good deal of space and time. The default is
FALSE, which tells the compressor to use the supplied or default
Huffman tables. In most cases optimal tables save only a few percent
of file size compared to the default tables. Note that when this is
TRUE, you need not supply Huffman tables at all, and any you do
supply will be overwritten.
意思就是如果設(shè)置optimize_coding為TRUE黔宛,將會使得壓縮圖像過程中基于圖像數(shù)據(jù)計算哈弗曼表,由于這個計算會顯著消耗空間和時間擒贸,默認(rèn)值被設(shè)置為FALSE臀晃。谷歌的Skia項目工程師們最終沒有設(shè)置這個參數(shù),optimize_coding在Skia中默認(rèn)的等于了FALSE酗宋,這就意味著更差的圖片質(zhì)量和更大的圖片文件积仗。關(guān)于為什么Android圖片比ios差疆拘,可以參考這篇文章:為什么Android的圖片質(zhì)量會比iPhone的差蜕猫?。
采用這種方法進行壓縮哎迄,并不會減少圖片的像素回右,官方文檔也解釋說, 它會讓圖片重新構(gòu)造, 但是有可能圖像的位深(即色深)和每個像素的透明度會變化,也就是說以jpeg格式壓縮后, 原來圖片中透明的元素將消失.所以這種格式很可能造成失真隆圆。
2.尺寸壓縮
尺寸壓縮主要使用到的是BitmapFactory.Options這個參數(shù),BitmapFactory.Options的成員變量主要有:
public Bitmap inBitmap;//重用Bitmap
public boolean inJustDecodeBounds;//設(shè)置只去讀圖片的附加信息(寬高),不去解析真實的Bitmap
public int inSampleSize;// 設(shè)置圖片的縮放比例(寬和高)
public int inDensity;
public int inTargetDensity;//inScaled設(shè)置為true的時候翔烁,如果InDensity和inTragetDensity都不是0并且不相等渺氧,那么bitmap就會被放縮成inTargtDensity
public int inScreenDensity;//正在使用的屏幕的像素密度
public boolean inScaled;
public int outWidth;
public int outHeight;
public String outMimeType;
inJustDecodeBounds:這個參數(shù)設(shè)置為true的時候,BitmapFactory通過decodeResource或者decodeFile解碼圖片時蹬屹,將會返回空(null)的Bitmap對象侣背,這樣可以避免Bitmap的內(nèi)存分配,但是它可以返回Bitmap的寬度慨默、高度以及MimeType贩耐。
inSampleSize:在獲取了圖片的寬高之后,在知道屏幕寬高的情況下厦取,就可以通過兩個的比例對圖片進行壓縮到屏幕尺寸進行顯示潮太。inSampleSize就是縮放比例,但是這個變量必須設(shè)置2的倍數(shù)虾攻,如果設(shè)置的非2的倍數(shù)铡买,他也會自動變換成最近的2的指數(shù)倍數(shù)。例如:inSampleSize=4霎箍,寬高都會變?yōu)樵瓉淼?/4奇钞,整體就會縮小16倍,壓縮比例還是很可觀的漂坏,但是壓縮到之前的16倍效果上就會差很多蛇券。
inPreferredConfig:
Bitmap.Config ARGB_4444:每個像素占四位,即A=4樊拓,R=4纠亚,G=4,B=4筋夏,那么一個像素點占4+4+4+4=16位
Bitmap.Config ARGB_8888:每個像素占四位蒂胞,即A=8,R=8条篷,G=8骗随,B=8,那么一個像素點占8+8+8+8=32位
Bitmap.Config RGB_565:每個像素占四位赴叹,即R=5鸿染,G=6,B=5乞巧,沒有透明度涨椒,那么一個像素點占5+6+5=16位
Bitmap.Config ALPHA_8:每個像素占四位,只有透明度,沒有顏色蚕冬。
3.我使用的壓縮方法
public static Bitmap compressImage(Bitmap image, int minSize) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
int bitmapSize = baos.toByteArray().length / 1024;
if (bitmapSize / minSize < 4) {//圖片需要縮小到1/4以內(nèi),用質(zhì)量壓縮
int options = minSize * 200 / bitmapSize;
while (baos.toByteArray().length / 1024 > minSize && options > 0) {
// 重置baos
baos.reset();
// 這里壓縮options%免猾,把壓縮后的數(shù)據(jù)存放到baos中
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 每次都減少10
options -= 10;
}
byte[] array = baos.toByteArray();
if (options >= 30) {
return decodeByteArray(array, 0, array.length);
}
}
//圖片需要縮小到1/4以上,用尺寸壓縮
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.RGB_565;
opts.inSampleSize = 1;
Bitmap bitmap = image;
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] array = baos.toByteArray();
while (bitmap.getByteCount() / 1024 > 2048) {
opts.inSampleSize *= 2;
bitmap = BitmapFactory.decodeByteArray(array, 0, array.length, opts);
}
return bitmap;
}
分享方法的使用
接下來就是ShareImgManager的使用過程,因為之前已經(jīng)設(shè)置好了公共和非公共部分囤热,所以在這里只要設(shè)置部分?jǐn)?shù)據(jù)猎提,并且調(diào)用createImgCard方法即可,看下代碼:
ShareImgManager shareImgManager = new ShareImgManager(getContext());
shareImgManager.setShareImgStrategy(new ShareUserCommentImg(getContext(), goodsComment, mShowGoodsComment.getGoods()));//自定義ui部分
shareImgManager.addImgBottomPart();
ShareImgManager shareImgManager = new ShareImgManager(getContext());
shareImgManager.setShareImgStrategy(new ShareUserCommentImg(getContext(), goodsComment, mShowGoodsComment.getGoods()));//自定義ui部分
shareImgManager.addImgBottomPart();
shareImgManager.setData(getContext(), "http://www.kaola.com/product/" + mShowGoodsComment.getGoods().getGoodsId() + ".html",R.drawable.ic_comment_share_head);//設(shè)置二維碼鏈接和頭部的背景圖
shareImgManager.createImgCard(getContext(), imgUrls, new ShareImgManager.ShareImgListener() {
@Override
public void createPicSuccess(String imgName) {
GoodsDetailUtils.shareComment(getActivity(), imgName, mCommentListLv);
}
@Override
public void createPicFailed() {
ToastUtils.show("網(wǎng)絡(luò)請求失敗");
}
});