Android上傳圖片之調用系統拍照和從相簿選擇圖片
Android上傳圖片之調用系統拍照和從相簿選擇圖片
前言:
萬丈高樓平底起,萬事起於微末。不知不覺距離上篇博文已近四個月,2015年12月17日下午發了第一篇博文,現在是2016年4月6日。時間間隔長的過分啊,我自己都看不下去了。原因呢?當然是自己的原因,其實是有很多時間來些部落格的,但是這些時間都花在DOTA上了(還是太年輕啊)。請原諒我的過錯…….
一、概述:
現在幾乎應用都會用到上傳圖片的功能,而要上傳圖片,首先得選擇圖片,本文不針對如何上傳圖片到伺服器(每個項目與伺服器互動的方式不同,因此不寫上傳圖片到伺服器相關代碼),只是對選擇圖片做簡單的介紹,沒有涉及到對圖片的圓角處理與剪裁。本文主要涉及以下幾個簡單的知識點:
簡單的調用系統拍照和系統相簿選擇圖片
通過GridView實現動態添加圖片的效果
Adapter使用的小技巧
Fragment中調用系統拍照該怎麼擷取資料(介面回調)
二、實現:
我們先來看項目目錄:
一個Adapter、兩個Activity,一個Fragment、一個工具類,一目瞭然。有人在這裡有疑問了,為什麼是兩個Activity?不是三個嗎?沒錯,理論上ChooseActivity、ChooseFragmentActivity、BaseActivity加起來是三個,不過在這裡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 dataList = new LinkedList(); /** * 圖片上傳GridView */ private GridView uploadGridView; /** * 圖片上傳Adapter */ private UploadImageAdapter adapter;
初始化GridView和Adapter:
uploadGridView = (GridView) findViewById(R.id.grid_upload_pictures); dataList.addLast(null);// 初始化第一個添加按鈕資料 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 = ""; if(data != null && data.getData() != null){//有資料返回直接使用返回的圖片地址 imagePath = ImageUtils.getFilePathByFileUri(this, data.getData()); }else{//無資料使用指定的圖片路徑 imagePath = mImagePath; } dataList.addFirst(imagePath);//每次資料放到首位 adapter.update(dataList); // 重新整理圖片 } }
Adapter使用的小技巧
我們可以看到GirdView點擊監聽和長按監聽都用到了
if(parent.getItemAtPosition(position) != null){ //相關邏輯}
判斷語句,為什麼用parent.getItemAtPosition(position) 而不用dataList .get(position)呢?個人認為使用適配器最好將資料來源隔離出來,即除了在Adapter傳入資料或者Adapter更新資料,其他情況不再使用資料來源,避免資料不同步造成一些問題。我們再來看一下Adapter的代碼:
/** * 多圖上傳,動態添加圖片適配器 */public class UploadImageAdapter extends BaseAdapter { private LinkedList imagePathList; private Context context; private boolean isAddData = true; /** * 控制最多上傳的圖片數量 */ private int imageNumber = 6; public UploadImageAdapter(Context context, LinkedList imagePath) { this.context = context; this.imagePathList = imagePath; } public void update(LinkedList 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()方法都做了非空的判斷,個人認為能避免null 指標異常就要避免,當然這樣做也是為了在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中的方法和屬性。這種操作在很多情景使用會帶來很大的便利。
好了,本片文章就進入尾聲了……
Think great thoughts and you will be great.
剛試了,上傳不了資源,等能上傳了再奉上源碼~