安卓开发:从系统相册选择照片

概述

与调用系统摄像头拍照类似的,从系统相册选择照片的核心代码也仅仅只是一句呼唤系统Intent:
Intent intent = new Intent("android.intent.action.GET_CONTENT");

不过我们同样也要围绕这句核心代码做很多准备工作。

第一步:获取权限

从系统相册获取照片,需要对存储器的读写操作权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

注意:

比较悲剧的是,这两个权限在Android6.x之后也被划分为高危系统权限,因此仅仅只是在AndroidManifest.xml文件中声明一下是没有用的,还需要转本编写代码进行动态运行时权限申请。
详细信息可以参考我的这篇文章:
http://blog.csdn.net/freezingxu/article/details/71409860

第二步:绘制或编写Activity界面

在我的应用场景中,我的界面是这个样子的:

我在界面上用TableLayout做了一个容器,每当从相册选择一张照片后,这张照片就会被放置在这个TableLayout中。如果选择了多张照片,那么就会像上图那样形成一个列表。
这样的界面结构其实非常简单,不过我还是把代码粘帖在这里:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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="com.yumi.mibao.easyasset.activities.TakePhotoActivity"
    android:background="@color/metro_white">



    <Button
        android:id="@+id/BUTTON_COMPLETE"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:background="@color/yumi_red"
        android:textColor="@color/metro_white"
        android:text="@string/str_complete"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="8dp"
        app:layout_constraintLeft_toRightOf="@+id/BUTTON_TAKE_PHOTO"
        android:layout_marginLeft="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp" />

    <Button
        android:id="@+id/BUTTON_TAKE_PHOTO"
        android:layout_width="68dp"
        android:layout_height="48dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:background="@color/metro_grass"
        android:text="@string/str_take_photo"
        android:textColor="@color/metro_white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/BUTTON_ALBUM"
        android:layout_marginStart="8dp"
        android:onClick="takePohto"/>

    <Button
        android:id="@+id/BUTTON_ALBUM"
        android:layout_width="68dp"
        android:layout_height="48dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:background="@color/metro_blue"
        android:text="@string/str_album"
        android:textColor="@color/metro_white"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginStart="8dp"
        android:onClick="pickPhotoFromAlbum"
        app:layout_constraintLeft_toLeftOf="parent" />



    <ScrollView
        android:layout_width="0dp"
        android:layout_height="495dp"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintHorizontal_bias="0.0"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/BUTTON_COMPLETE">

        <TableLayout
            android:id="@+id/TABLE_LAYOUT_TAKE_PHOTO_LIST"
            android:layout_width="match_parent"
            android:layout_height="fill_parent"
            android:layout_gravity="top"
            android:layout_marginTop="8dp"
            android:gravity="top" />
    </ScrollView>
</android.support.constraint.ConstraintLayout>

在上面的代码中,可以看到id为“BUTTON_ALBUM”的按钮就对应了图片中的“相册”按钮,在这个按钮的onclick事件中,我触发了一个方法“pickPhotoFromAlbum”,就是这个方法会调起手机的拍照功能。具体实现往下看。

第三步:编写事件触发方法

3.1定义用于Intent回调的请求码

由于呼出系统拍照功能的Intent是采用startActivityForResult的模式来进行,所以需要明确的请求码来标记操作的类型,这样才能够在Intent的回调方法中进行相应的数据处理:
private final int PICK_FROM_ALBUN = 3;//从相册选择
实际上,我们还会用到一个裁剪图片的请求码,定义如下:
private final int CROP_PHOTO = 2;//切图操作
在后续的步骤中,我们会用到这个请求码

3.2定义被选中的照片的保存路径和文件名

/*
 *  从相册选择所得到的图像的保存路径
 */
private Uri imageUri;

/*
 *  从相册选择的照片的文件名
 */
private String fileName;

3.3编写方法,呼出系统Intent,从相册选择照片

先来看一下选择相册的界面是什么样子的:


/**
 * 点击按钮从手机相册中获取图片
 * @param view
 */
public void pickPhotoFromAlbum(View view){
    /*
     *  用时间戳的方式来命名图片文件,这样可以避免文件名称重复
     */
    SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
    Date date = new Date(System.currentTimeMillis());
    this.fileName = "easyassetFromAlbum"+format.format(date);

    /*
     *  创建一个File对象,用于存放选到的照片
     */
    File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
    File outputImage = new File(path,this.fileName+".jpg");

    /*
     *  以防万一,看一下这个文件是不是存在,如果存在的话,先删除掉
     */
    if(outputImage.exists()){
        outputImage.delete();
    }

    try {
        outputImage.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }

    /*
     *  将File对象转换为Uri对象,以便拍照后保存
     */
    this.imageUri = Uri.fromFile(outputImage);

    /*
     *  启动系统的选择界面
     */
    Intent intent = new Intent("android.intent.action.GET_CONTENT");
    intent.putExtra("scale", true);//设置可以缩放
    intent.putExtra("crop", true);//设置可以裁剪
    intent.setType("image/*");//设置需要从系统选择的内容:图片
    intent.putExtra(MediaStore.EXTRA_OUTPUT, this.imageUri);//设置输出位置
    startActivityForResult(intent, this.PICK_FROM_ALBUN);//开始选择
}
可以看到,在代码的最后,采用startActivityForResult的形式呼出了系统Intent,并且将请求码“PICK_FROM_ALBUM”作为参数发送了过去。因此,我们必须编写onActivityResult方法,来处理回调业务。

3.4编写Intent回调方法

/**
 * 因为启动系统Intent使用的forResult模式,因此需要onActivityResult方法来接受回调参数
 * @param requestCode
 * @param resultCode
 * @param data
 */
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){
    super.onActivityResult(requestCode,resultCode,data);

    if (resultCode != RESULT_OK) {
        Toast.makeText(this, "获取图片出现错误", Toast.LENGTH_SHORT).show();
    }
    else{
        switch(requestCode) {
            

            /*
             *  case PICK_FROM_ALBUM 代表从选择相册的intent返回之后
             *  完成从相册中选择照片后,就要将图片生成bitmap对象,然后显示在界面上了
             */
            case PICK_FROM_ALBUN:
                this.cropPhoto(data.getData());
                break;
             default:
                break;
        }
    }
}
可以看到,在switch中我们捕获了请求码“PICK_FROM_ALBUM”,然后立即调用了 一个方法“cropPhoto”,实际上这是一个用来裁剪图片的方法。

3.5编写图片裁剪方法

/**
 * 打开裁剪图片的系统界面
 */
private void cropPhoto(Uri imageUri){
    /*
     *  准备打开系统自带的裁剪图片的intent
     */
    Intent intent = new Intent("com.android.camera.action.CROP"); //打开系统自带的裁剪图片的intent
    intent.setDataAndType(imageUri, "image/*");
    intent.putExtra("scale", true);

    /*
     *  设置裁剪区域的宽高比例
     */
    intent.putExtra("aspectX", 1);
    intent.putExtra("aspectY", 1);

    /*
     *  设置裁剪区域的宽度和高度
     */
    intent.putExtra("outputX", 340);
    intent.putExtra("outputY", 340);

    /*
     *  指定裁剪完成以后的图片所保存的位置
     */
    intent.putExtra(MediaStore.EXTRA_OUTPUT, this.imageUri);
    Toast.makeText(this, "剪裁图片", Toast.LENGTH_SHORT).show();

    /*
     *  以广播方式刷新系统相册,以便能够在相册中找到刚刚所拍摄和裁剪的照片
     */
    Intent intentBc = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    intentBc.setData(this.imageUri);
    this.sendBroadcast(intentBc);

    /*
     *  以forResult模式启动系统自带的裁剪图片的intent
     */
    startActivityForResult(intent, CROP_PHOTO); //设置裁剪参数显示图片至ImageView
}
可以看到,在上述方法的最后,我们启动了系统自带图片裁剪Intent,并且将请求码“CROP_PHOTO”以startActivityForResult的形式传递过去。
手机打开的且图界面是这样的:


3.6在Intent的回调方法中,增加对切图回调的处理

/*
 *  case CROP_PHOTO 代表从裁剪照片的intent返回之后
 *  完成裁剪照片后,就要将图片生成bitmap对象,然后显示在界面上面了
 */
case CROP_PHOTO:
    try {
        /*
         *  将图片转换成Bitmap对象
         */
        Bitmap bitmap = BitmapFactory.decodeStream(this.getContentResolver().openInputStream(this.imageUri));
        Toast.makeText(this, this.imageUri.toString(), Toast.LENGTH_SHORT).show();

        /*
         *  在界面上显示图片
         */
        this.addPhotoToActivity(bitmap);
    } catch(FileNotFoundException e) {
        e.printStackTrace();
    }
    break;
所以,整个回调方法看起来是这样的:
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){
    super.onActivityResult(requestCode,resultCode,data);

    if (resultCode != RESULT_OK) {
        Toast.makeText(this, "获取图片出现错误", Toast.LENGTH_SHORT).show();
    }
    else{
        switch(requestCode) {

            /*
             *  case CROP_PHOTO 代表从裁剪照片的intent返回之后
             *  完成裁剪照片后,就要将图片生成bitmap对象,然后显示在界面上面了
             */
            case CROP_PHOTO:
                try {
                    /*
                     *  将图片转换成Bitmap对象
                     */
                    Bitmap bitmap = BitmapFactory.decodeStream(this.getContentResolver().openInputStream(this.imageUri));
                    Toast.makeText(this, this.imageUri.toString(), Toast.LENGTH_SHORT).show();

                    /*
                     *  在界面上显示图片
                     */
                    this.addPhotoToActivity(bitmap);
                } catch(FileNotFoundException e) {
                    e.printStackTrace();
                }
                break;

            /*
             *  case PICK_FROM_ALBUM 代表从选择相册的intent返回之后
             *  完成从相册中选择照片后,就要将图片生成bitmap对象,然后显示在界面上了
             */
            case PICK_FROM_ALBUN:
                this.cropPhoto(data.getData());
                break;

            default:
                break;
        }
    }
}
可以看到,在完成切图后,直接调用了一个方法“addPhotoToActivity”,来将选择的照片显示在界面上。

3.7显示图片

/**
 * 将拍照和裁剪后所得到的照片,罗列在界面上
 */
private  void addPhotoToActivity(Bitmap bitMap){
    /*
     *  首先获取到用来显示照片的容器
     *  该容易是一个TableLayout
     */
    TableLayout tableLayout = (TableLayout)this.findViewById(R.id.TABLE_LAYOUT_TAKE_PHOTO_LIST);

    /*
     *  创建一个TableRow对象
     *  每一行TableRow对象都用来存放一张照片,以及该照片的上传情况信息
     *  将这个TableRow放入TableLayout中
     */
    TableRow tableRow = new TableRow(this);
    tableRow.setPadding(0,0,0,8);//设置每一行的下间距
    tableLayout.addView(tableRow);

    /*
     *  创建一个ImageView对象
     *  将这个对象放入TableRow中
     *  并在这个对象上显示刚刚拍照所得到的照片
     */
    ImageView imageView = new ImageView(this);
    imageView.setLayoutParams(this.photoParams);
    imageView.setImageBitmap(bitMap);
    tableRow.addView(imageView);

    /*
     *  创建一个TextView对象
     *  为这个对象设置一段“图片正在上传”的提示文字
     *  并将这个TextView对象放入TableRow中
     */
    TextView textView = new TextView(this);
    textView.setLayoutParams(this.uploadStateMsgParam);
    textView.setGravity(Gravity.CENTER_VERTICAL);
    textView.setText("正在上传照片...");
    textView.setTextColor(ContextCompat.getColor(this,R.color.metro_blue));
    tableRow.addView(textView);

    
}
在上述代码中,会用到两个界面组件的样式定义,如下:
/*
 *  一组界面样式,分别是照片在TableRow中所占的宽度比重和照片上传状态的文字信息在TableRow中所占的宽度比重
 */
private TableRow.LayoutParams photoParams;
private TableRow.LayoutParams uploadStateMsgParam;
/**
 * 初始化一些界面组件的样式
 */
private void initLayoutParams(){
    /*
     *  拍照所得到的图片被放置在界面上时,其在TableRow所占的宽度占比
     */
    this.photoParams = new TableRow.LayoutParams(
        TableRow.LayoutParams.WRAP_CONTENT,
        268,
        0.1f
    );

    /*
     *  照片上传状态的文字信息被放置在界面上时,其在TableRow所占的宽度占比
     */
    this.uploadStateMsgParam = new TableRow.LayoutParams(
        TableRow.LayoutParams.WRAP_CONTENT,
        268,
        0.9f
    );
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_easy_asset_take_photo);
 
    /*
     *  调用方法,初始化界面组件的样式
     */
    this.initLayoutParams();
}

至此所有的逻辑就全部实现了。




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值