Android影片圖片縮圖的獲取

語言: CN / TW / HK

這是我參與11月更文挑戰的第6天,活動詳情檢視:2021最後一次更文挑戰

Android影片圖片縮圖的獲取

這次專案中使用到拍照和拍攝影片的功能,那麼既然是拍照和拍影片肯定涉及到縮圖的處理。所以接下來我們要學習下載android中如何處理圖片和影片來獲取我們需要的縮圖。幸運的是,android已經給我們提供好了具體的工具類,所以我們只需要學習這麼工具類的api如何使用。

現在直接切入正題,對於獲取影片縮圖,android系統中提供了ThumbnailUtilsandroid.provider.MediaStore.Images.Thumbnailsandroid.provider.MediaStore.Video.ThumbnailsMediaMetadataRetriever幾個類可以使用,在這篇文章中,我僅僅對ThumbnailsUtils進行分析,肯能後續文章會介紹下後面三個類。

ThumbnailUtils

ThumbnailUtils方法是在android2.2(api8)之後新增的一個,該類為我們提供了三個靜態方法供我們使用。

  • ThumbnailUtils.createVideoThumbnail(filePath, kind): 建立影片縮圖,filePath:檔案路徑,kind:MINI_KIND or MICRO_KIND
  • ThumbnailUtils.extractThumbnail(bitmap, width, height): 將bitmap裁剪為指定的大小
  • ThumbnailUtils.extractThumbnail(bitmap, width, height, options):將bitmap裁剪為指定的大小,可以有引數BitmapFactory.Options引數

下面我們分別介紹下這三個方法:
(1)、createVideoThumbnail
我們先看看它的原始碼:

``` /* * Create a video thumbnail for a video. May return null if the video is * corrupt or the format is not supported. * * @param filePath the path of video file * @param kind could be MINI_KIND or MICRO_KIND /
public static Bitmap createVideoThumbnail(String filePath, int kind) {
Bitmap bitmap = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(filePath);
bitmap = retriever.getFrameAtTime(-1);
} catch (IllegalArgumentException ex) {
// Assume this is a corrupt video file
} catch (RuntimeException ex) {
// Assume this is a corrupt video file.
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
// Ignore failures while cleaning up.
}
}

    if (bitmap == null) return null;

    if (kind == Images.Thumbnails.MINI_KIND) {  
        // Scale down the bitmap if it's too large.  
        int width = bitmap.getWidth();  
        int height = bitmap.getHeight();  
        int max = Math.max(width, height);  
        if (max > 512) {  
            float scale = 512f / max;  
            int w = Math.round(scale * width);  
            int h = Math.round(scale * height);  
            bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);  
        }  
    } else if (kind == Images.Thumbnails.MICRO_KIND) {  
        bitmap = extractThumbnail(bitmap,  
                TARGET_SIZE_MICRO_THUMBNAIL,  
                TARGET_SIZE_MICRO_THUMBNAIL,  
                OPTIONS_RECYCLE_INPUT);  
    }  
    return bitmap;  
}

```

通過觀看原始碼:我們發現該方法的內部也是使用了一個MediaMetadataRetriever的物件,那這個物件究竟是何方神聖呢?容我們稍後再說,反正就是通過這個物件獲得了一個bitmap物件,然後再根據kind的型別進行圖片的壓縮。原始碼的總體思路就是這樣。
引數說明:

  • filePath表示影片檔案路徑
  • kind表示型別,可以有兩個選項,分別是Images.Thumbnails.MICRO_KIND和Images.Thumbnails.MINI_KIND,其中,MINI_KIND: 512 x 384,MICRO_KIND: 96 x 96,當然讀了程式碼你會發現,你也可以傳入任意的int型數字,只是不起作用罷了。

(2)、extractThumbnail(Bitmap source, int width, int height):進行圖片的裁剪
我們先看看原始碼:

/** * Creates a centered bitmap of the desired size. * * @param source original bitmap source * @param width targeted width * @param height targeted height */ public static Bitmap extractThumbnail( Bitmap source, int width, int height) { return extractThumbnail(source, width, height, OPTIONS_NONE); }

原始碼很簡單,就是呼叫了 extractThumbnail的兄弟。
引數說明:

  • source:表示圖片原始檔(Bitmap型別)
  • width:表示壓縮成後的寬度
  • height:表示壓縮成後的高度

(3)、extractThumbnail( Bitmap source, int width, int height, int options):進行圖片的額裁剪,指定options選項。
看看原始碼:

``` /* * Creates a centered bitmap of the desired size. * * @param source original bitmap source * @param width targeted width * @param height targeted height * @param options options used during thumbnail extraction /
public static Bitmap extractThumbnail(
Bitmap source, int width, int height, int options) {
if (source == null) {
return null;
}

        float scale;  
        if (source.getWidth() < source.getHeight()) {  
            scale = width / (float) source.getWidth();  
        } else {  
            scale = height / (float) source.getHeight();  
        }  
        Matrix matrix = new Matrix();  
        matrix.setScale(scale, scale);  
        Bitmap thumbnail = transform(matrix, source, width, height,  
                OPTIONS_SCALE_UP | options);  
        return thumbnail;  
 }

```

原始碼中的處理採用Matrix物件進行變換操作,也不是很複雜,對Matrix不是很熟悉的同學可以搜下用法。在我們自定義view中還是很有用途的。
引數說明:

  • source:表示圖片原始檔(Bitmap型別)
  • width:表示壓縮成後的寬度
  • height:表示壓縮成後的高度
  • options:表示縮圖抽取時的選項,如果options定義為OPTIONS_RECYCLE_INPUT,則回收@param source這個資原始檔(除非縮圖等於@param source)

ThumbnailUtils類的核心就是這三個方法,我們只需要知道如何使用即可。更多內容參考歐陽鵬寫的這篇文章

下面我們通過一個案例來介紹下它的使用。案例的需求就是:呼叫系統的相機進行拍照和拍攝影片功能,然後展示縮圖。需求很簡單,核心就是利用ThumbnailUtils工具類進行縮圖的處理。現在我們開始來完成這個需求。我們在eclipse中建立工程PicturePhotoDemo。

1、首先進行我們的主頁面佈局搭建:

```

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:textSize="20sp"
    android:padding="10dp"
    android:text="@string/hello_world" />

<LinearLayout 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:gravity="center_horizontal"
    android:orientation="horizontal">
    <Button 
        android:id="@+id/take_picture"
        android:layout_height="wrap_content"
        android:layout_width="100dp"
        android:text="@string/take_picture"
        android:textSize="17sp"/>
    <Button 
        android:id="@+id/take_video"
        android:layout_height="wrap_content"
        android:layout_width="100dp"
        android:text="@string/take_video"
        android:textSize="17sp"/>
</LinearLayout>
<com.dsw.horizonlistview.HorizontalListView
    android:id="@+id/horizontalListView"
    android:layout_width="match_parent"
    android:layout_gravity="center_vertical"
    android:spacing="5dp"
    android:layout_height="100dp"
    android:scrollbars="@null">

</com.dsw.horizonlistview.HorizontalListView>
</LinearLayout>

```

效果圖如下: layout

在上面的佈局中,我們使用了一個自定義View名為HorizontalListView(一個大牛寫的,拿來用了)。HorizontalListView的原始碼就不貼了,太多了,而且不是我們的重點,有興趣研究的同學可以稍後下載demo工程,在工程中有。我們只需知道HorizontalListView是一個橫向的ListView,使用方法同ListView,同樣需要Adapter的使用。

2、我們新建一個MeadiaInformation的實體,用於儲存我們的照片資訊。

public class MeadiaInformation { //圖片路徑 public String srcPath; //圖片:type=0 影片:type=1 public int type; //bitmap資源圖片 public Bitmap bitmap; }

3、我們自定義HorizontalListViewAdapter介面卡,用於處理我們的圖片展示。

``` public class HorizontalListViewAdapter extends BaseAdapter{ private Context mContext; private LayoutInflater mInflater; Bitmap iconBitmap; private int selectIndex; private List list; public HorizontalListViewAdapter(Context context, List list){ this.mContext = context; this.list = list; mInflater=(LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//LayoutInflater.from(mContext); } @Override public int getCount() { return list == null ? 0 : list.size(); } @Override public Object getItem(int position) { return list == null ? null : list.get(position); }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if(convertView==null){
            holder = new ViewHolder();
            convertView = mInflater.inflate(R.layout.horizontal_list_item, null);
            holder.mImage=(ImageView)convertView.findViewById(R.id.img_list_item);
            holder.mVideoImage = (ImageView) convertView.findViewById(R.id.img_video);
            convertView.setTag(holder);
        }else{
            holder=(ViewHolder)convertView.getTag();
        }

        //判斷當前項是否為選中項
        if(position == selectIndex){
            convertView.setSelected(true);
        }else{
            convertView.setSelected(false);
        }
        if(list != null && list.size() > 0){
            iconBitmap = list.get(position).bitmap;
            holder.mImage.setImageBitmap(iconBitmap);
            if(list.get(position).type == 1){//如果是影片,就顯示影片播放按鈕
                holder.mVideoImage.setVisibility(View.VISIBLE);
            }else{//如果不是影片就不顯示該播放按鈕
                holder.mVideoImage.setVisibility(View.INVISIBLE);
            }
        }
        return convertView;
    }

    private static class ViewHolder {
        //展示圖片的額ImageView
        private ImageView mImage;
        //展示影片中間的播放圖片
        private ImageView mVideoImage;
    }

    /**
     * 新增展示項
     * @param infor
     */
    public void addInformation(MeadiaInformation infor){
        list.add(infor);
        notifyDataSetChanged();
    }

    /**
     * 新增音訊集合資訊
     * @param listInfo
     */
    public void addInformationList(List<MeadiaInformation> listInfo){
        list.addAll(listInfo);
        notifyDataSetChanged();
    }

    /**
     * 新增選中的item
     * @param i
     */
    public void setSelectIndex(int i){
        selectIndex = i;
    }
    /**
     * 獲取當前選中項
     * @return
     */
    public int getSelectIndex(){
        return this.selectIndex;
    }
}

```

3、萬事具備,我們需要在MainActivity中處理我們的拍照邏輯,然後處理縮圖。本來我是想貼處理那部分的程式碼的,但是感覺邏輯有點接不上了,所以還是把MainActivity都貼出來吧!

``` public class MainActivity extends Activity { //拍照的請求碼 private static final int REQUEST_TAKE_PITURE = 100; //拍影片的請求碼 private static final int REQUEST_TAKE_VIDEO = 200; private MainActivity _this; //拍照按鈕 private Button btn_takePicture; //拍影片按鈕 private Button btn_takeVideo; //檔案儲存路徑 private String path; //檔案file private File photoFile; //圖片展示的ListView private HorizontalListView listView; //ListView的介面卡 private HorizontalListViewAdapter listViewAdapter; //構造的多媒體物件 private MeadiaInformation infor; private DisplayMetrics metrics; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); _this =this; btn_takePicture = (Button) findViewById(R.id.take_picture); btn_takeVideo = (Button) findViewById(R.id.take_video); listView = (HorizontalListView) findViewById(R.id.horizontalListView); metrics = getResources().getDisplayMetrics(); setOnListener(); initAdapter(); initPath(); }

    /**
     * 初始化儲存路徑
     */
    private void initPath(){
        //判斷是否有儲存卡
        if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
            //有儲存卡獲取路徑
            path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/PhotoDemo/";
        }else{
            //沒有儲存卡的時候,儲存到這個路徑
            path = getApplicationContext().getFilesDir().getPath()+ "/PhotoDemo/";
        }
        File file = new File(path);
        if(!file.exists()){
            file.mkdirs();
        }
    }

    /**
     * 設定介面卡的初始化
     */
    private void initAdapter(){
        List<MeadiaInformation> list = new ArrayList<MeadiaInformation>();
        listViewAdapter = new HorizontalListViewAdapter(_this, list);
        listView.setAdapter(listViewAdapter);
    }

    /**
     * 設定控制元件監聽
     */
    private void setOnListener(){
        btn_takePicture.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Date date = new Date();
                String name = path + "/" + date.getTime() + ".jpg";
                photoFile = new File(name);
                Uri uri = Uri.fromFile(photoFile);
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
                intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 10);
                //啟動照相
                startActivityForResult(intent, REQUEST_TAKE_PITURE);
            }
        });

        btn_takeVideo.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Date date = new Date();
                String name = path + "/" + date.getTime() + ".3gp";
                photoFile = new File(name);
                Uri uri = Uri.fromFile(photoFile);
                Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
                intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 10);
                //啟動照相
                startActivityForResult(intent, REQUEST_TAKE_VIDEO);
            }
        });

        listView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position,
                    long arg3) {
                listViewAdapter.setSelectIndex(position);
                listViewAdapter.notifyDataSetChanged();
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        //通過requestCode判斷型別,通過resultCode判斷是否成功
        if(resultCode == RESULT_OK){
            infor = new MeadiaInformation();
            infor.srcPath = photoFile.getAbsolutePath();
            switch(requestCode){
            case REQUEST_TAKE_PITURE:
                infor.type =0;
                break;
            case REQUEST_TAKE_VIDEO:
                infor.type =1;
                break;
            }
            getBitmapFromFile();
            //將此MeadiaInformation新增到Adapter
            listViewAdapter.addInformation(infor);
        }
    }

    //根據檔案路徑獲取縮圖
    private void getBitmapFromFile() {
        /**
         * android系統中為我們提供了ThumbnailUtils工具類來獲取縮圖的處理。
         * ThumbnailUtils.createVideoThumbnail(filePath, kind)
         *          建立影片縮圖,filePath:檔案路徑,kind:MINI_KIND or MICRO_KIND  
         * ThumbnailUtils.extractThumbnail(bitmap, width, height)
         *          將bitmap裁剪為指定的大小
         * ThumbnailUtils.extractThumbnail(bitmap, width, height, options)
         *          將bitmap裁剪為指定的大小,可以有引數BitmapFactory.Options引數
         * 
         */
        Bitmap bitmap = null;
        if(infor.type == 0){//若果是圖片,即拍照
            //直接通過路徑利用BitmapFactory來形成bitmap
            bitmap = BitmapFactory.decodeFile(infor.srcPath);
        }else if(infor.type == 1){//如果是影片,即拍攝影片
            //利用ThumnailUtils
            bitmap = ThumbnailUtils.createVideoThumbnail(infor.srcPath, Images.Thumbnails.MINI_KIND);
        }

        //獲取圖片後,我們隊圖片進行壓縮,獲取指定大小
        if(bitmap != null){
            //裁剪大小
            bitmap = ThumbnailUtils.extractThumbnail(bitmap, (int)(100*metrics.density), (int)(100*metrics.density));
        }else{//如果為空,採用我們的預設圖片
            bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
        }
        infor.bitmap = bitmap;
    }
}

```

在MainActivity中我們點選【拍照】、【拍影片】按鈕進行拍照和拍視屏,然後重寫onActivityResult方法,進行拍攝回來的影片處理,接著最後就是對圖片或影片進行獲取縮圖處理,最後新增到listviewAdatper中進行展示。總體的處理邏輯就是這樣,程式碼註釋的也很詳細,有興趣的同學可以下載demo玩玩。先貼幾張效果圖:

photo1 photo2