android 相机广播,Android实践:《简易照相机》的详细实现步骤及知识梳理

本文实现的简易的照相机,包括拍照、选图、分享三个功能,主要涉及到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中加入以下权限:

*  同时,还需要向用户请求获取这些权限,因为上述两个权限属于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

程序的部分运行结果如下所示:

(哇,我在编辑的时候发现这个图好大好大啊,不知道发布出去之后会不会还这么大。)

这个简易照相机非常的简单,使用起来非常的呆板,还需要不断的完善。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值