1. 程式人生 > >Android上傳圖片之調用系統拍照和從相冊選擇圖片

Android上傳圖片之調用系統拍照和從相冊選擇圖片

item 取圖 空指針 ctu setimage tro edate eas tostring

Android上傳圖片之調用系統拍照和從相冊選擇圖片

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家公布

前言:
萬丈高樓平底起,萬事起於微末。不知不覺距離上篇博文已近四個月,2015年12月17日下午發了第一篇博文。如今是2016年4月6日。時間間隔長的過分啊,我自己都看不下去了。

原因呢?當然是自己的原因。事實上是有非常多時間來些博客的,可是這些時間都花在DOTA上了(還是太年輕啊)。請原諒我的過錯…….
一、概述:
如今差點兒應用都會用到上傳圖片的功能,而要上傳圖片,首先得選擇圖片。本文不針對怎樣上傳圖片到server(每一個項目與server交互的方式不同。因此不寫上傳圖片到server相關代碼)。僅僅是對選擇圖片做簡單的介紹,沒有涉及到對圖片的圓角處理與剪裁。本文主要涉及下面幾個簡單的知識點:

  • 簡單的調用系統拍照和系統相冊選擇圖片
  • 通過GridView實現動態加入圖片的效果
  • Adapter使用的小技巧
  • Fragment中調用系統拍照該怎麽獲取數據(接口回調)

二、實現:
我們先來看項目文件夾:
技術分享圖片
一個Adapter、兩個Activity,一個Fragment、一個工具類,一目了然。有人在這裏有疑問了,為什麽是兩個Activity?不是三個嗎?沒錯,理論上ChooseActivityChooseFragmentActivityBaseActivity加起來是三個,只是在這裏BaseActivity是模擬實際項目抽離Activity中公共的代碼,不做為視圖,所以我不把BaseActivity算進去。
ChooseActivity是模擬Activity中調用系統拍照和系統相冊選擇圖片。ChooseFragmentActivity中放入ChooseFragment模擬Fragment中調用系統拍照和系統相冊選擇圖片(在這裏我定死了一個Fragment模擬項目實際情況,實際情況一個Activity中會有多個Fragment),ImageUtils做一些簡單的圖片處理。

SelectPicPopupWindow一個簡單的PopupWindow,UploadImageAdapter動態選擇圖片上傳的適配器。
先來點效果圖吧:
技術分享圖片
技術分享圖片
圖中展示的效果:點擊默認圖片彈出PopupWindow讓用戶選擇拍照還是從相冊選擇圖片(模擬器中不便使用拍照功能,本人在幾臺手機上試過沒有問題,請到真機上測試),選擇好圖片後已選擇好的圖片可長按刪除。這裏控制了最多選擇6張圖片。

簡單的調用系統拍照和系統相冊選擇圖片
我們先來看是怎麽調用系統拍照和從相冊選擇圖片的:
申明組件與變量:

/**
     * 選擇圖片的返回碼
     */
    public final
static int SELECT_IMAGE_RESULT_CODE = 200; /** * 當前選擇的圖片的路徑 */ public String mImagePath; /** * 自己定義的PopupWindow */ private SelectPicPopupWindow menuWindow;

彈出PopupWindow:

    /**
     * 拍照或從圖庫選擇圖片(PopupWindow形式)
     */
    public void showPicturePopupWindow(){
        menuWindow = new SelectPicPopupWindow(this, new OnClickListener() {

            @Override
            public void onClick(View v) {
                // 隱藏彈出窗體
                menuWindow.dismiss();
                switch (v.getId()) {
                case R.id.takePhotoBtn:// 拍照
                    takePhoto();
                    break;
                case R.id.pickPhotoBtn:// 相冊選擇圖片
                    pickPhoto();
                    break;
                case R.id.cancelBtn:// 取消
                    break;
                default:
                    break;
                }
            }
        });  
        menuWindow.showAtLocation(findViewById(R.id.choose_layout), Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0);
    }   

拍照最重要的就是takephoto方法了。部分機型拍完照後沒有數據返回,僅僅能通過指定拍完照獲得圖片的存儲路徑來解決問題了。凝視寫的非常具體,這裏不再多解釋了。可是註意一點指定路徑的時候可能會出現拍完照後無法點確定返回,有的手機甚至會點擊後掛掉。這個時候會報不是有效路徑的錯誤。我遇到錯誤是在獲取到的與應用相關聯的路徑後面再創建一個文件/xxxx,至於為什麽不行。我也不知道原理。

private void takePhoto() {
        // 運行拍照前。應該先推斷SD卡是否存在
        String SDState = Environment.getExternalStorageState();
        if (SDState.equals(Environment.MEDIA_MOUNTED)) {
            /**
             * 通過指定圖片存儲路徑,解決部分機型onActivityResult回調 data返回為null的情況
             */
            //獲取與應用相關聯的路徑
            String imageFilePath = getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath();
            SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA); 
            //依據當前時間生成圖片的名稱
            String timestamp = "/"+formatter.format(new Date())+".jpg"; 
            File imageFile = new File(imageFilePath,timestamp);// 通過路徑創建保存文件
            mImagePath = imageFile.getAbsolutePath();
            Uri imageFileUri = Uri.fromFile(imageFile);// 獲取文件的Uri
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT,imageFileUri);// 告訴相機拍攝完成輸出圖片到指定的Uri
            startActivityForResult(intent, SELECT_IMAGE_RESULT_CODE);
        } else {
            Toast.makeText(this, "內存卡不存在。", Toast.LENGTH_LONG).show();
        }
    }

通過GridView實現動態加入圖片的效果
事實上你們更關心GridView動態添加item,item刪除等效果:

申明組件和變量:

/**
     * 須要上傳的圖片路徑  控制默認圖片在最後面須要用LinkedList
     */
    private LinkedList<String> dataList = new LinkedList<String>();
    /**
     * 圖片上傳GridView
     */
    private GridView uploadGridView;
    /**
     * 圖片上傳Adapter
     */
    private UploadImageAdapter adapter;

初始化GridView和Adapter:

    uploadGridView = (GridView) findViewById(R.id.grid_upload_pictures);
        dataList.addLast(null);// 初始化第一個加入button數據
        adapter = new UploadImageAdapter(this, dataList);
        uploadGridView.setAdapter(adapter);
        uploadGridView.setOnItemClickListener(mItemClick);
        uploadGridView.setOnItemLongClickListener(mItemLongClick);

GridView的item點擊監聽和長按監聽:

/**
     * 上傳圖片GridView Item單擊監聽
     */
    private OnItemClickListener mItemClick = new OnItemClickListener(){

        @Override
        public void onItemClick(AdapterView<?

> parent, View view, int position, long id) { if(parent.getItemAtPosition(position) == null){ // 加入圖片 //showPictureDailog();//Dialog形式 showPicturePopupWindow();//PopupWindow形式 } } }; /** * 上傳圖片GridView Item長按監聽 */ private OnItemLongClickListener mItemLongClick = new OnItemLongClickListener(){ @Override public boolean onItemLongClick(AdapterView<?

> parent, View view, int position, long id) { if(parent.getItemAtPosition(position) != null){ // 長按刪除 dataList.remove(parent.getItemAtPosition(position)); adapter.update(dataList); // 刷新圖片 } return true; } };

對於onActivityResult的回調例如以下:

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == SELECT_IMAGE_RESULT_CODE && resultCode == RESULT_OK){
        String imagePath = "";
            Uri uri = null;
            if (data != null && data.getData() != null) {// 有數據返回直接使用返回的圖片地址
                uri = data.getData();
                Cursor cursor = getContentResolver().query(uri, proj, null,
                        null, null);
                if (cursor == null) {//出現如小米等手機返回的絕對路徑錯誤時,自己拼出路徑
                    uri = ImageUtils.getUri(this, data);
                }
                imagePath = ImageUtils.getFilePathByFileUri(this, uri);
            } else {// 無數據使用指定的圖片路徑
                imagePath = mImagePath;
            }
            dataList.addFirst(imagePath);
            adapter.update(dataList); // 刷新圖片
        }
    }

這裏須要註意ImageUtils.getUri()方法了。相信非常多人遇到過小米等定制ROM系統的手機廠商了,把原生Android系統改的面目全非,所以調用系統自帶的功能問題就多了,我兩個手機。魅族的沒事。可是小米的就出問題,圖庫選擇圖片返回的絕對的路徑居然是錯誤的,沒錯,有返回路徑。可是用Cursor 就查詢卻找不到該圖片!所以,我們僅僅能自己拼寫圖片路徑了。來看看ImageUtils.getUri()。

/**
     * 解決小米等定制ROM手機返回的絕對路徑錯誤的問題
     * @param context
     * @param intent
     * @return uri 拼出來的URI
     */
     public static Uri getUri(Context context , Intent intent) {  
            Uri uri = intent.getData();  
            String type = intent.getType();  
            if (uri.getScheme().equals("file") && (type.contains("image/"))) {  
                String path = uri.getEncodedPath();  
                if (path != null) {  
                    path = Uri.decode(path);  
                    ContentResolver cr = context.getContentResolver();  
                    StringBuffer buff = new StringBuffer();  
                    buff.append("(").append(Images.ImageColumns.DATA).append("=")  
                            .append("‘" + path + "‘").append(")");  
                    Cursor cur = cr.query(Images.Media.EXTERNAL_CONTENT_URI,  
                            new String[] { Images.ImageColumns._ID },  
                            buff.toString(), null, null);  
                    int index = 0;  
                    for (cur.moveToFirst(); !cur.isAfterLast(); cur.moveToNext()) {  
                        index = cur.getColumnIndex(Images.ImageColumns._ID);  
                        // set _id value  
                        index = cur.getInt(index);  
                    }  
                    if (index == 0) {  
                        // do nothing  
                    } else {  
                        Uri uri_temp = Uri  
                                .parse("content://media/external/images/media/"  
                                        + index);  
                        if (uri_temp != null) {  
                            uri = uri_temp;  
                            Log.i("urishi", uri.toString());  
                        }  
                    }  
                }  
            }  
            return uri;  
        }  

Adapter使用的小技巧
我們能夠看到GirdView點擊監聽和長按監聽都用到了

if(parent.getItemAtPosition(position) != null){
            //相關邏輯
}

推斷語句,為什麽用parent.getItemAtPosition(position) 而不用dataList .get(position)呢?個人覺得使用適配器最好將數據源隔離出來。即除了在Adapter傳入數據或者Adapter更新數據,其它情況不再使用數據源。避免數據不同步造成一些問題。我們再來看一下Adapter的代碼:

/**
 * 多圖上傳,動態加入圖片適配器
 */
public class UploadImageAdapter extends BaseAdapter {

    private LinkedList<String> imagePathList;
    private Context context;
    private boolean isAddData = true;
    /**
     * 控制最多上傳的圖片數量
     */
    private int imageNumber = 6;

    public UploadImageAdapter(Context context, LinkedList<String> imagePath) {
        this.context = context;
        this.imagePathList = imagePath;
    }

    public void update(LinkedList<String> imagePathList){
        this.imagePathList = imagePathList;
        //這裏控制選擇的圖片放到前面,默認的圖片放到最後面,
        if(isAddData){
            //集合中的總數量等於上傳圖片的數量加上默認的圖片不能大於imageNumber + 1
            if(imagePathList.size() == imageNumber + 1){
                //移除默認的圖片
                imagePathList.removeLast();
                isAddData = false;
            }
        }else{
            //加入默認的圖片
            imagePathList.addLast(null);
            isAddData = true;
        }
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return imagePathList == null ? 0 : imagePathList.size();
    }

    @Override
    public Object getItem(int position) {
        return imagePathList == null ? null : imagePathList.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView iv_image;
        if (convertView == null) {//創建ImageView
            iv_image = new ImageView(context);
            iv_image.setLayoutParams(new AbsListView.LayoutParams(ImageUtils.getWidth(context) / 3 - 5, ImageUtils.getWidth(context) / 3 - 5) );  
            iv_image.setScaleType(ImageButton.ScaleType.CENTER_CROP);
            convertView = iv_image;
        }else{
            iv_image = (ImageView) convertView;
        }
        if(getItem(position) == null ){//圖片地址為空時設置默認圖片
            iv_image.setImageResource(R.drawable.upload);
        }else{
            //獲取圖片縮略圖,避免OOM
            Bitmap bitmap = ImageUtils.getImageThumbnail((String)getItem(position), ImageUtils.getWidth(context) / 3 - 5, ImageUtils.getWidth(context) / 3 - 5);
            iv_image.setImageBitmap(bitmap);
        }
        return convertView;
    }

在這裏我對getCount()、getItem()方法都做了非空的推斷,個人覺得能避免空指針異常就要避免,當然這樣做也是為了在getView中直接使用getItem(position)方法,而不是取用dataList.get(position)獲取當前item的相應的數據,原因在GridView點擊和長按事件中有提到過。邏輯比較簡單。不做過多的介紹。

Fragment與Activity之間通過接口傳遞數據
我覺得最重要的就是Fragment與Activity之間怎麽傳遞數據,在這裏我採取了接口回調來實現數據傳遞。
首先在BaseActivity中定義一個接口:

/**
     * 選擇圖片的返回碼
     */
    public final static int SELECT_IMAGE_RESULT_CODE = 200; 
    /**
     * 當前選擇的圖片的路徑
     */
    public String mImagePath;
    /**
     * 自己定義的PopupWindow
     */
    private SelectPicPopupWindow menuWindow;
    /**
     * Fragment回調接口
     */
    public OnFragmentResult mOnFragmentResult;

    public void setOnFragmentResult(OnFragmentResult onFragmentResult){
        mOnFragmentResult = onFragmentResult;
    }

    /**
     * 回調數據給Fragment的接口
     */
    public interface OnFragmentResult{
        void onResult(String mImagePath);
    }

然後我們來看看是怎麽使用的吧:
由於ChooseFragmentActivity繼承自BaseActivity,所以直接mOnFragmentResult

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        String imagePath = "";
        if(requestCode == SELECT_IMAGE_RESULT_CODE && resultCode== RESULT_OK){
            if(data != null && data.getData() != null){
                imagePath = ImageUtils.getFilePathByFileUri(this, data.getData());
            }else{
                imagePath = mImagePath;
            }
            mOnFragmentResult.onResult(imagePath);
        }
    }

Fragment中:

//設置監聽      ((BaseActivity)getActivity()).setOnFragmentResult(mOnFragmentResult);

private OnFragmentResult mOnFragmentResult = new OnFragmentResult() {

        @Override
        public void onResult(String mImagePath) {
            dataList.addFirst(mImagePath);
            adapter.update(dataList); // 刷新圖片
        }
    };      

而在Fragment中對GridView點擊、長按事件操作與Activity中大同小異,主要是Context的獲取。

/**
     * 上傳圖片GridView Item單擊監聽
     */
    private OnItemClickListener mItemClick = new OnItemClickListener(){

        @Override
        public void onItemClick(AdapterView<?

> parent, View view, int position, long id) { if(parent.getItemAtPosition(position) == null){ // 加入圖片 //((BaseActivity)getActivity()).showPictureDailog();//Dialog形式 ((BaseActivity)getActivity()).showPicturePopupWindow();//PopupWindow形式 } } };

最關鍵的地方就是(BaseActivity)getActivity()這步操作,這樣能在Fragment中拿到BaseActivity中的方法和屬性。這樣的操作在非常多情景使用會帶來非常大的便利。

好了,本片文章就進入尾聲了……

PS:對CSDN資源模塊感到無力,使用github來保存代碼了。

Think great thoughts and you will be great.

github

Android上傳圖片之調用系統拍照和從相冊選擇圖片