android后臺通過View生成分享圖片

最近工作特忙,好久沒靜下心總結(jié)一些開發(fā)中的心得,后面會陸續(xù)寫一些文章總結(jié)一下最近遇到的問題和一些收獲吧~

閑話少說,今天想跟大家分享的是,在android中,如何后臺將一個(gè)view繪制成圖片,并簡單梳理下其中遇到的坑。很多app都有這么一個(gè)功能,當(dāng)用戶完成了app的某個(gè)任務(wù)時(shí),產(chǎn)品希望用戶點(diǎn)擊分享的時(shí)候,能動(dòng)態(tài)繪制出一張圖片,讓用戶的分享的內(nèi)容更加生動(dòng)化。舉個(gè)例子,比如扇貝單詞的打卡,點(diǎn)擊分享到新浪微博的時(shí)候,app會動(dòng)態(tài)在后臺生成一張圖片,用戶確認(rèn)分享就會將這張圖片分享出去。首先確認(rèn)一下分享的圖片上包含的元素吧:

  • ui提供的圖片素材
  • 用戶的信息,比如昵稱,頭像等
  • 本次任務(wù)完成的數(shù)據(jù),比如跑了多少km啊,背了多少單詞啊,復(fù)雜點(diǎn)的話還會包括一張網(wǎng)絡(luò)圖片,需要加載完畢后再生成指定的圖片

比如說向下圖這樣(這算個(gè)廣告吧)

461480663.jpg

首先可以確認(rèn)的是,直接在View上布局不是一件難事,需要在代碼中操作的信息如前面提到的用戶信息啊,本次任務(wù)的數(shù)據(jù)啊,和兩張需要異步加載的圖片(軌跡圖和頭像)。

首先這不是一個(gè)簡單的截屏,有些app會將分享的圖片先展示給用戶,然后當(dāng)前頁面“截屏”,生成一張圖片,然后調(diào)取第三方的圖片分享,總結(jié)來說要么是通過View.getDrawingCache()方法拿到當(dāng)前View的緩存,要么是直接調(diào)用Bitmap.createBitmap()生成Bitmap。我們簡單分析一下這兩種做法:

方法1 View.getDrawingCache() 只適用于分享的View已經(jīng)完整展示在用戶的屏幕上,超出屏幕范圍內(nèi)的內(nèi)容是不在生成的Bitmap內(nèi)的。因?yàn)閍ndroid手機(jī)的屏幕尺寸差異太大,通常我們需要生成的圖片不會很短,所以很難保證這點(diǎn),同時(shí)如果當(dāng)前展示的View和最終生成的圖片有一些差異的話,比如某個(gè)按鈕不顯示,某個(gè)文字換個(gè)內(nèi)容等,就沒辦法用這種辦法了。

第二種其實(shí)也是我們最終采用的方式,不過沒那么簡單,先來看這樣一種做法,在實(shí)踐中證明存在挺多問題。不過確實(shí)有人在采用,還是說一下吧

假設(shè)我當(dāng)前是在A頁面,我要分享出去的B圖片和A頁面只需要隱藏分享按鈕,這種方法的做法是:

vShare.setVisibility(INVISIBLE);
Bitmap b = Bitmap.createBitmap( v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888); 
Canvas c = new  Canvas(b); 
v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 
v.draw(c);
return b;

說一下問題在哪,首先生成Bitmap的操作應(yīng)該是后臺異步操作,當(dāng)前app應(yīng)該有一個(gè)阻斷態(tài),我們會發(fā)現(xiàn),用戶會觀察到分享按鈕消失了,然后生成圖片后又再次出現(xiàn),一個(gè)按鈕也許還ok,如果界面上的差異很大,這種方式給用戶的體驗(yàn)就很不好。其次,也是最重要的,通常我們View的布局的寬高都是類似于macth_parent,wrap_content, 分享出去的圖片尺寸無法控制(完全取決于手機(jī)的屏幕尺寸),圖片中的一些元素的寬度(例如異步加載的網(wǎng)絡(luò)圖片)經(jīng)過試驗(yàn)發(fā)現(xiàn)是異常的,原因我暫時(shí)還不清楚,大家可以和我探討一下。

像我這個(gè)項(xiàng)目中需要生成的圖片,和分享頁面可以說差別非常大,那么我們該如何處理呢?

首先單獨(dú)寫一個(gè)布局,寬高全都是固定值。我設(shè)置的寬高單位是dp,也就是說我生成的圖片的實(shí)際寬高取決于用戶手機(jī)的屏幕密度,好處在于低配手機(jī)通常性能是首要考慮目標(biāo),尺寸過大很容易導(dǎo)致OOM,這些低配手機(jī)的屏幕密度一般都不高,而同時(shí)高配手機(jī)上,生成的分享圖片如果不夠清晰,給用戶的體驗(yàn)就很不好(想像一下高清屏幕上的顆粒圖吧)。因?yàn)樯婕暗綌?shù)據(jù)的展示,我這邊采取自定義View的方式,假設(shè)名稱叫ShareView,它需要對外暴露這樣幾個(gè)方法:

  1. 根據(jù)用戶是否登陸,繪制不同的樣式
  2. 接收相關(guān)數(shù)據(jù),填充指定View
  3. 提供一個(gè)生成分享圖片鏈接Uri的方法,并在適當(dāng)?shù)臅r(shí)機(jī),回調(diào)外部的生成圖片成功或失敗的回調(diào)方法。

思路確定,這里只提供最核心的代碼:

/** 
* 創(chuàng)建分享的圖片文件 
*/
public String createShareFile() {    
    Bitmap bitmap = createBitmap();
    //將生成的Bitmap插入到手機(jī)的圖片庫當(dāng)中,獲取到圖片路徑
    String filePath = MediaStore.Images.Media.insertImage(getContext().getContentResolver(),     bitmap, null, null);    
    //及時(shí)回收Bitmap對象,防止OOM
    if (!bitmap.isRecycled()) {        
        bitmap.recycle();    
    } 
    //轉(zhuǎn)uri之前必須判空,防止保存圖片失敗
    if (TextUtils.isEmpty(filePath)) {        
        return "";    
    }    
    return getRealPathFromURI(getContext(), Uri.parse(filePath));
}

/** 
* 創(chuàng)建分享Bitmap 
*/
private Bitmap createBitmap() {  
    //自定義ViewGroup,一定要手動(dòng)調(diào)用測量,布局的方法  
    measure(getLayoutParams().width, getLayoutParams().height);    
    layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
    //如果圖片對透明度無要求,可以設(shè)置為RGB_565
    Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);    
    Canvas canvas = new Canvas(bitmap);    
    draw(canvas);   
    return bitmap;
}

private static String getRealPathFromURI(Context context, Uri contentUri) {
    Cursor cursor = null;    
    try {        
        String[] proj = {MediaStore.Images.Media.DATA};        
        cursor = context.getContentResolver().query(contentUri, proj, null, null, null);       
         if (cursor == null) {            
            return "";        
          }        
         int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();        
        return cursor.getString(column_index);    
    } finally {        
        if (cursor != null) {            
            cursor.close();       
        }    
   }
}

核心代碼交代完了,說一下一些小點(diǎn)吧:

  1. 網(wǎng)絡(luò)圖片需要加載完成后再回調(diào)生成圖片成功的方法,例如在Glide的RequestListener等。
  2. 從職責(zé)單一的角度,我們需要定義一個(gè)圖片分享管理器的類,類似于ImageShareManager,諸如處理子線程創(chuàng)建分享圖片,管理生成的圖片(登陸/未登錄),具體如何布局代碼,我想大家都有自己的想法~
  3. 由于內(nèi)存考慮和第三方分享圖片時(shí)的限制,生成的圖片大小需要在實(shí)踐中自己把握,像我項(xiàng)目中現(xiàn)在分享出去的圖片寬高為360dp*892dp, 1080p的手機(jī)生成的圖片大概150k,基本上都能成功分享。只遇到2k屏幕的手機(jī),朋友圈分享圖片失敗(微信聊天,qq,qq空間,新浪微博都可以),于是針對2k屏幕,判斷下屏幕密度,如果大于3.0的,我會采用寬高縮小的布局文件。我在項(xiàng)目中嘗試過直接縮放Bitmap,結(jié)果發(fā)現(xiàn)圖片質(zhì)量模糊的非常厲害,UI無法接受,應(yīng)該是強(qiáng)制縮放的效果本身就很差,建議還是對這部分手機(jī)單獨(dú)處理吧。
  4. 分享到微信聊天的圖片,需要設(shè)置縮略圖,否則對方聊天界面不打開大圖是看不到東西的,另外,網(wǎng)上所謂的32k的限制我沒遇到,就算是也肯定指縮略圖,原圖不超過200k應(yīng)該還是可以的。(吐槽一下當(dāng)時(shí)幾個(gè)同事都信誓旦旦說圖片不能超過32k,害得我折騰了好久,又是縮小布局,又是縮放Bitmap,最后發(fā)現(xiàn)原圖根本沒那個(gè)限制,轉(zhuǎn)過頭來問那幾個(gè)同事,語氣又不那么確定了...總之不能輕信很多沒經(jīng)過驗(yàn)證的說法)

謝謝大家

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容