Android制作卡片類長圖方法設(shè)計

在實現(xiàn)需求過程中谣旁,產(chǎn)品喜歡上了分享大圖卡片。但是發(fā)現(xiàn)每個地方分享的卡片大圖還不一樣滋早。經(jīng)過分析發(fā)現(xiàn)還是能夠找到一點共性榄审。先上圖看看最基礎(chǔ)的卡片樣子。由于分享的卡片在四張圖片上下都需要自定義一些其他控件杆麸,主要有文字商品信息等等搁进。

在實現(xiàn)之前先思考了一下,需要解決的問題有以下幾個:

  1. 因為每個地方的分享大圖是不一樣的昔头,需要找出共性部分饼问,并將不同之處方便替換;
  2. 在卡片中的幾張圖片還沒下載完就吊起分享肯定是不行的揭斧。這就需要監(jiān)聽圖片下載了莱革;
  3. 分享到的App對大圖都是有大小限制的,其中微博限制最大2M讹开,這樣就需要進行圖片壓縮盅视。

公共部分

在實現(xiàn)的過程中,需要一個layout文件萧吠,先將公共部分設(shè)置好左冬,像上半部分和下半部分都是共有的桐筏,所以直接放在布局中纸型。在布局的最外層需要使用到ScrollView,因為卡片可能會很長梅忌,只有ScrollView能夠達(dá)到這種長圖效果狰腌。此時在布局中需要預(yù)留出一個位置來放置下載好的圖片,預(yù)留的包裹圖片的控件叫做mShareImgContainer牧氮,在mShareImgContainer的上下也需要各預(yù)留一個ViewGroup琼腔,放置會變化的東西。公共的部分都很好做踱葛,下面仔細(xì)說一下下載圖和放置圖的處理部分丹莲。

非公共部分

圖2

如果沒有非公共部分光坝,可以不使用此方法。非公共部分由于分享的內(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ò)請求失敗");
      }
});

參考文檔和延伸閱讀

  1. App圖片壓縮裁剪原理和上傳方案旁蔼,以及那些有趣的事兒...
  2. Android圖片編碼機制深度解析(Bitmap锨苏,Skia,libJpeg)
  3. Android Bitmap 優(yōu)化(1) - 圖片壓縮
  4. https://github.com/bither/bither-android-lib
  5. 非常全面的 Android Bitmap 知識點梳理
  6. 你的 Bitmap 究竟占多大內(nèi)存棺聊?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚓炬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子躺屁,更是在濱河造成了極大的恐慌肯夏,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犀暑,死亡現(xiàn)場離奇詭異驯击,居然都是意外死亡,警方通過查閱死者的電腦和手機耐亏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門徊都,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人广辰,你說我怎么就攤上這事暇矫。” “怎么了择吊?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵李根,是天一觀的道長。 經(jīng)常有香客問我几睛,道長房轿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任所森,我火速辦了婚禮囱持,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焕济。我一直安慰自己纷妆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布晴弃。 她就那樣靜靜地躺著掩幢,像睡著了一般逊拍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粒蜈,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音旗国,去河邊找鬼枯怖。 笑死,一個胖子當(dāng)著我的面吹牛能曾,可吹牛的內(nèi)容都是我干的度硝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼寿冕,長吁一口氣:“原來是場噩夢啊……” “哼蕊程!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起驼唱,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤藻茂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后玫恳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辨赐,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年京办,在試婚紗的時候發(fā)現(xiàn)自己被綠了掀序。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡惭婿,死狀恐怖不恭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情财饥,我是刑警寧澤换吧,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站钥星,受9級特大地震影響式散,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜打颤,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一暴拄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧编饺,春花似錦乖篷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豁鲤。三九已至,卻和暖如春鲸沮,著一層夾襖步出監(jiān)牢的瞬間琳骡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工讼溺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留楣号,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓怒坯,卻偏偏與公主長得像炫狱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剔猿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評論 25 707
  • 先發(fā)一張昨天去看我雷哥演唱會的皂片然后再說正文哈哈视译。 簡介 由于工作原因,boss下達(dá)的任務(wù)就大概說了對圖片進行壓...
    我叫王菜鳥閱讀 5,232評論 2 16
  • 此文章首發(fā): https://mp.weixin.qq.com/s/QZ-XTsO7WnNvpnbr3DWQmg ...
    Shawn_Dut閱讀 1,697評論 1 10
  • 測試覆蓋率归敬,簡單的說酷含,就是評價測試活動覆蓋產(chǎn)品代碼的指標(biāo)。測試的目的汪茧,是確認(rèn)產(chǎn)品代碼按照預(yù)期一樣工作第美,也可以看作是...
    一個壞員工閱讀 2,170評論 1 2
  • 這幾年,隨著互聯(lián)網(wǎng)的流行陆爽,人們的真實交流越來越少什往,微信越來越多。提到微信慌闭,我們不得不想到紅包和微商這兩個詞别威,且不論...
    笑雨閱讀 93評論 0 0