美文网首页安卓实用知识
安卓图片裁剪(拍照or从相册选取)学习笔记及问题记录

安卓图片裁剪(拍照or从相册选取)学习笔记及问题记录

作者: TheShy_ | 来源:发表于2017-01-06 17:19 被阅读640次

    当前项目用到了拍照/从相册选取照片并裁剪展示到ImageView上的功能,在网上找到很多资料,却发现大同小异,知道我看到Ryan Hoo大神的文章 茅塞顿开,在此记录一下原理以及实现步骤.
    一.点击相应Button弹出拍照/从相册选取/取消布局(如图)


    拍照/照片图库.jpg

    实现方式很多 可以用Dialogfragment 我的做法是PopWindow,代码贴上:

    import android.content.Context;
    import android.graphics.drawable.ColorDrawable;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.PopupWindow;
    import android.widget.TextView;
    /** * PopWindow */
    public class SelectPicturePopupWindow extends PopupWindow implements View.OnClickListener, PopupWindow.OnDismissListener {    
            private View mContentView;    
            private Context mContext;    
            private PictureCallBack mPictureCallBack;    
            public SelectPicturePopupWindow(Context context, PictureCallBack pictureCallBack) {
            super(context);
            this.mContext = context;
            this.mPictureCallBack = pictureCallBack;
            init(context);    
    }
        private void init(Context context) {
            mContentView = LayoutInflater.from(context).inflate(R.layout.popup_select_picture,null);
            mContentView.setOnClickListener(this);        // 设置SelectPicPopupWindow的View
            this.setContentView(mContentView);        // 设置SelectPicPopupWindow弹出窗体的宽
            this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);        // 设置SelectPicPopupWindow弹出窗体的高
            this.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);        // 刷新状态
            this.update();        // 实例化一个ColorDrawable颜色为半透明
            ColorDrawable dw = new ColorDrawable(0000000000);        // 点back键和其他地方使其消失,设置了这个才能触发OnDismisslistener ,设置其他控件变化等操作
            this.setBackgroundDrawable(dw);
            TextView cancel = (TextView) mContentView.findViewById(R.id.popup_cancel);
            TextView gallery = (TextView) mContentView.findViewById(R.id.popup_select_from_gallery);
            TextView takePicture = (TextView) mContentView.findViewById(R.id.popup_take_picture); 
            cancel.setOnClickListener(this);
            gallery.setOnClickListener(this);
            takePicture.setOnClickListener(this);
            setOnDismissListener(this);
        }
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.popup_cancel:                //取消
                    break;
                case R.id.popup_select_from_gallery:                //图片图库
                    if (mPictureCallBack != null) {
                        mPictureCallBack.onSelectFromGallery();
                    }
                    break;
                case R.id.popup_take_picture:       //拍照
                    if (mPictureCallBack != null) {
                        mPictureCallBack.onTakePicture();
                    }
                    break;
            }
            dismiss();
        }
        @Override
        public void onDismiss() {
            dismiss();
        }
        public interface PictureCallBack{
            void onTakePicture();
            void onSelectFromGallery();
        }   
     /** 
         * 显示popupWindow
         * @param parent
         */
        public void show(View parent) {
            if (!this.isShowing()) { 
               // 以下拉方式显示popupwindow 
               showAtLocation(parent, Gravity.NO_GRAVITY,0,0);
            } else {
                this.dismiss();
            }
        }
    }
    

    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"              android:layout_width="match_parent"              android:layout_height="match_parent"              android:background="#88000000"
                  android:gravity="bottom"
                  android:orientation="vertical">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="10dp"
            android:background="@drawable/popup_white_bg"
            android:orientation="vertical">
            <TextView
                android:id="@+id/popup_take_picture"
                android:layout_width="match_parent"
                android:layout_height="45dp" 
                android:gravity="center"
                android:paddingLeft="20dp"
                android:text="拍照"
                android:textColor="#666666"/>
            <View
                android:layout_width="match_parent"
                android:layout_height="1px"            android:background="#dfdfdf"/>
            <TextView
                android:id="@+id/popup_select_from_gallery"
                android:layout_width="match_parent"
                android:layout_height="45dp"
                android:gravity="center"
                android:paddingLeft="20dp"
                android:text="照片图库"
                android:textColor="#666666"/>
        </LinearLayout>
        <TextView
            android:id="@+id/popup_cancel"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:background="@drawable/popup_white_bg"
            android:gravity="center"
            android:paddingLeft="20dp"
            android:text="取消"
            android:textColor="#666666"/>
    </LinearLayout>
    

    注释也很清晰 在此不做过多赘述,接下来进入重点,首先是拍照截图:
    (一).在Button点击事件中弹出刚自定义的Popwindow:

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.btn_hey) {
            //弹出PopWindow
            if (mSelectPicturePopup == null) {
                mSelectPicturePopup = new SelectPicturePopupWindow(this, this);
            }
            mSelectPicturePopup.show(view);
        }
    }
    

    (二).准备好使用到的Uri:

    private static final String IMAGE_FILE_LOCATION = "file:///sdcard/test.jpg";
    Uri imageUri = Uri.parse(IMAGE_FILE_LOCATION);
    

    (三).在回调中调用Camera程序进行拍照:

    //拍照 调用相机
    @Override
    public void onTakePicture() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        startActivityForResult(intent, CODE_TAKE_PICTURE);
    }
    

    (四). onActivityResult中拿到返回的数据,并传递给截图的程序:

    case CODE_TAKE_PICTURE:
        if(resultCode == RESULT_OK){
            cutPhoto(2,1,280,140,imageUri,TAKE_CUT);
        }
        break;
    

    使用系统自带的裁剪程序的方法我封装好了一个方法:
    Tip:


    Paste_Image.png

    intent.putExtra("return-data", false);
    false代表不返回数据,返回url
    true代表返回数据,bitmap

    private void cutPhoto(int aspectX,int aspectY,int outputX,int outputY,Uri uri,int requestCode) {
        // 裁剪图片意图
        Intent in = new Intent("com.android.camera.action.CROP");
        in.setDataAndType(uri, "image/*");
        in.putExtra("crop", "true");
        // 裁剪框的比例,2:1
        in.putExtra("aspectX", aspectX);
        in.putExtra("aspectY", aspectY);
        // 裁剪后输出图片的尺寸大小
        in.putExtra("outputX", outputX);
        in.putExtra("outputY", outputY);
        in.putExtra("scale", true);
        in.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        in.putExtra("return-data", true);
        in.putExtra("outputFormat", "PNG");// 图片格式
        in.putExtra("noFaceDetection", true);// 取消人脸识别
        startActivityForResult(in, requestCode);// 开启一个带有返回值的Activity
    }
    

    (五).最后一步,处理返回的Bitmap:

    case TAKE_CUT:
        //拍照 拿到剪切数据
        Bitmap bmap2 = data.getParcelableExtra("data");
        iv_photo.setImageBitmap(bmap2);
    

    如果你在裁剪意图中返回的是url,那么只需这样转换就好:

    if(imageUri != null){
       Bitmap bitmap = decodeUriAsBitmap(imageUri);
       iv_photo.setImageBitmap(bitmap);
     }
    private Bitmap decodeUriAsBitmap(Uri uri){
     Bitmap bitmap = null;
     try {
         bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
     } catch (FileNotFoundException e) {
         e.printStackTrace();
         return null; 
    }
     return bitmap;
     }
    

    贴上效果图:


    显示.png

    从相册图库选取只有意图和url不一样:

    //从相册选取 调用android的图库
    @Override
    public void onSelectFromGallery() {
        Intent i = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        startActivityForResult(i, CODE_FROM_GALLERY);}
    

    在onActivityResult中

    case CODE_FROM_GALLERY:
        if (data != null) {
            Uri uri = data.getData();
            if (uri != null) {
                cutPhoto(3,2,280,140,uri,PHOTO_REQUEST_CUT);
            }
        }
    

    至此功能已经完成 拿到bitmap之后,我们要上传到服务器,我这边要求是Base64,那么也很简单:

    public String bitmaptoString(Bitmap bitmap) {
        String s = null;
        // 将Bitmap转换成Base64字符串
        ByteArrayOutputStream bStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, bStream);
        byte[] bytes = bStream.toByteArray();
        s = Base64.encodeToString(bytes, Base64.DEFAULT);
        return s;
    }
    

    bug以及我不理解的地方:
    1.点击裁剪界面的取消按钮程序会直接退出,不知道怎么控制.
    2(已解决,方法:将裁剪图片方法中返回数据改为false,使用url进行操作,因为直接返回bitmap,一张相片3M多,会造成OOM)
    如果我将裁剪的尺寸设置为宽800 高400,那么裁剪界面的确定取消按钮都会无反应
    如果我将裁剪的尺寸设置为宽560 高280那么程序会蹦,报这个错:

    System: stat file error, path is /data/app/com.lxd.photocut-2/lib/arm64, exception is android.system.ErrnoException: stat failed: ENOENT (No such file or directory)
    

    3.拍照后并没有保存到我们的手机相册中,我看了下qq微信,拍照后相册中也会有,我在查阅第一行代码(第二版)之后,按照郭霖大神的思路去写 发现无效.代码如下:

            private Uri imageUri;
            //创建File对象 用于存储拍照后的图片
            File outputImage = new File(getExternalCacheDir(),"test111.jpg");
            //如果文件存在 先删除
            try {
                if(outputImage.exists()){
                    outputImage.delete();
                }
                outputImage.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if(Build.VERSION.SDK_INT >= 24){
                imageUri = FileProvider.getUriForFile(MainActivity.this,"com.lxd.photocut.fileprovider",outputImage);
            }else {
                imageUri = Uri.fromFile(outputImage);
            }
            //调用相机
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            startActivityForResult(intent, CODE_TAKE_PICTURE);
    

    思路是这样的:首先创建File对象,放到了SD卡的应用关联缓存目录下(因为6.0系统开始读写SD卡也被列为危险权限),接着将File对象转换成uri对象.我觉得是因为我手机没有装SD卡 那么相片存储的位置该怎么获取到呢?
    以上.希望大神交流指点~

    相关文章

      网友评论

      本文标题:安卓图片裁剪(拍照or从相册选取)学习笔记及问题记录

      本文链接:https://www.haomeiwen.com/subject/bwxxbttx.html