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

当前项目用到了拍照/从相册选取照片并裁剪展示到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卡 那么相片存储的位置该怎么获取到呢?
以上.希望大神交流指点~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容