安卓圖片裁剪(拍照or從相冊選取)學(xué)習(xí)筆記及問題記錄

當(dāng)前項(xiàng)目用到了拍照/從相冊選取照片并裁剪展示到ImageView上的功能,在網(wǎng)上找到很多資料,卻發(fā)現(xiàn)大同小異,知道我看到Ryan Hoo大神的文章 茅塞頓開,在此記錄一下原理以及實(shí)現(xiàn)步驟.
一.點(diǎn)擊相應(yīng)Button彈出拍照/從相冊選取/取消布局(如圖)


拍照/照片圖庫.jpg

實(shí)現(xiàn)方式很多 可以用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);        // 設(shè)置SelectPicPopupWindow的View
        this.setContentView(mContentView);        // 設(shè)置SelectPicPopupWindow彈出窗體的寬
        this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);        // 設(shè)置SelectPicPopupWindow彈出窗體的高
        this.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);        // 刷新狀態(tài)
        this.update();        // 實(shí)例化一個(gè)ColorDrawable顏色為半透明
        ColorDrawable dw = new ColorDrawable(0000000000);        // 點(diǎn)back鍵和其他地方使其消失,設(shè)置了這個(gè)才能觸發(fā)OnDismisslistener 对碌,設(shè)置其他控件變化等操作
        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>

注釋也很清晰 在此不做過多贅述,接下來進(jìn)入重點(diǎn),首先是拍照截圖:
(一).在Button點(diǎn)擊事件中彈出剛自定義的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);
    }
}

(二).準(zhǔn)備好使用到的Uri:

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

(三).在回調(diào)中調(diào)用Camera程序進(jìn)行拍照:

//拍照 調(diào)用相機(jī)
@Override
public void onTakePicture() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    startActivityForResult(intent, CODE_TAKE_PICTURE);
}

(四). onActivityResult中拿到返回的數(shù)據(jù),并傳遞給截圖的程序:

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

使用系統(tǒng)自帶的裁剪程序的方法我封裝好了一個(gè)方法:
Tip:


Paste_Image.png

intent.putExtra("return-data", false);
false代表不返回?cái)?shù)據(jù),返回url
true代表返回?cái)?shù)據(jù),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);// 開啟一個(gè)帶有返回值的Activity
}

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

case TAKE_CUT:
    //拍照 拿到剪切數(shù)據(jù)
    Bitmap bmap2 = data.getParcelableExtra("data");
    iv_photo.setImageBitmap(bmap2);

如果你在裁剪意圖中返回的是url,那么只需這樣轉(zhuǎn)換就好:

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不一樣:

//從相冊選取 調(diào)用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);
        }
    }

至此功能已經(jīng)完成 拿到bitmap之后,我們要上傳到服務(wù)器,我這邊要求是Base64,那么也很簡單:

public String bitmaptoString(Bitmap bitmap) {
    String s = null;
    // 將Bitmap轉(zhuǎn)換成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.點(diǎn)擊裁剪界面的取消按鈕程序會直接退出,不知道怎么控制.
2(已解決,方法:將裁剪圖片方法中返回?cái)?shù)據(jù)改為false,使用url進(jìn)行操作,因?yàn)橹苯臃祷豣itmap,一張相片3M多,會造成OOM)
如果我將裁剪的尺寸設(shè)置為寬800 高400,那么裁剪界面的確定取消按鈕都會無反應(yīng)
如果我將裁剪的尺寸設(shè)置為寬560 高280那么程序會蹦,報(bào)這個(gè)錯(cuò):

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.拍照后并沒有保存到我們的手機(jī)相冊中,我看了下qq微信,拍照后相冊中也會有,我在查閱第一行代碼(第二版)之后,按照郭霖大神的思路去寫 發(fā)現(xiàn)無效.代碼如下:

        private Uri imageUri;
        //創(chuàng)建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);
        }
        //調(diào)用相機(jī)
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        startActivityForResult(intent, CODE_TAKE_PICTURE);

思路是這樣的:首先創(chuàng)建File對象,放到了SD卡的應(yīng)用關(guān)聯(lián)緩存目錄下(因?yàn)?.0系統(tǒng)開始讀寫SD卡也被列為危險(xiǎn)權(quán)限),接著將File對象轉(zhuǎn)換成uri對象.我覺得是因?yàn)槲沂謾C(jī)沒有裝SD卡 那么相片存儲的位置該怎么獲取到呢?
以上.希望大神交流指點(diǎn)~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朽们,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诉位,更是在濱河造成了極大的恐慌骑脱,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苍糠,死亡現(xiàn)場離奇詭異叁丧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)岳瞭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門拥娄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瞳筏,你說我怎么就攤上這事稚瘾。” “怎么了姚炕?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵摊欠,是天一觀的道長。 經(jīng)常有香客問我钻心,道長凄硼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任捷沸,我火速辦了婚禮摊沉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痒给。我一直安慰自己说墨,他們只是感情好骏全,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尼斧,像睡著了一般姜贡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棺棵,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天楼咳,我揣著相機(jī)與錄音,去河邊找鬼烛恤。 笑死母怜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缚柏。 我是一名探鬼主播苹熏,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼币喧!你這毒婦竟也來了轨域?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤杀餐,失蹤者是張志新(化名)和其女友劉穎干发,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體史翘,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铐然,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恶座。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沥阳,死狀恐怖跨琳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情桐罕,我是刑警寧澤脉让,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布郑什,位于F島的核電站刁赦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抑诸。R本人自食惡果不足惜薪伏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一滚澜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫁怀,春花似錦设捐、人聲如沸借浊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚂斤。三九已至,卻和暖如春槐沼,著一層夾襖步出監(jiān)牢的瞬間曙蒸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工岗钩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纽窟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓凹嘲,卻偏偏與公主長得像师倔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子周蹭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容