前言:
萬丈高樓平底起,萬事起於微末。不知不覺距離上篇博文已近四個月,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張圖片。
簡單的呼叫系統拍照和系統相簿選擇圖片
我們先來看是怎麼呼叫系統拍照和從相簿選擇圖片的:
申明元件與變數:
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * 選擇圖片的返回碼 */ public final static int SELECT_IMAGE_RESULT_CODE = 200; /** * 當前選擇的圖片的路徑 */ public String mImagePath; /** * 自定義的PopupWindow */ private SelectPicPopupWindow menuWindow; |
彈出PopupWindow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * 拍照或從相簿選擇圖片(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,至於為什麼不行,我也不知道原理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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刪除等效果:
申明元件和變數:
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * 需要上傳的圖片路徑 控制預設圖片在最後面需要用LinkedList */ private LinkedList dataList = new LinkedList(); /** * 圖片上傳GridView */ private GridView uploadGridView; /** * 圖片上傳Adapter */ private UploadImageAdapter adapter; |
初始化GridView和Adapter:
1 2 3 4 5 6 |
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點選監聽和長按監聽:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/** * 上傳圖片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的回撥如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@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點選監聽和長按監聽都用到了
1 2 3 |
if(parent.getItemAtPosition(position) != null){ //相關邏輯 } |
判斷語句,為什麼用parent.getItemAtPosition(position) 而不用dataList .get(position)呢?個人認為使用介面卡最好將資料來源隔離出來,即除了在Adapter傳入資料或者Adapter更新資料,其他情況不再使用資料來源,避免資料不同步造成一些問題。我們再來看一下Adapter的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
/** * 多圖上傳,動態新增圖片介面卡 */ 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()方法都做了非空的判斷,個人認為能避免空指標異常就要避免,當然這樣做也是為了在getView中直接使用getItem(position)方法,而不是取用dataList.get(position)獲取當前item的對應的資料,原因在GridView點選和長按事件中有提到過。邏輯比較簡單,不做過多的介紹。
Fragment與Activity之間通過介面傳遞資料
我覺得最重要的就是Fragment與Activity之間怎麼傳遞資料,在這裡我採取了介面回撥來實現資料傳遞。
首先在BaseActivity中定義一個介面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/** * 選擇圖片的返回碼 */ 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
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@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中:
1 2 3 4 5 6 7 8 9 10 |
//設定監聽 ((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的獲取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * 上傳圖片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.