1. 程式人生 > >Android拍照和從相簿選取圖片,相容7.0

Android拍照和從相簿選取圖片,相容7.0

首先配置一下我們的專案

(1)在build.gradle裡新增類庫:

compile 'com.squareup.picasso:picasso:2.5.2'

(2)

2.1、在清單檔案裡面新增許可權

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
/>

2.2、在清單檔案裡面新增provider,紅色部分為專案包名

<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.administrator.testb.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
    <meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/> </provider>
(3)在res目錄下,新建一個xml資料夾,在xml資料夾裡面,新建一個檔案:file_provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <paths>
        <external-path path="" name="myFile"/>
    </paths>
</resources>

(4)引用一個類,封裝了包括拍照,相簿選取圖片,裁剪為一體的類(具體見後面),

這個類是網上找的,原作者徐巨集 http://blog.csdn.net/xh870189248

(5)呼叫這個類,實現拍照和相簿方法

public class PhotoActivity extends Activity {
    private Button btn;
    private ImageView iv;
    private TakePictureManager takePictureManager;
    private ImageView ivShow;
    private Button  btn_image;
 

    //從相簿選取圖片點選事件
private void getImage(){
        takePictureManager = new TakePictureManager(this);
        takePictureManager.setTailor(1, 1, 350, 350);
        takePictureManager.startTakeWayByAlbum();
        takePictureManager.setTakePictureCallBackListener(new TakePictureManager.takePictureCallBackListener() {
            @Override
public void successful(boolean isTailor, File outFile, Uri filePath) {
               // tvShow.setText(filePath.getPath());
Picasso.with(PhotoActivity.this).load(outFile).error(R.mipmap.ic_launcher).into(ivShow);
            }

            @Override
public void failed(int errorCode, List<String> deniedPermissions) {

            }

        });
    }

    //使用手機拍照點選事件
private void getTake(){
        takePictureManager = new TakePictureManager(this);
        //開啟裁剪 比例 1:3 寬高 350 350  (預設不裁剪)
takePictureManager.setTailor(1, 1, 350, 350);
        //拍照方式
takePictureManager.startTakeWayByCarema();
        //回撥
takePictureManager.setTakePictureCallBackListener(new TakePictureManager.takePictureCallBackListener() {
            //成功拿到圖片,isTailor 是否裁剪? ,outFile 拿到的檔案 ,filePath拿到的URl
@Override
public void successful(boolean isTailor, File outFile, Uri filePath) {
              //  tvShow.setText(filePath.getPath());
Picasso.with(PhotoActivity.this).load(outFile).error(R.mipmap.ic_launcher).into(ivShow);
            }
            //失敗回撥
@Override
public void failed(int errorCode, List<String> deniedPermissions) {
            }
        });
    }

    //把本地的onActivityResult()方法回撥繫結到物件
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        takePictureManager.attachToActivityForResult(requestCode, resultCode, data);
    }

    //onRequestPermissionsResult()方法許可權回撥繫結到物件
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        takePictureManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
    
}
(6)執行效果截圖


封裝類:

package com.example.administrator.testb;



       
        import java.io.ByteArrayInputStream;
        import java.io.ByteArrayOutputStream;
        import java.io.File;
        import java.io.FileNotFoundException;
        import java.io.FileOutputStream;
        import java.io.IOException;
        import java.util.ArrayList;
        import java.util.List;

/*
 * 專案名:TakePictureManager
 * 包名:com.xuhong.takepictureandroidn_master
 * 檔名:TakePictureManager
 * 建立時間:2017/8/18 12:08
 * 建立者: 徐巨集  http://blog.csdn.net/xh870189248
 * 描述:只為適配Android N 拍照和本地相簿上傳封裝而生
 */
public class TakePictureManager {

    private Activity mActivity;
    private Fragment mFragment;

    //預設不開啟裁剪
private boolean isTailor = false;

    //裁剪寬高的比例,預設是是 1 :1
private int aspectX = 1;
    private int aspectY = 1;

    //裁剪圖片的寬高,預設是是 1 :1
private int outputX = 350;
    private int outputY = 350;

    //拿到未裁剪相片的回撥碼(拍照後)
private static final int CODE_ORIGINAL_PHOTO_CAMERA = 101;

    //拿到未裁剪相片的回撥碼(選擇本地相簿後)
private static final int CODE_ORIGINAL_PHOTO_ALBUM = 102;

    //拿到已裁剪相片的回撥碼
private static final int CODE_TAILOR_PHOTO = 103;

    //布林值,true:在mActivity進行操作 ;false :Fragment操作
private boolean isActicity;

    //上下文
private Context mContext;

    //activity
private Activity tempActivity;

    //FileProvider的主機名:一般是包名+".fileprovider"
private String FILE_PROVIDER_AUTHORITY;

    //臨時儲存相片地址
private String imgPath;

    //最終得到的Url
private Uri outputUri;

    //是否壓縮圖片 預設開啟壓縮圖片的
private boolean isCompressor = true;

    //圖片回撥介面
private takePictureCallBackListener takeCallBacklistener;

    //內部許可權介面,學習於郭神
private PermissionListener permissionListener;


    public TakePictureManager(Activity mActivity) {
        this.mActivity = mActivity;
        tempActivity = mActivity;
        isActicity = true;
        mContext = mActivity;
        FILE_PROVIDER_AUTHORITY = mActivity.getPackageName() + ".fileprovider";
    }

    public TakePictureManager(Fragment mFragment) {
        this.mFragment = mFragment;
        isActicity = false;
        mContext = mFragment.getActivity();
        tempActivity = mFragment.getActivity();
        FILE_PROVIDER_AUTHORITY = mFragment.getActivity().getPackageName() + ".fileprovider";
    }


    //開始拍照
public void startTakeWayByCarema() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //如果是6.0或6.0以上,則要申請執行時許可權,這裡需要申請拍照和寫入SD卡的許可權
requestRuntimePermission(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}
                    , new PermissionListener() {
                        @Override
public void onGranted() {
                            startOpencamera();
                        }

                        @Override
public void onDenied(List<String> deniedPermissions) {
                            if (takeCallBacklistener != null) {
                                takeCallBacklistener.failed(1, deniedPermissions);
                            }
                        }
                    });
            return;
        }
        startOpencamera();
    }

    //開始從相簿獲取
public void startTakeWayByAlbum() {

        imgPath = generateImgePath(mContext);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //如果是6.0或6.0以上,則要申請執行時許可權,這裡需要申請拍照和寫入SD卡的許可權
requestRuntimePermission(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, new PermissionListener() {
                @Override
public void onGranted() {
                    startAlbum();
                }

                @Override
public void onDenied(List<String> deniedPermissions) {
                    if (takeCallBacklistener != null) {
                        takeCallBacklistener.failed(1, deniedPermissions);
                    }
                }
            });

            return;
        }
        startAlbum();
    }

    private void startAlbum() {
        Intent intent = new Intent(Intent.ACTION_PICK, null);
        intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                "image/*");
        if (isActicity) {
            mActivity.startActivityForResult(intent, CODE_ORIGINAL_PHOTO_ALBUM);
        } else {
            mFragment.startActivityForResult(intent, CODE_ORIGINAL_PHOTO_ALBUM);
        }
    }


    /**
     * 對外介面,是否裁剪
     *
     * @param aspectX 要裁剪的寬比例
     * @param aspectY 要裁剪的高比例
     * @param outputX 要裁剪圖片的寬
     * @param outputY 要裁剪圖片的高
     */
public void setTailor(int aspectX, int aspectY, int outputX, int outputY) {
        isTailor = true;
        this.aspectX = aspectX;
        this.aspectY = aspectY;
        this.outputX = outputX;
        this.outputY = outputY;
    }

    /**
     * 裁剪方法
     *
     * @param srcFile
* @param output
*/
private void statZoom(File srcFile, File output) {

        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(getImageContentUri(mContext, srcFile), "image/*");
        // crop為true是設定在開啟的intent中設定顯示的view可以剪裁
intent.putExtra("crop", "true");
        intent.putExtra("scale", true);// 去黑邊
        // aspectX aspectY 是寬高的比例
intent.putExtra("aspectX", aspectX);
        intent.putExtra("aspectY", aspectY);

        // outputX,outputY 是剪裁圖片的寬高
intent.putExtra("outputX", outputX);
        intent.putExtra("outputY", outputY);
        intent.putExtra("return-data", false);// true:不返回uri,false:返回uri
intent.putExtra("scaleUpIfNeeded", true);//黑邊
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(output));
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());

        if (isActicity) {
            mActivity.startActivityForResult(intent, CODE_TAILOR_PHOTO);
        } else {
            mFragment.startActivityForResult(intent, CODE_TAILOR_PHOTO);
        }

    }


    /**
     * 獲取到的相片回撥方法,
     * 必須要在當前的Activity或Fragment中的onActivityResult下呼叫!
     *
     * @param requestCode
* @param resultCode
* @param data
*/
public void attachToActivityForResult(int requestCode, int resultCode, Intent data) {

        if (resultCode != -1) {
            return;
        }

        Bitmap bm = null;
        File temFile = null;
        File srcFile = null;
        File outPutFile = null;

        switch (requestCode) {
            //拍照後得到的圖片
case CODE_ORIGINAL_PHOTO_CAMERA:

                srcFile = new File(imgPath);
                outPutFile = new File(generateImgePath(mContext));
                outputUri = Uri.fromFile(outPutFile);
                Uri imageContentUri = getImageContentUri(mContext, srcFile);

                if (isTailor) {
                    statZoom(srcFile, outPutFile);
                } else {
                    if (takeCallBacklistener != null) {
                        takeCallBacklistener.successful(false, srcFile, imageContentUri);
                    }
                }

                break;

            //選擇相簿後得到的圖片
case CODE_ORIGINAL_PHOTO_ALBUM:
                if (data != null) {
                    Uri sourceUri = data.getData();
                    String pickPath = getPath(mContext, sourceUri);
                    srcFile = new File(pickPath);
                    temFile = srcFile;

                    if (isTailor) {
                        //裁剪之後的檔案和ur
outPutFile = new File(generateImgePath(mContext));
                        outputUri = Uri.fromFile(outPutFile);
                        //發起裁剪請求
statZoom(srcFile, outPutFile);
                    } else {

                        outputUri = Uri.fromFile(srcFile);
                        //如果選擇返回一個壓縮後的圖片
if (isCompressor) {
                            temFile = outputIamge(mContext, compressImage(decodeUriAsBitmap(outputUri), 100));
                            outputUri = Uri.fromFile(temFile);
                        }

                        if (takeCallBacklistener != null) {
                            takeCallBacklistener.successful(true, temFile, outputUri);
                        }
                    }

                } else {
                    if (takeCallBacklistener != null) {
                        takeCallBacklistener.failed(0, null);
                    }
                }
                break;

            //裁剪後的圖片:
case CODE_TAILOR_PHOTO:
                //拿到圖片之後,使用者可能會捨棄,所以先判斷
if (data != null) {
                    if (outputUri != null) {
                        //如果是拍照的,刪除臨時檔案
temFile = new File(imgPath);

                        if (temFile.exists()) {
                            temFile.delete();
                        }

                        //返回一個壓縮後的圖片
if (isCompressor) {
                            temFile = outputIamge(mContext, compressImage(decodeUriAsBitmap(outputUri), 100));
                            outputUri = Uri.fromFile(temFile);
                        }

                        if (takeCallBacklistener != null) {
                            takeCallBacklistener.successful(true, temFile, outputUri);
                        }

                    }
                } else {
                    if (takeCallBacklistener != null) {
                        takeCallBacklistener.failed(0, null);
                    }
                }

                break;
        }
    }


    //開啟相機
private void startOpencamera() {
        imgPath = generateImgePath(mContext);
        File imgFile = new File(imgPath);
        Uri imgUri = null;

        //判斷當前手機版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            imgUri = FileProvider.getUriForFile(mContext, FILE_PROVIDER_AUTHORITY, imgFile);
        } else {
            imgUri = Uri.fromFile(imgFile);
        }

        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);

        //判斷當前在哪啟動
if (isActicity) {
            mActivity.startActivityForResult(intent, CODE_ORIGINAL_PHOTO_CAMERA);
        } else {
            mFragment.startActivityForResult(intent, CODE_ORIGINAL_PHOTO_CAMERA);
        }
    }


    //許可權回撥
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0) {
                    List<String> deniedPermissions = new ArrayList<>();
                    for (int i = 0; i < grantResults.length; i++) {
                        int grantResult = grantResults[i];
                        String permission = permissions[i];
                        if (grantResult != PackageManager.PERMISSION_GRANTED) {
                            deniedPermissions.add(permission);
                        }
                    }
                    //被拒絕許可權
if (deniedPermissions.isEmpty()) {
                        permissionListener.onGranted();
                    } else {
                        permissionListener.onDenied(deniedPermissions);
                        if (takeCallBacklistener != null) {
                            takeCallBacklistener.failed(1, deniedPermissions);
                        }
                    }
                }
                break;
        }

    }

    /**
     * 申請執行時許可權
     */
private void requestRuntimePermission(String[] permissions, PermissionListener listener) {

        permissionListener = listener;
        List<String> permissionList = new ArrayList<>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(mContext, permission) != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(permission);
            }
        }

        //此處相容了無法在fragment回撥監聽事件
if (!permissionList.isEmpty()) {
            if (isActicity) {
                ActivityCompat.requestPermissions((Activity) mContext, permissionList.toArray(new String[permissionList.size()]), 1);
            } else {
                mFragment.requestPermissions(permissionList.toArray(new String[permissionList.size()]), 1);
            }


            if (takeCallBacklistener != null) {
                takeCallBacklistener.failed(1, permissionList);
            }
        } else {
            permissionListener.onGranted();
        }
    }


    public void setTakePictureCallBackListener(takePictureCallBackListener takeCallBacklistener) {
        this.takeCallBacklistener = takeCallBacklistener;
    }


    //得到圖片回撥介面(內部)
public interface takePictureCallBackListener {
        /**
         * 成功回撥
         *
         * @param isTailor 是否開啟了裁剪
         * @param outFile
* @param filePath
*/
void successful(boolean isTailor, File outFile, Uri filePath);

        /**
         * 失敗回撥
         *
         * @param errorCode         錯誤碼  0:圖片發生錯誤  1:被拒絕的許可權
         * @param deniedPermissions 被拒絕的許可權
         */
void failed(int errorCode, List<String> deniedPermissions);

    }


    private interface PermissionListener {

        void onGranted();

        void onDenied(List<String> deniedPermissions);
    }

    /*---------------------------------------------------------------------------------------------------------------------------------------*/
    /*--------------------------------------------以下是檔案操作相關------------------------------------------------------------------------------*/
    /*----------------------------------------------------------------------------------------------------------------------------------------*/
private static final String ICON_DIR = "icon";
    private static final String APP_STORAGE_ROOT = "AndroidNAdaption";

    //判斷SD卡是否掛載
private static boolean isSDCardAvailable() {
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 獲取app在外接SD卡的路徑
     *
     * @param name
* @return
*/
private static String getAppDir(Context context, String name) {
        StringBuilder sb = new StringBuilder();
        if (isSDCardAvailable()) {
            sb.append(getAppExternalStoragePath());
        } else {
            sb.append(getCachePath(context));
        }
        sb.append(name);
        sb.append(File.separator);
        String path = sb.toString();
        if (createDirs(path)) {
            return path;
        } else {
            return null;
        }
    }

    //獲取SD下當前APP的目錄
private static String getAppExternalStoragePath() {
        StringBuilder sb = new StringBuilder();
        sb.append(Environment.getExternalStorageDirectory().getAbsolutePath());
        sb.append(File.separator);
        sb.append(APP_STORAGE_ROOT);
        sb.append(File.separator);
        return sb.toString();
    }

    //獲取應用的cache目錄
private static String getCachePath(Context context) {
        File f = context.getCacheDir();
        if (null == f) {
            return null;
        } else {
            return f.getAbsolutePath() + "/";
        }
    }

    //建立資料夾
private static boolean createDirs(String dirPath) {
        File file = new File(dirPath);
        if (!file.exists() || !file.isDirectory()) {
            return file.mkdirs();
        }
        return true;
    }

    //產生圖片的路徑,帶資料夾和檔名,檔名為當前毫秒數
private static String generateImgePath(Context context) {
        return getAppDir(context, ICON_DIR) + String.valueOf(System.currentTimeMillis()) + ".jpg";
    }

    //裁剪根據檔案路徑獲取uri
private static Uri getImageContentUri(Context context, File imageFile) {
        String filePath = imageFile.getAbsolutePath();
        Cursor cursor = context.getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[]{MediaStore.Images.Media._ID},
                MediaStore.Images.Media.DATA + "=? ",
                new String[]{filePath}, null);

        if (cursor != null && cursor.moveToFirst()) {
            int id = cursor.getInt(cursor
                    .getColumnIndex(MediaStore.MediaColumns._ID));
            Uri baseUri = Uri.parse("content://media/external/images/media");
            return Uri.withAppendedPath(baseUri, "" + id);
        } else {
            if (imageFile.exists()) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.DATA, filePath);
                return context.getContentResolver().insert(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            } else {
                return null;
            }
        }
    }


    /**
     * 根據uri返回bitmap
     *
     * @param uri
* @return
*/
public Bitmap decodeUriAsBitmap(Uri uri) {
        Bitmap bitmap = null;
        try {
            // 先通過getContentResolver方法獲得一個ContentResolver例項,
            // 呼叫openInputStream(Uri)方法獲得uri關聯的資料流stream
            // 把上一步獲得的資料流解析成為bitmap
bitmap = BitmapFactory.decodeStream(mContext.getContentResolver().openInputStream(uri));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
        return bitmap;
    }

    /**
     * 返回一張壓縮後的圖片
     *
     * @param image 原圖片
     * @param size  裁剪之後的大小
     * @return
*/
private static Bitmap compressImage(Bitmap image, int size) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//質量壓縮方法,這裡100表示不壓縮,把壓縮後的資料存放到baos中
int options = 100;
        while (baos.toByteArray().length / 1024 > size) {    //迴圈判斷如果壓縮後圖片是否大於100kb,大於繼續壓縮
baos.reset();
            options -= 10;
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);//這裡壓縮options%,把壓縮後的資料存放到baos中
}
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把壓縮後的資料baos存放到ByteArrayInputStream中
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream資料生成圖片
return bitmap;
    }

    //在自定義目錄建立圖片
private static File outputIamge(Context context, Bitmap bitmap) {

        File outputIamge = new File(generateImgePath(context));

        //建立
try {
            outputIamge.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

        FileOutputStream fOut = null;

        try {
            fOut = <