Android:设置头像

该文章讲述了Android原生态开发过程中设置用户原型头像的实现过程。主要使用到技术有:Android原生态开发、CircleImageView圆形图片视图、Crop裁剪工具等。

1.业务介绍

业务具体流程可以分为一下几个过程:
1.用户点击进行圆形头像设置,可以选择拍照设置和从本地选择图片进行设置两种设计方案。(一下以拍照设置为例进行说明)
2.调用手机相机进行拍照
3.获取拍照照片后调用Crop工具进行照片裁剪。
4.将照片资源添加到CircleImageView视图中。
下面将对整个过程进行详细讲解。

2.详细过程说明

选择照片来源

头像来源可以时本地也可以时拍照,系统可以为用户提供两种选择途径。该功能的实现方式可以采用Dialog实现。具体可见:一个好看的Dialog样式实现,仿IOS。

获取照片资源

上述已经说明,获取照片的途径有两种,如果进行拍照设置,那么系统应该调用手机相机进行拍照,如果选择本地照片,那么系统应该打开手机本地图库。该过程的具体代码如下:

    /**
     * 从本地相册选取图片作为头像
     * 将为用户打开本地图库
     */
    public void choseHeadImageFromGallery() {
        Intent intentFromGallery = new Intent();
        // 设置文件类型
        intentFromGallery.setType("image/*");
        intentFromGallery.setAction(Intent.ACTION_GET_CONTENT);
        activity.startActivityForResult(intentFromGallery, CODE_GALLERY_REQUEST);
    }
    /**
     * 启动手机相机拍摄照片作为头像
     * 将调用本地相机
     * 注意:该过程中首先判断了系统是否有存储卡,如果有的情况下将为Intent设置一个Uri对象,该对象可以理解为资源标识符。
     * Uri资源标识符将标识一个资源的存在,可以通过它获取一个资源信息。
     * 当为Intent设置 MediaStore.EXTRA_OUTPUT 输出位置时onActivityResult方法的intent.getData()方法将获取的时一个null
     * 否则获取是Bitmap对象
     */
    public void choseHeadImageFromCameraCapture() {
        Intent intentFromCapture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 判断存储卡是否可用,存储照片文件
        if (hasSdcard()) {
            uri = getUriForFile(activity,"head");
            intentFromCapture.putExtra(MediaStore.EXTRA_OUTPUT,uri);
        }
        activity.startActivityForResult(intentFromCapture, CODE_CAMERA_REQUEST);
    }

上述过程中使用了hasSdcard()方法和getUriForFile(activity,“head”)方法,这两个方法的实现过程如下:

    /**
     * 检查设备是否存在SDCard的工具方法
     */
    public boolean hasSdcard() {
        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {
            // 有存储的SDCard
            return true;
        } else {
            return false;
        }
    }
    /** 获取uri资源 **/
    public static Uri getUriForFile(Context context,String path){
        // 生成文件
        makeRootDirectory(basePath + DataTool.sdf_ymd.format(new Date()));
        // 生成文件
        File file = new File(basePath +DataTool.sdf_ymd.format(new Date()) , path + ".jpg");
        PicTool.file = file.getAbsolutePath();
        return getUriForFile(context,file);
    };
	/** 生成文件夹 **/
	public static boolean makeRootDirectory(String filePath) {
		File file = null;
		try {
			file = new File(filePath);
			if (!file.exists()) {
				file.mkdirs();
			}else{
				file.delete();
				file.mkdirs();
			}
			return true;
		} catch (Exception e) {
			Log.i("error:", e+"");
			return false;
		}
	}
    /**
     * 生成URL
     * @param context
     * @param file
     * @return
     */
    public static Uri getUriForFile(Context context, File file) {
        if (context == null || file == null) {
            throw new NullPointerException();
        }
        Uri uri;
        if (Build.VERSION.SDK_INT >= 24) {
            uri = FileProvider.getUriForFile(context.getApplicationContext(), "包名.fileprovider", file);
        } else {
            uri = Uri.fromFile(file);
        }
        return uri;
    }

由以上代码可以看出,在生成Uri对象的过程中使用到了FileProvider,这是由于在Android7.0以后使用FileProvider在应用中共享文件资源。在使用FileProvider过程中需要进行配置。配置过程如下:
1.首先在res文件夹下床架xml文件夹,并创建file_path.xml文件。在该文件中做出一下配置:


<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="external"
        path="." />
    <external-files-path
        name="external_files"
        path="." />
    <cache-path
        name="cache"
        path="." />
    <external-cache-path
        name="external_cache"
        path="." />
    <files-path
        name="files"
        path="." />
paths>

2.在AndroidManifest.xml文件中进行配置刚才的xml信息。

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="包名.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path" />
        provider>

以上两个操作可以完成FileProvider使用设置,该过程实际上标识了该应用可获取文件资源的范围。

拍照后的操作

拍照后的结果是由onActivityResult来接受处理的,在前面代码中

 Intent intentFromCapture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 判断存储卡是否可用,存储照片文件
        if (hasSdcard()) {
            uri = getUriForFile(activity,"head");
            intentFromCapture.putExtra(MediaStore.EXTRA_OUTPUT,uri);
        }
        activity.startActivityForResult(intentFromCapture, CODE_CAMERA_REQUEST);

我们可以看出给Intent对象传入的uri并不是个局部对象,这是由于当intent对象携带uri时,onActivityResult方法的intent对象并不能再次接受到Uri信息。因此需要将Uri对象作为全局变量存储。当调用onActivityResult方法处理照片资源时其实是处理全局的Uri对象。onAcitivityResult方法的具体操作输入:

    public void deal(int requestCode, int resultCode,
                     Intent intent){
        switch (requestCode) {
            case CODE_GALLERY_REQUEST: // 从本地获取
                if (PicTool.hasSdcard()) {
                    Crop.of(intent.getData(), resultUri).asSquare().start(activity);
                } else {
                    Toast.makeText(activity, "没有SDCard!", Toast.LENGTH_LONG)
                            .show();
                }
                break;
            case CODE_CAMERA_REQUEST: // 拍照成功后调用
                if (PicTool.hasSdcard()) {
                    Uri resultUri =  PicTool.getUriForFile(activity,"headPic");
                    Crop.of(uri, resultUri).asSquare().start(activity);
                } else {
                    Toast.makeText(activity, "没有SDCard!", Toast.LENGTH_LONG)
                            .show();
                }
                break;
            case Crop.REQUEST_CROP://使用Crop裁剪之后调用
            {
                if(resultCode == Crop.RESULT_ERROR) {
                    Toast.makeText(activity,"裁剪失败",Toast.LENGTH_SHORT).show();
                }else{
                    //裁剪成功后调用  如果setImageURL设置的url是同一个值的话  则无法改变前端显示
                    Uri result = Crop.getOutput(intent);
                    view.setImageURI(null);
                    view.setImageURI(result);
                }
                break;
            }
        }
    }

调用裁剪

在该过程中,调用裁剪使用了Crop工具。使用该工具需要做出一下引入:

    //圆形头像
    implementation 'de.hdodenhof:circleimageview:3.1.0'
    //裁剪照片
    compile 'com.soundcloud.android:android-crop:1.0.1@aar'
    compile 'com.github.bumptech.glide:glide:3.7.0'

Crop的实际使用过程相对比较简单,可以分为两个过程,如下:
1.在AndroidManifest.xml文件中进行配置

<activity android:name="com.soundcloud.android.crop.CropImageActivity" />

实际上就是声明了一个acitivity,这是因为在进行裁剪的过程中实际上实在Crop实现的Acitivity上进行的,该Activity已经在crop包中实现了,因此需要在AndroidManifest.xml文件中文件中进行说明。
2.代码中调用

/**
* 在该方法中需要三个参数:uri、resultUri和activity
* uri是需要裁剪的照片资源标识符
* resultUri是存储裁剪后的照片的资源标识符
* activity是调用裁剪的主体
**/
Crop.of(uri, resultUri).asSquare().start(activity);

当裁剪操作接受后,其实还是由onResultActivity进行接受处理,处理过程:

            case Crop.REQUEST_CROP://使用Crop裁剪之后调用
            {
                if(resultCode == Crop.RESULT_ERROR) {
                    Toast.makeText(activity,"裁剪失败",Toast.LENGTH_SHORT).show();
                }else{
                    //裁剪成功后调用  如果setImageURL设置的url是同一个值的话  则无法改变前端显示
                    Uri result = Crop.getOutput(intent);
                    view.setImageURI(null);
                    view.setImageURI(result);
                }
                break;
            }

注意:在该过程中,对view进行设置图片资源,首先执行了view.setImageURI(null);方法。这是由于setImageURI方法做了优化处理,它首先判断Uri指向的是否是同一个资源(路径以及文件名是否相同),如果是同一个资源的话,该方便并不会对view再进行Uri设置。

以上整个过程就完成了,下面进行简单梳理一下:
1.调用dialog显示
2.调用相机或者本地图库(如果调用相机的过程中该Intent设置了Uri,则onResultActivity方法的Intent对象的getData方法将接受不到信息)
3.图片资源信息获取成功后通过Crop进行裁剪
4.裁剪后的信息依旧交由onResultActivity方法进行处理

3.具体实现过程

1.jar包的引入:

    //圆形头像
    implementation 'de.hdodenhof:circleimageview:3.1.0'
    //裁剪照片
    compile 'com.soundcloud.android:android-crop:1.0.1@aar'
    compile 'com.github.bumptech.glide:glide:3.7.0'

2.在xml视图文件中配置圆形图片信息

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/register_user_head_pic"
        android:layout_width="150dp"
        android:layout_height="120dp"
        android:layout_marginTop="50dp"
        app:civ_border_color="@color/gray"
        app:civ_border_width="1dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />

3.配置file_path方法:
Android:设置头像_第1张图片


<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="external"
        path="." />
    <external-files-path
        name="external_files"
        path="." />
    <cache-path
        name="cache"
        path="." />
    <external-cache-path
        name="external_cache"
        path="." />
    <files-path
        name="files"
        path="." />
paths>

AndroidManifest.xml文件

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.baiyang.instant_messaging_based_on_android.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path" />
        provider>

provider是和activity标签同级的。此外还需要声明Crop的activity资源:

        <activity android:name="com.soundcloud.android.crop.CropImageActivity" />

4.工具类,下面给将直接给出整个过程中的工具类:

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;

import androidx.core.content.FileProvider;

import com.soundcloud.android.crop.Crop;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import de.hdodenhof.circleimageview.CircleImageView;

/**
 *  圆形头像工具类
 */
public class CircleImageTool {
    // 圆形视图
    private CircleImageView view;
    // 调用主体
    private Activity activity;

    private String fileprovider;

    /**
     * 构造函数
     * @param activity -- 进行头像蛇者的activity
     * @param view -- 圆形视图对象
     * @param fileprovider -- AndroidManifest.xml文件中provider资源的所有者:android:authorities
     */
    public CircleImageTool(Activity activity,CircleImageView view,String fileprovider){
        this.activity = activity;
        this.view = view;
        this.fileprovider = fileprovider;
    }

    /* 请求识别码 */
    private static final int CODE_GALLERY_REQUEST = 0xa0; // 本地照片
    private static final int CODE_CAMERA_REQUEST = 0xa1; // 拍照
    // 图片资源标识符
    private Uri uri;

    /**
     * 从本地相册选取图片作为头像
     * 将为用户打开本地图库
     *
     * 当用户点击从本地获取时直接调用该方法
     */
    public void choseHeadImageFromGallery() {
        Intent intentFromGallery = new Intent();
        // 设置文件类型
        intentFromGallery.setType("image/*");
        intentFromGallery.setAction(Intent.ACTION_GET_CONTENT);
        activity.startActivityForResult(intentFromGallery, CODE_GALLERY_REQUEST);
    }
    /**
     * 启动手机相机拍摄照片作为头像
     * 将调用本地相机
     * 注意:该过程中首先判断了系统是否有存储卡,如果有的情况下将为Intent设置一个Uri对象,该对象可以理解为资源标识符。
     * Uri资源标识符将标识一个资源的存在,可以通过它获取一个资源信息。
     * 当为Intent设置 MediaStore.EXTRA_OUTPUT 输出位置时onActivityResult方法的intent.getData()方法将获取的时一个null
     * 否则获取是Bitmap对象
     *
     * 当用户点击拍照时直接调用该方法
     */
    public void choseHeadImageFromCameraCapture() {
        Intent intentFromCapture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 判断存储卡是否可用,存储照片文件
        if (hasSdcard()) {
            uri = getUriForFile(activity,"head");
            intentFromCapture.putExtra(MediaStore.EXTRA_OUTPUT,uri);
        }
        activity.startActivityForResult(intentFromCapture, CODE_CAMERA_REQUEST);
    }

    /**
     * 接受到返回结果时调用
     * @param requestCode
     * @param resultCode
     * @param intent
     */
    public void deal(int requestCode, int resultCode,
                     Intent intent){
        switch (requestCode) {
            case CODE_GALLERY_REQUEST: // 从本地获取
                if (PicTool.hasSdcard()) {
                    Uri resultUri = getUriForFile(activity,"headPic");
                    // 从本地获取  intent.getData()方法返回选中图片资源
                    Crop.of(intent.getData(), resultUri).asSquare().start(activity);
                } else {
                    Toast.makeText(activity, "没有SDCard!", Toast.LENGTH_LONG)
                            .show();
                }
                break;
            case CODE_CAMERA_REQUEST: // 拍照成功后调用
                if (hasSdcard()) {
                    Uri resultUri =  getUriForFile(activity,"headPic");
                    Crop.of(uri, resultUri).asSquare().start(activity);
                } else {
                    Toast.makeText(activity, "没有SDCard!", Toast.LENGTH_LONG)
                            .show();
                }
                break;
            case Crop.REQUEST_CROP://使用Crop裁剪之后调用
            {
                if(resultCode == Crop.RESULT_ERROR) {
                    Toast.makeText(activity,"裁剪失败",Toast.LENGTH_SHORT).show();
                }else{
                    //裁剪成功后调用  如果setImageURL设置的url是同一个值的话  则无法改变前端显示
                    Uri result = Crop.getOutput(intent);
                    view.setImageURI(null);
                    view.setImageURI(result);
                }
                break;
            }
        }
    }


    /**
     * 检查设备是否存在SDCard的工具方法
     */
    private boolean hasSdcard() {
        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {
            // 有存储的SDCard
            return true;
        } else {
            return false;
        }
    }

    private static final SimpleDateFormat sdf_ymd = new SimpleDateFormat("yyyy-MM-dd", Locale.CANADA);

    private Uri getUriForFile(Context context,String path){
        // 文件管理下:/Android/Data/包名/file/日期/
        String basePath = activity.getExternalFilesDir("").getAbsolutePath()+ File.separator  + sdf_ymd.format(new Date());
        // 生成文件
        makeRootDirectory(basePath);
        // 生成文件
        File file = new File(basePath , path + ".jpg");
        return getUriForFile(context,file);
    };

    // 生成文件夹
    private boolean makeRootDirectory(String filePath) {
        File file = null;
        try {
            file = new File(filePath);
            if (file.exists()) {
                file.delete();
            }
            file.mkdirs();
            return true;
        } catch (Exception e) {
            Log.i("error:", e+"");
            return false;
        }
    }
    /**
     * 生成URL
     * @param context
     * @param file
     * @return
     */
    private Uri getUriForFile(Context context, File file) {
        if (context == null || file == null) {
            throw new NullPointerException();
        }
        Uri uri;
        if (Build.VERSION.SDK_INT >= 24) {
            uri = FileProvider.getUriForFile(context.getApplicationContext(), fileprovider, file);
        } else {
            uri = Uri.fromFile(file);
        }
        return uri;
    }

}


你可能感兴趣的:(Android原生态开发,android,java)