1. 程式人生 > >Android 系統自帶圖片裁剪功能(適配7.0、8.0、對了還有小米手機)

Android 系統自帶圖片裁剪功能(適配7.0、8.0、對了還有小米手機)

前段時間寫了如何獲取相簿和拍照之後的照片並且進行顯示和上傳,這一次是如何進行圓形影象製作,經常看我寫的筆記的人會知道,我很懶。那麼我就懶的自定義了,目前需求就用原生的就好了,大神的輪子,我會在後面進行推薦。這篇筆記是依賴於:Android呼叫相簿、相機(相容6.0、7.0、8.0)
文章撰寫的,所以不會基本操作的,請先去看看那個。

先上效果哈:
系統自帶的裁剪效果

##一、佈局設定
由於是圓形頭像,所以,我們引用一個舉世聞名的專案哈:CircleImageView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_get_pic_form_photo_album"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_margin="10dp"
        android:text="呼叫相簿" />

    <Button
        android:id="@+id/btn_get_Permission"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_margin="10dp"
        android:text="動態許可權申請" />

    <Button
        android:id="@+id/btn_get_pic_from_camera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_margin="10dp"
        android:text="呼叫相機" />

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iv_test"
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:layout_centerInParent="true"
        android:src="@drawable/ic_launcher_background"
        app:civ_border_color="#999999"
        app:civ_border_width="2dp" />
</RelativeLayout>

佈局沒有太大變化,就是把之前的ImageView修改為CircleImageView控制元件

##二、.java 檔案
這裡增加了兩個方法
######方法一

  private void photoClip(Uri uri) {
        // 呼叫系統中自帶的圖片剪裁
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(uri, "image/*");
        // 下面這個crop=true是設定在開啟的Intent中設定顯示的VIEW可裁剪
        intent.putExtra("crop", "true");
        // aspectX aspectY 是寬高的比例
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        // outputX outputY 是裁剪圖片寬高
        intent.putExtra("outputX", 150);
        intent.putExtra("outputY", 150);
        intent.putExtra("return-data", true);
        startActivityForResult(intent, 3);
    }

該方法,傳入我們拿到的照片的 uri 進行啟用 Android 系統的裁剪介面。我是在 onActivityResult 內進行呼叫該方法。

######方法二

  public String saveImage(String name, Bitmap bmp) {
        File appDir = new File(Environment.getExternalStorageDirectory().getPath());
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        String fileName = name + ".jpg";
        File file = new File(appDir, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
            return file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

該方法 傳入獲取照片的 bitmap 和裁剪之後的照片名稱,生成檔案的儲存路徑,韓小呆將其儲存在了本地的根目錄了。

完整程式碼

package com.example.hxd.pictest;

import android.Manifest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.bumptech.glide.Glide;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;

import de.hdodenhof.circleimageview.CircleImageView;
import pub.devrel.easypermissions.EasyPermissions;

public class MainActivity extends AppCompatActivity implements View.OnClickListener, EasyPermissions.PermissionCallbacks {

    private CircleImageView ivTest;

    private File cameraSavePath;//拍照照片路徑
    private Uri uri;
    private String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};

    private String photoName = System.currentTimeMillis() + ".jpg";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnGetPicFromCamera = findViewById(R.id.btn_get_pic_from_camera);
        Button btnGetPicFromPhotoAlbum = findViewById(R.id.btn_get_pic_form_photo_album);
        Button btnGetPermission = findViewById(R.id.btn_get_Permission);
        ivTest = findViewById(R.id.iv_test);

        btnGetPicFromCamera.setOnClickListener(this);
        btnGetPicFromPhotoAlbum.setOnClickListener(this);
        btnGetPermission.setOnClickListener(this);

        cameraSavePath = new File(Environment.getExternalStorageDirectory().getPath() + "/" + photoName);

    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id) {
            case R.id.btn_get_pic_from_camera:
                goCamera();
                break;
            case R.id.btn_get_pic_form_photo_album:
                goPhotoAlbum();
                break;
            case R.id.btn_get_Permission:
                getPermission();
                break;
        }
    }

    //獲取許可權
    private void getPermission() {
        if (EasyPermissions.hasPermissions(this, permissions)) {
            //已經開啟許可權
            Toast.makeText(this, "已經申請相關許可權", Toast.LENGTH_SHORT).show();
        } else {
            //沒有開啟相關許可權、申請許可權
            EasyPermissions.requestPermissions(this, "需要獲取您的相簿、照相使用許可權", 1, permissions);
        }

    }


    private void photoClip(Uri uri) {
        // 呼叫系統中自帶的圖片剪裁
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");
        // 下面這個crop=true是設定在開啟的Intent中設定顯示的VIEW可裁剪
        intent.putExtra("crop", "true");
        // aspectX aspectY 是寬高的比例
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        // outputX outputY 是裁剪圖片寬高
        intent.putExtra("outputX", 150);
        intent.putExtra("outputY", 150);
        intent.putExtra("return-data", true);
        startActivityForResult(intent, 3);


    }


    //啟用相簿操作
    private void goPhotoAlbum() {
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_PICK);
        intent.setType("image/*");
        startActivityForResult(intent, 2);
    }

    //啟用相機操作
    private void goCamera() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            uri = FileProvider.getUriForFile(MainActivity.this, "com.example.hxd.pictest.fileprovider", cameraSavePath);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            uri = Uri.fromFile(cameraSavePath);
        }
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        MainActivity.this.startActivityForResult(intent, 1);
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //框架要求必須這麼寫
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }


    //成功開啟許可權
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {

        Toast.makeText(this, "相關許可權獲取成功", Toast.LENGTH_SHORT).show();
    }

    //使用者未同意權限
    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        Toast.makeText(this, "請同意相關許可權,否則功能無法使用", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        String photoPath;
        if (requestCode == 1 && resultCode == RESULT_OK) {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                photoPath = String.valueOf(cameraSavePath);
                photoClip(Uri.fromFile(cameraSavePath));
            } else {
                photoPath = uri.getEncodedPath();
                photoClip(uri);
            }
            Log.d("拍照返回圖片路徑:", photoPath);
            Glide.with(MainActivity.this).load(photoPath).into(ivTest);
        } else if (requestCode == 2 && resultCode == RESULT_OK) {
            photoPath = getPhotoFromPhotoAlbum.getRealPathFromUri(this, data.getData());
            Log.d("相簿返回圖片路徑:", photoPath);
            photoClip(data.getData());
            Glide.with(MainActivity.this).load(photoPath).into(ivTest);
        } else if (requestCode == 3 && resultCode == RESULT_OK) {
            Bundle bundle = data.getExtras();

            if (bundle != null) {
                //在這裡獲得了剪裁後的Bitmap物件,可以用於上傳
                Bitmap image = bundle.getParcelable("data");
                //設定到ImageView上
                ivTest.setImageBitmap(image);
                //也可以進行一些儲存、壓縮等操作後上傳
                String path = saveImage("頭像", image);
                Log.d("裁剪路徑:", path);
            }


        }

        super.onActivityResult(requestCode, resultCode, data);
    }

    public String saveImage(String name, Bitmap bmp) {
        File appDir = new File(Environment.getExternalStorageDirectory().getPath());
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        String fileName = name + ".jpg";
        File file = new File(appDir, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
            return file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

當代碼寫成了這樣,我以為就可以完事了,但是後來拿出來我的小米4和小米8se 測試的時候,我就懵逼了,出現瞭如下結果:
bug
描述一下:
1、相簿選擇照片之後,只要進行裁剪就會出現“儲存時發生錯誤,儲存失敗”;
2、就是拍照之後進行裁剪無效果。

#三、解決bug
###1、將 photoClip() 方法內新增如下程式碼,其他程式碼不進行變動

uritempFile = Uri.parse("file://" + "/" + Environment.getExternalStorageDirectory().getPath() + "/" + System.currentTimeMillis() + ".jpg");
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uritempFile);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        intent.putExtra("noFaceDetection", true); 

這裡的 uritempFile 是個 Uri 。

###2、將裁剪的返回成功的返回方法內新增如下程式碼:

  Picasso.with(EditMyInfoActivity.this).load(uritempFile).into(ivHead);
            File file = null;
            try {
                file = new File(new URI(uritempFile.toString()));
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
            //照片路徑
            String path = Objects.requireNonNull(file).getPath();

然後,就可以完美的運行了。
##三、推薦一篇自定義裁剪的博文
Android 高仿微信頭像擷取 打造不一樣的自定義控制元件----作者: 家喻戶曉的 張鴻洋

##四、推薦幾款裁剪框架
1、uCrop—薄荷,嗶哩嗶哩 在使用

uCrop

2、android-crop—網易新聞,芒果TV 在使用
android-crop

3、PhotoCrop—快手 使用
PhotoCrop

4、CropBitmap—>出自一位牛x的群友
clipbitmap2.gif