Android项目模块提取 | 拍照、从本地相册选择图片,UI、动画等提取

对应项目github代码

https://github.com/aaLiweipeng/XiaoYunEC/commit/5ed0d2d02e204b408aa38b3e50c5e3e2e00d2259

效果

第三方库

    //工具包
    implementation 'com.blankj:utilcode:1.7.1'
    //动态权限处理
    api 'com.github.hotchemi:permissionsdispatcher:3.0.1'
    annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.0.1'
    //图片剪裁
    implementation 'com.github.yalantis:ucrop:2.2.1-native'

权限

    <!--网络权限-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <!--拨号权限-->
    <uses-permission android:name="android.permission.CALL_PHONE" />

    <!--文件权限-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <!--相机权限-->
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.CAMERA" />

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

照片处理类【UI加载find,逻辑处理】

public class CameraHandler implements View.OnClickListener {

    private final AlertDialog DIALOG;
    private final PermissionCheckerDelegate DELEGATE;

    public CameraHandler(PermissionCheckerDelegate delegate) {
        this.DELEGATE = delegate;
        DIALOG = new AlertDialog.Builder(delegate.getContext()).create();
    }

    //弹出Dialog弹框 内容:三个按钮,拍照 选图 取消
    final void beginCameraDialog() {
        DIALOG.show();

        final Window window = DIALOG.getWindow();
        if (window != null) {
            window.setContentView(R.layout.dialog_camera_panel);//设置弹框布局
            window.setGravity(Gravity.BOTTOM);
            window.setWindowAnimations(R.style.anim_panel_up_from_bottom);
            window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

            //设置属性
            final WindowManager.LayoutParams params = window.getAttributes();
            params.width = WindowManager.LayoutParams.MATCH_PARENT;
            params.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            params.dimAmount = 0.5f;//设置幕布黑暗程度
            window.setAttributes(params);

            //设置弹框中 组件的监听
            window.findViewById(R.id.photodialog_btn_cancel).setOnClickListener(this);//取消按钮
            window.findViewById(R.id.photodialog_btn_take).setOnClickListener(this);//拍照按钮
            window.findViewById(R.id.photodialog_btn_native).setOnClickListener(this);//本地按钮
        }
    }

    private String getPhotoName() {
        //获取一个 模板格式化后的 文件名(模板:文件头_当前时间.后缀)
        return FileUtil.getFileNameByTime("IMG", "jpg");
    }
    //拍照取图
    public void takePhoto() {
        final String currentPhotoName = getPhotoName();
        //拍照意图
        final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        //File 临时文件句柄   临时文件:这里是系统相册目录下的当前文件名的文件临时句柄
        //CAMERA_PHOTO_DIR 系统相册目录
        final File tempFile = new File(FileUtil.CAMERA_PHOTO_DIR, currentPhotoName);

        //兼容7.0及以上的写法
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            final ContentValues contentValues = new ContentValues(1);
            contentValues.put(MediaStore.Images.Media.DATA, tempFile.getPath());
            //使用 ContentProvider 的方式
            final Uri uri = DELEGATE.getContext().getContentResolver().
                    insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

            //需要讲Uri路径转化为实际路径
            final File realFile =
                    FileUtils.getFileByPath(FileUtil.getRealFilePath(DELEGATE.getContext(), uri));

            final Uri realUri = Uri.fromFile(realFile);
            CameraImageBean.getInstance().setPath(realUri);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        } else {

            final Uri fileUri = Uri.fromFile(tempFile);
            CameraImageBean.getInstance().setPath(fileUri);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
        }

        //使用startActivityForResult()的形式 启动Activity
        DELEGATE.startActivityForResult(intent, RequestCodes.TAKE_PHOTO);
    }

    //本地取图
    private void pickPhoto() {
        final Intent intent = new Intent();
        intent.setType("image/*");//所有的Image类型
        intent.setAction(Intent.ACTION_GET_CONTENT);//获取内容
        intent.addCategory(Intent.CATEGORY_OPENABLE);

        //使用startActivityForResult()的形式 启动Activity
        // createChooser 创建选择器
        DELEGATE.startActivityForResult
                (Intent.createChooser(intent, "选择获取图片的方式"), RequestCodes.PICK_PHOTO);
    }


    @Override
    public void onClick(View v) {
        int id = v.getId();

        if (id == R.id.photodialog_btn_cancel) {
            DIALOG.cancel();
        } else if (id == R.id.photodialog_btn_take) {
            //拍照取图
            takePhoto();
            DIALOG.cancel();
        } else if (id == R.id.photodialog_btn_native) {
            //本地取图
            pickPhoto();
            DIALOG.cancel();
        }
    }
}

FileUtil

https://github.com/aaLiweipeng/XiaoYunEC/blob/master/xiaoyun_core/src/main/java/com/lwp/xiaoyun_core/util/file/FileUtil.java

https://github.com/Blankj/AndroidUtilCode/releases/tag/1.7.1

public class MyFileUtil {
    //格式化的模板
    private static final String TIME_FORMAT = "_yyyyMMdd_HHmmss";

    private static final String SDCARD_DIR =
            Environment.getExternalStorageDirectory().getPath();

    //系统相机目录!!!
    public static final String CAMERA_PHOTO_DIR =
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath() + "/Camera/";

    public static String getRealFilePath(final Context context, final Uri uri) {
        if (null == uri) return null;
        final String scheme = uri.getScheme();
        String data = null;
        if (scheme == null)
            data = uri.getPath();
        else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
            data = uri.getPath();
        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
            final Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
            if (null != cursor) {
                if (cursor.moveToFirst()) {
                    final int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
                    if (index > -1) {
                        data = cursor.getString(index);
                    }
                }
                cursor.close();
            }
        }
        return data;
    }

    public static File getFileByPath(String filePath) {
        return isSpace(filePath) ? null : new File(filePath);
    }

    private static boolean isSpace(String s) {
        if (s == null) return true;
        for (int i = 0, len = s.length(); i < len; ++i) {
            if (!Character.isWhitespace(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    //按照 模板 返回 由 文件头 和 当前系统时间 组合而成的 命名字符串
    private static String getTimeFormatName(String timeFormatHeader) {
        final Date date = new Date(System.currentTimeMillis());
        //必须要加上单引号 下面是是格式化模板
        final SimpleDateFormat dateFormat = new SimpleDateFormat("'" + timeFormatHeader + "'" + TIME_FORMAT, Locale.getDefault());
        return dateFormat.format(date);
    }

    /**
     * @param timeFormatHeader 调用者自己指定的文件头(除去时间部分)
     * @param extension         文件的后缀名,同样由 调用者指定
     * @return 返回 模板格式化后 的文件名(指定文件头 + 格式化的时间)!!
     *
     *          模板:文件头_当前时间.后缀
     */
    public static String getFileNameByTime(String timeFormatHeader, String extension) {
        return getTimeFormatName(timeFormatHeader) + "." + extension;
    }
}

CameraImageBean

public final class CameraImageBean {

    private Uri mPath = null;

    //单例模式
    private static final CameraImageBean INSTANCE = new CameraImageBean();
    public static CameraImageBean getInstance(){
        return INSTANCE;
    }

    public Uri getPath() {
        return mPath;
    }

    public void setPath(Uri mPath) {
        this.mPath = mPath;
    }
}

RequestCodes

public class RequestCodes {
    public static final int TAKE_PHOTO = 4;//拍照
    public static final int PICK_PHOTO = 5;//选择图片
    public static final int CROP_PHOTO = UCrop.REQUEST_CROP;//剪裁
    public static final int CROP_ERROR = UCrop.RESULT_ERROR;//剪裁错误
    public static final int SCAN = 7;//扫描二维码
}

push_bottom_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300">

    <!--从下往上的这样一个过程动画-->
    <translate
        android:fromYDelta="100%p"
        android:toYDelta="0" />

    <alpha
        android:fromYDelta="0.0"
        android:toYDelta="1.0" />

</set>

push_bottom_out.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300">

    <!--从上往下的这样一个过程动画-->
    <translate
        android:fromYDelta="0"
        android:toYDelta="50%p" />

    <alpha
        android:fromYDelta="1.0"
        android:toYDelta="0.0" />

</set>

btn_border.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/white" />
    <corners
        android:bottomLeftRadius="8dp"
        android:bottomRightRadius="8dp"
        android:topLeftRadius="8dp"
        android:topRightRadius="8dp" />

</shape>

btn_border_nativephoto.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/white" />
    <corners
        android:bottomLeftRadius="8dp"
        android:bottomRightRadius="8dp"
        android:topLeftRadius="0dp"
        android:topRightRadius="0dp" />

</shape>

btn_border_takephoto.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/white" />
    <corners
        android:bottomLeftRadius="0dp"
        android:bottomRightRadius="0dp"
        android:topLeftRadius="8dp"
        android:topRightRadius="8dp" />

</shape> 

dialog_camera_panel.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    android:background="@android:color/transparent"
    android:orientation="vertical"
    android:paddingBottom="10dp">

    <android.support.v7.widget.AppCompatButton
        android:id="@+id/photodialog_btn_take"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@drawable/btn_border_takephoto"
        android:gravity="center"
        android:text="拍一张"
        android:textColor="#323232" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:layout_gravity="center"
        android:background="@android:color/transparent"
        android:gravity="center" />


    <android.support.v7.widget.AppCompatButton
        android:id="@+id/photodialog_btn_native"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:background="@drawable/btn_border_nativephoto"
        android:gravity="center"
        android:text="从手机相册选择"
        android:textColor="#323232" />

    <View
        android:layout_width="match_parent"
        android:layout_height="10dp"
        android:layout_gravity="center"
        android:background="@android:color/transparent"
        android:gravity="center" />

    <android.support.v7.widget.AppCompatButton
        android:id="@+id/photodialog_btn_cancel"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:background="@drawable/btn_border"
        android:gravity="center"
        android:text="取消"
        android:textColor="#323232" />

</android.support.v7.widget.LinearLayoutCompat>

style.xml

<resources> 
....
    <!--从底下往上弹、退场弹回底下的动画-->
    <style name="anim_panel_up_from_bottom" parent="@android:style/Animation">
        <!--进入动画-->
        <item name="android:windowEnterAnimation">@anim/push_bottom_in</item>
        <!--退场动画-->
        <item name="android:windowExitAnimation">@anim/push_bottom_out</item>
    </style>
</resources> 

调用

XiaoYunCamera.java

public class XiaoYunCamera {

    //需要剪裁的文件
    public static Uri createCropFile() {
        return Uri.parse(FileUtil.createFile("crop_image",
                FileUtil.getFileNameByTime("IMG", "jpg")).getPath());
    }

    public static void start(PermissionCheckerDelegate delegate) {
        new CameraHandler(delegate).beginCameraDialog();//弹出Dialog弹框 内容:三个按钮,拍照 选图 取消
    }
}

回调

https://github.com/aaLiweipeng/XiaoYunEC/commit/0dc55e754356b84fe436db7dcdc0424f9ce68846
@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == RESULT_OK) {

            switch (requestCode) {
                case RequestCodes.TAKE_PHOTO:
                    //获取到 存储照片的 临时文件路径
                    final Uri resultUri = CameraImageBean.getInstance().getPath();
                    UCrop.of(resultUri, resultUri)//一参为 欲剪裁图片的路径,二参为 放置剪切完图片的路径
                            .withMaxResultSize(400, 400)
                            .start(getContext(), this);//start()用意看源码
                    break;

                case RequestCodes.PICK_PHOTO:

                    if (data != null) {
                        final Uri pickPath = data.getData();//拿到用户选择的图片的路径

                        //从相册选择后 需要有个路径 来存放 剪裁过的图片
                        final String pickCropPath = XiaoYunCamera.createCropFile().getPath();

                        UCrop.of(pickPath, Uri.parse(pickCropPath))
                                .withMaxResultSize(400, 400)
                                .start(getContext(), this);
                    }
                    break;

                case RequestCodes.CROP_PHOTO:

                    final Uri cropUri = UCrop.getOutput(data);

                    //拿到剪裁后的数据进行处理
                    @SuppressWarnings("unchecked")
                    final IGlobalCallback<Uri> callback = CallbackManager
                            .getInstance()
                            .getCallback(CallbackType.ON_CROP);//拿到回调接口
                    if (callback != null) {
                        callback.executeCallback(cropUri);//执行回调接口方法
                    }
                    break;

                case RequestCodes.CROP_ERROR:
                    Toast.makeText(getContext(), "剪裁出错", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    }

推荐阅读更多精彩内容