zoukankan      html  css  js  c++  java
  • Android上传图片之调用系统拍照和从相冊选择图片

    Android上传图片之调用系统拍照和从相冊选择图片

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家公布

    前言:
    万丈高楼平底起,万事起于微末。不知不觉距离上篇博文已近四个月,2015年12月17日下午发了第一篇博文。如今是2016年4月6日。时间间隔长的过分啊,我自己都看不下去了。

    原因呢?当然是自己的原因。事实上是有非常多时间来些博客的,可是这些时间都花在DOTA上了(还是太年轻啊)。请原谅我的过错…….
    一、概述:
    如今差点儿应用都会用到上传图片的功能,而要上传图片,首先得选择图片。本文不针对怎样上传图片到server(每一个项目与server交互的方式不同。因此不写上传图片到server相关代码)。仅仅是对选择图片做简单的介绍,没有涉及到对图片的圆角处理与剪裁。本文主要涉及下面几个简单的知识点:

    • 简单的调用系统拍照和系统相冊选择图片
    • 通过GridView实现动态加入图片的效果
    • Adapter使用的小技巧
    • Fragment中调用系统拍照该怎么获取数据(接口回调)

    二、实现:
    我们先来看项目文件夹:
    project文件夹
    一个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

  • 相关阅读:
    BZOJ1251: 序列终结者
    BZOJ1014 [JSOI2008]火星人prefix
    NOI模拟赛Day6
    NOI模拟赛Day5
    BZOJ2329: [HNOI2011]括号修复
    NOI模拟赛Day4
    状压dp题目总结
    BZOJ2097[Usaco2010 Dec] 奶牛健美操
    BZOJ4027: [HEOI2015]兔子与樱花 贪心
    BZOJ1443: [JSOI2009]游戏Game
  • 原文地址:https://www.cnblogs.com/llguanli/p/8455137.html
Copyright © 2011-2022 走看看