本文实现的简易的照相机,包括拍照、选图、分享三个功能,主要涉及到Intent隐式启动、Intent动作、Uri、危险权限读取、图像处理、手机旋转等知识。
首先,设计程序布局,仅有一个活动界面,如下所示:
设计程序的详细步骤、所涉知识整理如下:
/**
* 项目目标:实现简易照相机
* 实现方式:启动系统照相机
* 学习目标:Intent隐式启动、简单模仿相机的图片处理
*
* 实现步骤:
* 1、设计程序布局
* 2、使用Intent启动系统照相机
* 3、图片处理:
* (1)、权限获取
* (2)、保存图片
* (3)、显示图片
* (4)、手机旋转,图像的处理
* 4、使用Intent浏览并选取照片
* 5、分享照片
*
* 详细实现过程及知识整理:
* 一、使用Intent启动系统照相机
* Intent it = new Intent("android.media.action.IMAGE_CAPTURE");
* startActivityForResult(it,100);
* 涉及动作:android.media.action.IMAGE_CAPTURE,意为拍照。
* 启动方式:要求动作完成后返回数据。
* 100作为自定义标志符,用于识别。即处理多个返回的数据时,可以确定是哪一个Intent。
*
* 二、图片处理
* 1、权限获取
* 若要保存图片,必然涉及写操作,需要写权限才可以,这在android系统中是一个危险权限,
* 实现读写操作,需要在AndroidManifest.xml中加入以下权限:
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
* 同时,还需要向用户请求获取这些权限,因为上述两个权限属于STORAGE权限组,所以只要其中一个
* 权限被允许,其他同类权限也会被自动允许,故程序中可以只添加其中一个权限。
*
* 以写权限为例:
* if (ActivityCompat.checkSelfPermission(this,
* Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
* PackageManager.PERMISSION_GRANTED) { //检查是否已获得写入权限
* ActivityCompat.requestPermissions(this, //向用户要求允许写入权限
* new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},200);
* }
* 在向用户请求权限的代码中,方法的第二个参数必须为数组形式,可以一次请求多个权限,第三个参数200
* 代表自定义的识别码,用户无论是否同意权限,程序都可以通过onRequestPermissionsResult方法接收
* 结果,此时可以作为识别之用。如下代码所示:
* public void onRequestPermissionsResult(int requestCode, String[] permissions,
* int[] grantResults){
* if (requestCode == 200){
* if (grantResults[0] == PackageManager.PERMISSION_GRANTED){ //用户允许权限
* savePhoto(); //获取权限后才可以进行保存图片操作
* }else{ //用户拒绝权限
* Toast.makeText(this,"程序需要写入权限才能运行",Toast.LENGTH_SHORT).show();
* }
* }
* }
* 其中grantResults数组中存放的就是用户允许还是拒绝权限的结果,可以同时有多个,与要求权限时的
* 权限排列顺序、数量相同。
*
* 2、保存图片
* 因为资源的存取只能通过内容提供者,利用URI的传递,以便同意管控权限而避免不当的存取。
* 首先使用getContentResolver()获得系统的内容提供者,然后通过它在手机共享图像文件路径里新增
* 一个文件,并传回该文件的Uri对象,然后将该对象保存。
* 综上所述,获取权限后,启动系统照相机,启动时将Uri对象加入额外数据中,如下代码所示:
* private void savePhoto(){
* imgUri = getContentResolver().insert(
* MediaStore.Images.Media.EXTERNAL_CONTENT_URI,new ContentValues());
* Intent it = new Intent("android.media.action.IMAGE_CAPTURE");
* it.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);
* startActivityForResult(it, 100);
* }
* 其中putExtra方法中,通过内容提供者获取的对象就是imgUri,名字为MediaStore.EXTRA_OUTPUT,
* 相机程序会用这个名字读取未来拍照存档的URI。
*
* 3、显示图片
* (1)、用BitmapFactory来读取图像。
* Bitmap bmp = BitmapFactory.decodeStream(getContentResolver().
* openInputStream(imgUri), null, null);//读取图像内容并存储为Bitmap对象
* imv.setImageBitmap(bmp); //将Bitmap对象显示在ImageView中
* 如此一来就会把图片显示出来了,但是这样做有一个弊端,当图片过大的时候,很有可能会因为内存不
* 足而导致程序崩溃,所以需要对显示的图片进行约束,合理显示,避免过大。
* (2)、用BitmapFactory.Options设置加载图像文件的选项
* BitmapFactory.Options option = new BitmapFactory.Options(); //新建选项对象
* option.inJustDecodeBounds = true; //设置选项:只读取图片文件信息而不加载图片文件
* BitmapFactory.decodeStream(imgUri.getPath(),option);//读取图像文件信息存入option中
* iw = option.outWidth(); //从option中读取图像文件的宽
* ih = option.outHeight(); //从option中读取图像文件的高
* 按比例缩小图像:
* vw = imv.getWidth(); //获取 ImageView 的宽度
* vh = imv.getHeight(); //获取 ImageView 的高度
* int scaleFactor = Math.min(iw/vw, ih/vh); // 计算缩小比例
* option.inJustDecodeBounds = false; //关闭只加载图片文件信息的选项
* option.inSampleSize = scaleFactor; //设置缩小比例,例如2则长宽都将缩小为原来的1/2
* 这时图像的处理之后的信息都在Options对象中,载入图片:
* Bitmap bmp = BitmapFactory.decodeStream(
* getContentResolver().openInputStream(imgUri), null, option);
* 显示图片:imv.setImageBitmap(bmp); //显示图片
*
* 4、手机旋转,图像的处理。
* 如果手机开启自动旋转屏幕,则刚才拍摄的照片在显示中,会因为屏幕的旋转而消失。
* 解决方式有两种:
* 方式一:在发生旋转时,立即将所显示照片的Uri存储起来,等旋转完并重新启动Activity后,在按
* 照存储的Uri将照片显示出来,另外,还可以按照旋转的方向载入不用的界面Layout文件来显示。
* 方式二:关闭手机界面的自动旋转功能。
* 用户在拍摄时会有横拍或竖拍,所以最好由程序决定要不要旋转屏幕,而不是随手机的旋转而旋转。
* (1)、关闭自动旋转功能并设置屏幕为直向显示
* //设置屏幕不随手机旋转
* setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
* /设置屏幕直向显示/
* setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
* (2)、处理横拍和竖拍时,图像宽高的缩小比例
* Boolean needRotate; //用来存储是否需要旋转
* int scaleFactor;
* if(iw < ih) { //如果图片的宽度小于高度
* needRotate = false; //不需要旋转
* scaleFactor = Math.min(iw/vw, ih/vh); // 计算缩小比例
* }
* else {
* needRotate = true; //需要旋转
* scaleFactor = Math.min(iw/vh, ih/vw);// 将ImageView的宽高互换来计算缩小比例
* }
* (3)、用Matrix(android.graphics.Matrix)对象旋转图片
* Matrix是一个旋转矩阵,用Bitmap.createBitmap()创建新的Bitmap时,可以用它来旋转图片。
* createBitmap(Bitmap src,int x,int y,int width,int height,Matrix m,boolean filter)
* src为要复制的来源Bitmap对象。
* x、y指定要由来源Bitmap的那个位置开始复制(从左上角算起)。
* width、height为新的Bitmap的宽高。
* m是旋转矩阵,而最后一个参数filter设置了旋转时需要传入true。
* 注:生成新的Bitmap后,原来的Bitmap对象就会被系统回收。
* 具体代码如下:
* if(needRotate) { //如果需要旋转
* Matrix matrix = new Matrix(); //新建 Matrix 对象
* matrix.postRotate(90); //设置旋转角度
* bmp = Bitmap.createBitmap(bmp , //用原来的 Bitmap 创建一个新的 Bitmap
* 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
* }
*
* 做了这么多的图像处理后,为了更直观的了解图片从拍照到显示的变化,可以加入以下代码,用一个
* 弹窗显示出图片的详细信息。
* new AlertDialog.Builder(this)
* .setTitle("图像文件信息")
* .setMessage("图像文件URI:" + imgUri.toString() +
* "\n原始尺寸:" + iw + "x" + ih +
* "\n载入尺寸:" + bmp.getWidth() + "x" + bmp.getHeight() +
* "\n显示尺寸:" + vw + "x" + vh + (needRotate?"(旋转)":""))
* .setNeutralButton("关闭",null)
* .show();
* 窗口中详细的展示了图像的URI路径、拍照尺寸、Bitmap载入的尺寸、ImageView显示的尺寸,还
* 包括是否做了旋转操作。
*
*三、使用Intent浏览选择图片
* Android系统中内建了一个内容提供者,存储着各种数据和多媒体文件等共享数据的相关信息。
* Android内建的图库程序就是从这个数据库中读取可共享的图片文件信息,然后列出来供用户选取。
* 所以将拍摄的照片设为系统共享文件,并用广播Intent的方式通知系统。
* //将imgUri所指的文件设为系统共享媒体文件
* Intent it = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, imgUri);
* sendBroadcast(it); //通知系统
* 选取图片:
* Intent it = new Intent(Intent.ACTION_GET_CONTENT); //动作设为 "选取内容"
* it.setType("image/*"); //设置要选取的媒体类型为:所有类型的图片
* startActivityForResult(it, 101); //启动意图, 并要求返回选取的图片
* 101是自定义的识别码。上面的代码中要求返回选取的图片,所以在onActivityResult方法中可以
* 根据识别码判断是哪一个Intent的动作,并获取这个图片的Uri信息,然后将其显示。
*
* 四、分享图片
* 只要图片不为空,就可以使用动作ACTION_SEND分享图片,代码如下:
* if(imgUri != null){
* Intent it = new Intent(Intent.ACTION_SEND);
* it.setType("image/*");
* it.putExtra(Intent.EXTRA_STREAM,imgUri);
* startActivity(it);
* }
*
* */
程序具体代码如下所示:
package com.my.camera;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
Uri imgUri; //记录图片信息
ImageView imv; //视图组件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//设置屏幕不随手机旋转
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
//设置屏幕直向显示
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//关联视图组件
imv = (ImageView)findViewById(R.id.imageView);
}
/**
* 按钮“拍照”的点击事件
* */
public void onGet(View v){
//检查是否获得写入权限,未获得则向用户请求
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED){
//未获得,向用户请求
ActivityCompat.requestPermissions(this,new String[]
{Manifest.permission.WRITE_EXTERNAL_STORAGE},200);
}else{
//已经获得权限
savePhoto();
}
}
/**
* 按钮“图库”的点击事件
* */
public void onPick(View v){
Intent it = new Intent(Intent.ACTION_GET_CONTENT); //设置动作为选取内容
it.setType("image/*"); //设置要选取的媒体类型:所有类型图片
startActivityForResult(it,101); //启动Intent,并要求返回选取的图像文件
}
/**
* 按钮“分享”的点击事件
* */
public void onShare(View v){
if(imgUri != null){
Intent it = new Intent(Intent.ACTION_SEND);
it.setType("image/*");
it.putExtra(Intent.EXTRA_STREAM,imgUri);
startActivity(it);
}
}
/**
* 返回用户是否允许权限的结果,并处理
* */
public void onRequestPermissionsResult(int requestCode,String[] permissions, int[] grantResult){
if(requestCode == 200){
//用户允许权限
if(grantResult[0] == PackageManager.PERMISSION_GRANTED){
savePhoto(); //允许写权限才可以保存图片
}else{ //用户拒绝
Toast.makeText(this,"程序需要写入权限才能运行",Toast.LENGTH_SHORT).show();
}
}
}
private void savePhoto(){
//通过内容提供者新增一个图像文件
imgUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new ContentValues());
Intent it = new Intent("android.media.action.IMAGE_CAPTURE"); //指定动作,拍照
it.putExtra(MediaStore.EXTRA_OUTPUT,imgUri); //将uri加到拍照Intent的额外数据
startActivityForResult(it,100); //设置100为识别码,启动活动
}
/**
* 处理通过Intent启动的活动的返回结果
* 识别码100,拍照活动返回的数据,将拍摄的图片设为共享,并通知系统
* 识别码101,选取图片活动返回的数据,直接读取数据并存储到imgUri中
* 调用showImg方法显示图片
* 根据识别码来显示不同的出错Toast信息
* */
protected void onActivityResult(int requestCode,int resultCode,Intent data){
super.onActivityResult(requestCode,resultCode,data);
if(resultCode == Activity.RESULT_OK){
switch(requestCode){
case 100:
//设为系统共享媒体文件
Intent it = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,imgUri);
sendBroadcast(it);
break;
case 101:
//选取图片,直接获取该图片的数据
imgUri = data.getData();
break;
}
showImg(); //显示图片
}else{ //根据识别码的不同,显示不同的没有获取到图片的信息
Toast.makeText(this,requestCode==100?"没有拍到照片":"没有选取照片",
Toast.LENGTH_SHORT).show();
}
}
/**
* 显示图片
* 避免图像过大,进行处理后显示
* 判断图片是横拍还是直拍决定是否旋转
* 显示图片处理过程的详细信息
* */
public void showImg(){
int iw,ih,vw,vh; //iw,ih为图片宽高,vw,vh为ImageView组件的宽高
boolean needRotate; //用来存储是否需要旋转
Bitmap bmp = null; //创建Bitmap对象
BitmapFactory.Options option = new BitmapFactory.Options(); //创建选项对象
option.inJustDecodeBounds = true; //设置选项:只读取图像文件信息而不载入图像文件
try{
//读取图像文件信息存入option中
BitmapFactory.decodeStream(getContentResolver().openInputStream(imgUri),null,option);
}catch(IOException e){
Toast.makeText(this,"读取照片信息时发生错误",Toast.LENGTH_SHORT).show();
return;
}
iw = option.outWidth; //从option中读取图像文件的宽度
ih = option.outHeight; //从option中读取图像文件的高度
vw = imv.getWidth(); //获取ImageView的宽度
vh = imv.getHeight(); //获取ImageView的高度
int scaleFactor;
if(iw < ih){
needRotate = false; //不需要旋转
scaleFactor = Math.min(iw/vw,ih/vh); //计算缩小比例
}else{
needRotate = true;
scaleFactor = Math.min(ih/vw,iw/vh); //改用旋转后的图像宽、高计算缩小比例
}
option.inJustDecodeBounds = false; //关闭之加载图像文件信息的选项
option.inSampleSize = scaleFactor; //设置缩小比例,若为3,则长宽将缩小为原来的1/3
try{
//加载图片
bmp = BitmapFactory.decodeStream(getContentResolver().openInputStream(imgUri),
null,option);
}catch(IOException e){
Toast.makeText(this,"无法取得照片",Toast.LENGTH_SHORT).show();
}
if(needRotate){
Matrix matrix = new Matrix(); //创建Matrix对象
matrix.postRotate(90); //旋转90°,顺时针
//用原来的图像产生一个新的图片
bmp = Bitmap.createBitmap(bmp,0,0,bmp.getWidth(),bmp.getHeight(),matrix,true);
}
imv.setImageBitmap(bmp); //显示图片
//弹窗显示图片的信息
new AlertDialog.Builder(this)
.setTitle("图像文件信息")
.setMessage("图像文件URI:" + imgUri.toString() +
"\n原始尺寸:" + iw + "x" + ih +
"\n载入尺寸:" + bmp.getWidth() + "x" + bmp.getHeight() +
"\n显示尺寸:" + vw + "x" + vh + (needRotate?"(旋转)":""))
.setNeutralButton("关闭",null)
.show();
}
}
// 总结参考:《Android App开发入门 第2版》 机械工业出版社 施威铭 著 2017.8
程序的部分运行结果如下所示:
(哇,我在编辑的时候发现这个图好大好大啊,不知道发布出去之后会不会还这么大。)
这个简易照相机非常的简单,使用起来非常的呆板,还需要不断的完善。