編輯:關於Android編程
前言:
萬丈高樓平底起,萬事起於微末。不知不覺距離上篇博文已近四個月,2015年12月17日下午發了第一篇博文,現在是2016年4月6日。時間間隔長的過分啊,我自己都看不下去了。原因呢?當然是自己的原因,其實是有很多時間來些博客的,但是這些時間都花在DOTA上了(還是太年輕啊)。請原諒我的過錯…….
一、概述:
現在幾乎應用都會用到上傳圖片的功能,而要上傳圖片,首先得選擇圖片,本文不針對如何上傳圖片到服務器(每個項目與服務器交互的方式不同,因此不寫上傳圖片到服務器相關代碼),只是對選擇圖片做簡單的介紹,沒有涉及到對圖片的圓角處理與剪裁。本文主要涉及以下幾個簡單的知識點:
二、實現:
我們先來看項目目錄:
一個Adapter、兩個Activity,一個Fragment、一個工具類,一目了然。有人在這裡有疑問了,為什麼是兩個Activity?不是三個嗎?沒錯,理論上ChooseActivity、ChooseFragmentActivity、BaseActivity加起來是三個,不過在這裡BaseActivity是模擬實際項目抽離Activity中公共的代碼,不做為視圖,所以我不把BaseActivity算進去。
ChoZ喎?/kf/ware/vc/" target="_blank" class="keylink">vc2VBY3Rpdml0ecrHxKPE4kFjdGl2aXR51tC199PDz7XNs8XE1dW6zc+1zbPP4LLh0aHU8c28xqyjrENob29zZUZyYWdtZW50QWN0aXZpdHnW0LfFyOtDaG9vc2VGcmFnbWVudMSjxOJGcmFnbWVudNbQtffTw8+1zbPFxNXVus3Ptc2zz+Cy4dGh1PHNvMasKNTa1eLA787StqjLwMHL0ru49kZyYWdtZW50xKPE4s/uxL/KtbzKx+m/9qOsyrW8ysfpv/bSu7j2QWN0aXZpdHnW0Lvh09C24Lj2RnJhZ21lbnQpo6xJbWFnZVV0aWxz1/bSu9CpvPK1pbXEzbzGrLSmwO2ho1NlbGVjdFBpY1BvcHVwV2luZG930ru49rzytaW1xFBvcHVwV2luZG93LFVwbG9hZEltYWdlQWRhcHRlcravzKzRodTxzbzGrMnPtKu1xMrKxeTG96GjPGJyIC8+DQrPyMC0tePQp7n7zbywyaO6PGJyIC8+DQo8aW1nIGFsdD0="" src="/uploadfile/Collfiles/20160407/2016040709265765.gif" title="\" />
圖中展示的效果:點擊默認圖片彈出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()方法都做了非空的判斷,個人認為能避免空指針異常就要避免,當然這樣做也是為了在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.
剛試了,上傳不了資源,等能上傳了再奉上源碼~
谷歌在推出Android5.0的同時推出了全新的設計Material Design,谷歌為了給我們提供更加規范的MD設計風格的控件,在2015年IO大會上推出了Desig
在Android開發過程中,如果Android系統自帶的屬性不能滿足我們日常開發的需求,那麼就需要我們給系統控件添加額外的屬性了。假如有個需求是實現帶下劃線的文本顯示(下
在《Android Handler之消息循環的深入解析》中談到了Handler是用於操作線程內部的消息隊列,所以Handler可以用來線程間通信ITC,這種方式更加安全和
本節引言: 好的,上一節中,我們又寫了一個關於Xfermode圖片混排的例子——擦美女衣服的Demo,加上前面的 利用Xfermode