Android上传图片之调用系统拍照和从相冊选择图片
Android上传图片之调用系统拍照和从相冊选择图片
本篇文章已授权微信公众号 guolin_blog (郭霖)独家公布
前言:
万丈高楼平底起,万事起于微末。不知不觉距离上篇博文已近四个月,2015年12月17日下午发了第一篇博文。如今是2016年4月6日。时间间隔长的过分啊,我自己都看不下去了。
原因呢?当然是自己的原因。事实上是有非常多时间来些博客的,可是这些时间都花在DOTA上了(还是太年轻啊)。请原谅我的过错…….
一、概述:
如今差点儿应用都会用到上传图片的功能,而要上传图片,首先得选择图片。本文不针对怎样上传图片到server(每一个项目与server交互的方式不同。因此不写上传图片到server相关代码)。仅仅是对选择图片做简单的介绍,没有涉及到对图片的圆角处理与剪裁。本文主要涉及下面几个简单的知识点:
- 简单的调用系统拍照和系统相冊选择图片
- 通过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<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上传图片之调用系统拍照和从相冊选择图片的更多相关文章
- Android实现批量照片上传至server,拍照或者从相冊选择
近期因为项目需求,须要完毕批量照片上传,折腾了一段时间,最终完毕了,达到了例如以下效果 主界面主要有GridView组成和button组成,当按下一个格点时,会调用相机或者相冊,拍照或者选择相冊照片, ...
- 摄像头(3)调用系统拍照activity来拍照
import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager ...
- 摄像头(2)调用系统拍照activity来录像
import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager ...
- Java乔晓松-android中调用系统拍照功能并显示拍照的图片
android中调用系统拍照功能并显示拍照的图片 如果你是拍照完,利用onActivityResult获取data数据,把data数据转换成Bitmap数据,这样获取到的图片,是拍照的照片的缩略图 代 ...
- Android调用系统拍照裁剪和选图功能
最近项目中用到修改用户头像的功能,基本上都是模板代码,现在简单记录一下. 调用系统拍照 private fun openCamera() { //调用相机拍照 // 创建File对象,用于存储拍照后的 ...
- Android 实例解说加入本地图片和调用系统拍照图片
在项目的开发过程我们离不开图片.而有时候须要调用本地的图片,有时候须要调用拍照图片.同一时候实现拍照的方法有两种,一种是调用系统拍照功能.还有一种是自己定义拍照功能. 而本博文眼下仅仅解说第一种方法, ...
- HTML5: 实现调用系统拍照或者选择照片并预览
ylbtech-HTML5: 实现调用系统拍照或者选择照片并预览 1.返回顶部 1. <!DOCTYPE html> <html> <head> <meta ...
- 【Android学习】调用系统相机
Android调用系统相机分三步走: 首先是要设置调用相机的权限. 其次是给按钮加打开相机的事件. 最后是拍照后进行图片的保存. 第一步,添加权限: <!-- 调用系统相机 --> < ...
- h5 实现调用系统拍照或者选择照片并预览
这次又来分享个好东西! 调用手机相机拍照或者是调用手机相册选择照片,这个功能在 手机端页面 或者 webApp 应该是常用到的,就拿个人或会员资料录入那块来说就已经是经常会碰到的, 每当看到这块功能的 ...
随机推荐
- git clone 出现错误
看了好多资料终于搞定了git 中clone命令报错这个问题,废话不多说直接上步骤希望对大家有帮助. 1 删除.ssh文件夹(直接搜索该文件夹)下的known_hosts(手动删除即可,不需要git ...
- XOJ测试 2016.5.22
哈哈 我是最先使用XOJ的人之一 膜拜zrt ing 首先是XOJ神奇的界面 还没有建设完的OJ是这个样子的 一共有5道题 这次小测有3道题 是T2T3T4 首先是骑士精神 (BZOJ1085) 上来 ...
- net .异步委托知识
以前在编程中,异步用的比较少,导致C# 一些基础的 东西用法都不怎么熟悉,经常要用的时候在去查找资料比较被动,而已没真正里面理解起来,始终感觉不是自己的知识 (题外话) 首先委托关键字 Delega ...
- 可变长度参数列表(Stering...aaa)
- SQLServer 里的三种条件判断的用法:Where GroupBy Having
HAVING 子句对 GROUP BY 子句设置条件的方式与 WHERE 子句和 SELECT 语句交互的方式类似.WHERE 子句搜索条件在进行分组操作之前应用:而 HAVING 搜索条件在进行分组 ...
- [hihocoder][Offer收割]编程练习赛50
循环数组 计算a[i]的前缀和s[i],计算l[i]为1~i-1中最小的s值,r[i]为i~n中最大的s值. 则a[i]~a[n]满足性质的条件为r[i]-s[i-1]>0,a[1]~a[i-1 ...
- 获取Json中特定的值
假如我们得到了一个json的数据:json===> {"Head":{"TransCode":"X1009","Tr ...
- 【SQL】日期型函数
1. SYSTATE 用来返回系统当前时间 SQL> select sysdate from dual; SYSDATE ------------------- 2017-03-03 09:49 ...
- 复习java第五天(枚举、Annotation(注释) 概述)
一.枚举 传统的方式: •在某些情况下,一个类的对象是有限而且固定的.例如季节类,只能有 4 个对象 •手动实现枚举类: —private 修饰构造器. —属性使用 private final 修饰. ...
- Embedded之Stack之三
Stack Overflow While stacks are generally large, they don't occupy all of memory. It is possible to ...