*本篇文章已授权微信公众号 hongyangAndroid(鸿洋)独家发布
今日,产品经理又疯了,搞这么一个功能,也是没事做了,算了,先来说一下具体功能吧,简化来就是:
各位看官,别着急,是不是觉得特简单,不就Intent跳转打开相机,拍几张照片,在onActivityResult方法里面拿到刚才拍的照片,显示出来不就可以了么?嗯,是的,是这么简单。我来帮您写过程,当然有任何问题可以加QQ群:661614986,进行询问。
这个过程非常的简单,就看下代码,直接跳过。
布局文件:
<RelativeLayout 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.exampleenen.ruedy.moreimagetwo.MainActivity">
<Button
android:id="@+id/bt_main"
android:layout_width="200dp"
android:layout_height="70dp"
android:layout_centerHorizontal="true"
android:text="点击打开相机" />
<GridView
android:id="@+id/gv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/bt_main"
android:numColumns="3">GridView>
RelativeLayout>
初始化View:
public class MainActivity extends AppCompatActivity {
private View btMain;
private GridView gvMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
btMain = findViewById(R.id.bt_main);
gvMain = ((GridView) findViewById(R.id.gv_main));
}
}
1.2、添加点击事件打开相机:
这里也应该非常的简单和熟悉,直接上代码:
点击事件:
btMain.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
putOnCamera();//打开相机的方法
}
});
打开相机:
ACTION_IMAGE_CAPTURE //打开相机拍一次
ACTION_IMAGE_CAPTURE_SECURE
INTENT_ACTION_STILL_IMAGE_CAMERA
INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
这个intent是隐式跳转的,有四个常量,第一个大家都很熟悉,就是打开相机拍一张照片完事,所以我们不选他,那该选择哪个呢?不着急,我们一个个试一下,打个log试试先
1.2.2、ACTION_IMAGE_CAPTURE_SECURE:
/**
* 打开相机
*/
private void putOnCamera() {
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
startActivityForResult(intent, REQUEST_SMALL);
}
在onActivityResult中我们得到返回值
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_SMALL) {
Log.e("datadata", "requestCode:"+requestCode+",resultCode:"+resultCode+",data:"+data);
}
}
开机运行啦,测试机是华为小米的6.0系统,看一下效果,
嗯,不错,打开相机是这样的:
图片比较大,就贴一张看一下,和直接打开系统相机是一样的,没错,可以多次拍照,哈哈,原来真的这么简单,拍完返回看一下log如下:
04-13 16:38:10.934 28785-28785/com.exampleenen.ruedy.moreimagetwo E/datadata: requestCode:11111,resultCode:0,data:null
那好,咱们看截图:
哈哈,眼看就要完成了,最后竟然 得到一个null,所以这个”ACTION_IMAGE_CAPTURE_SECURE”模式完成不了此功能,我们再看看另外两个参数,额。。。各位看官,不用试了,小生已经试过了,结果是一样的。
所以嘛,没这么简单,这么简单,我也不会费这么大力气写篇博客了。
嗯,想想,可以的,您先想,应该有其他方法,我也想到了一个方法,您看这样可以么?前方高能,同志请带好安全帽:
自己重新写个相机,点击按钮的时候 我们打开自己的相机,在相机里面写个方法,把照片数据都返回出去?嗯,听着可以啊,你去写呗,我可不写,我的项目估计还没写个相机复杂。那好吧,那这样行么?我给两组log数据您看下:
111小米6.0系统:
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165443.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165451.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165447.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165442.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165448.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165449.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165445.jpg
222华为7.0系统:
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165655.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165652.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165641.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165647.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165643.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165645.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165650.jpg
E/setsize: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_165659.jpg
不要问是什么 ,从哪里来的,您就看熟不熟悉?
对了,是在哪里见过,在哪里呢?打开你手机拍出来的照片,打开照片信息,看一下路径。对嘛,我就说在快播,额不 , 手机照片上见过嘛。
有点意思,有点意思
可这有个卵用啊 , 你的data里面还是拿不到刚才拍的照片呀,是的,说得对奥,data还是拿不到打开相机拍得照片,可是。。。。。,这样行么大兄弟,不一定非要从data里面拿呀,咱可以从系统里面拿呀,打开相机是必须的,所以东西还得在onActivityResult里面进行,说的是轻松,从系统里面拿?怎么拿?根据什么拿?
问的好啊 , 那我来说一下我的几个思路吧
111思路一:
先看一下这个路径
/storage/emulated/0/DCIM/Camera/
手机拍的照片好像都在这个手机文件夹下面,直接读取这个文件夹下面的文件,不就能拿到了么。
是的,
不过有个问题,拍摄的照片所在的路径不一定,是的,照片的储存位置用户是可以设置、改变的,所以本着最近《人民的名义》 所教导的精神,做一名良心程序员,咱们还是把这个情况考虑进去吧,所以这个方法有缺陷,很大的缺陷。
222思路二:
安卓有个四大组件之一的内容提供者,也就是ContentProvider,这玩意很少用到吧,安卓系统把手机里面所有的照片的 路径 (记住是路径啊) 做成了一个数据库 , 我们可以通过内容提供者ContentProvider拿到所有(记住是所有)照片的路径,然后根据某些规律 , 找到 打开相机以后所拍的照片不就可以了吗 ? 是的,那么问题转化为这个规律 是什么 ,仔细看这些数据:
/storage/emulated/0/DCIM/Camera/IMG_20170413_165655.jpg
/storage/emulated/0/DCIM/Camera/IMG_20170413_165652.jpg
/storage/emulated/0/DCIM/Camera/IMG_20170413_165641.jpg
/storage/emulated/0/DCIM/Camera/IMG_20170413_165647.jpg
/storage/emulated/0/DCIM/Camera/IMG_20170413_165643.jpg
/storage/emulated/0/DCIM/Camera/IMG_20170413_165645.jpg
/storage/emulated/0/DCIM/Camera/IMG_20170413_165650.jpg
/storage/emulated/0/DCIM/Camera/IMG_20170413_165659.jpg
规律发现不了的大兄弟应该不怎么会玩游戏吧,比如我认识的一位老司机:某浒,规律精华如下:
“IMG_20170413_165655.jpg”———这个是照片文件的文件名,中间的”20170413_165655“中,“_”前面是年月日嘛,后面是时分秒嘛,哈哈,可以了,规律可以了,这些照片名是按时间组合起来的,那我们就可以根据时间,判断哪些是我们打开相机后拍的,可以伐?可以。
根据思路 我们要用到的地方我做了以下总结,真正写的时候还是很多坑的,总结如下:
- 内容提供者ContentProvider与内容接受者ContentResolver
- 数据库、多线程(这个可以忽略但是要知道,数据库 ,上一条已经把这个做好了,多线程是因为查数据库其实是耗时的,最好在子线程里面,避免ANR,咱这里就不提了)
- 路径转换出时间再转换成数字(时间格式转化,String类型转换成其他类型)
- 塞选图片路径(正则表达式)
- 根据路径path找到图片
- 把图片压缩放后放到集合里面(二次采样)
目测东西不少呢,
等等,根据路径path找到图片,是要读取手机内存卡内容的呀,所以针对6.0及以上,还要做权限申请那一块,为了减少篇幅,这一块就不细说了,大家知道就好。
那么来吧
让我自己写肯定写不出来,不怎么好记,百度了一下,如下得到图片数据库,因为是退出相机以后查询,所以要在onActivityResult里面,如下::
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_SMALL) {
final String[] projection = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA};
final String orderBy = MediaStore.Images.Media.DISPLAY_NAME;
final Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}
}
这里拿到了uri,基本就能拿到内容提供者了,go on ,由于代码比较多,还是封装起来吧;
getContentProvider(uri, projection, orderBy);
public void getContentProvider(Uri uri, String[] projection, String orderBy) {
Cursor cursor = getContentResolver().query(uri, projection, null,
null, orderBy);
if (null == cursor) {
return ;
}
while (cursor.moveToNext()) {
for (int i = 0; i < projection.length; i++) {
String string = cursor.getString(i);
if (string != null) {
Log.e("string", "getContentProvider: "+string);
}
}
}
}
OK,run一下,打个log:
数据太大,截个图看一下吧就,还是比较喜观的,这里是所有图片的路径,下面我们来赛选。
04-13 18:08:42.808 28206-28206/com.exampleenen.ruedy.moreimagetwo E/string: getContentProvider: /storage/emulated/0/DCIM/Camera/IMG_20170413_154316.jpg
“/storage/emulated/0/DCIM/Camera/IMG_20170413_154316.jpg”——这个就是我们的目标,我简化了一下,锁定方法,仁者见仁,智者见智,判断方法有很多种,我的判断代码如下, 在上面的if中加入代码:
int length = string.length();
if (length >= 30) {
//根据实际路径得到的,大一点保险
ss = string.substring(length - 23, length);//判断后23位的字符串,
String substring = ss.substring(0, 4);//大致判断一下是系统图片,后面严格塞选
String hen = ss.substring(12, 13);
if (substring.equals("IMG_") && hen.equals("_")) {
//懒得写正则了,就这样吧,可自行拓展
String laststring = ss.substring(4, 19).replace("_", "");
Log.e("string", "getContentProvider: " + string);
}
}
再打个log看一下:
对了,逮住了,就是这些,下面就是拿到路径对应时间的数学数字time(注意是long,不是int,很多人会栽跟头的),在打开相机之前根据java代码,拿到时间systemTime1;方法为:
public long getSystemTime() {
//("yyyy年MM月dd日 HH时MM分ss秒"
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
long times = System.currentTimeMillis();
System.out.println(times);
Date date = new Date(times);
String time = sdf.format(date).toString();
Log.e("timeintimet", "timeint: " + time.toString());
long timeint = 0;
try {
;
timeint = Long.valueOf(time).longValue();
} catch (Exception e) {
Log.e("exception", "getSystemTime: " + e.toString());
}
return timeint;
}
然后返回界面以后拿到时间systemTime2;再在上上面代码中加入图下判断,
try {
long time = Long.valueOf(laststring).longValue();
if (time > systemTime1 && time <= systemTime2) {
//位于两个时间之间的就是我们拍摄的图片
}
} catch (Exception e) {
Log.e("exception", "getContentProvider: " + e.toString());
}
加个try、catch保险一点。
继续run,拍照,返回,log如下:
嗯嗯,可以了,拿到了,终于拿到所谓的data了;
下面就简单了,根据路径拿到图片并且二次采样如下:
/**
* 根据路径,二次采样并且压缩
* @param filePath 路径
* @param destWidth 压缩到的宽度
* @param destHeight 压缩到的高度
* @return
*/
public Bitmap convertToBitmap(String filePath, int destWidth, int destHeight) {
//第一采样
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
int outWidth = options.outWidth;
int outHeight = options.outHeight;
int sampleSize = 1;
while ((outWidth / sampleSize > destWidth) || (outHeight / sampleSize > destHeight)) {
sampleSize *= 2;
}
//第二次采样
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeFile(filePath, options);
}
后面就可以把路径和图片放起来add到List里面就可以了,我是新建了一个实体类。
这样数据源就有了可以设置到GridView的适配器里面啦,哈哈,
工作我来做,您休息
。。。。。
。。。。。
。。。。。
搞定了,看下效果:
额,这次动图比较大,所以压缩了一下,不大好看,终于有效果了。不过 这里面还有几个问题,如果真的要搞到项目你需注意以下几点:
- 打开相机那里需要优化,各大厂商系统改的面目全非
- 权限必须打开 相机和读内存卡文件权限都得打开, 这个必须
- 按照上面代码,每张图片会得到两次,所以去重一下,我用Hashset去重的(实践而来)
- 如果用户打开手机刷刷刷,每一秒牌好几张,那一秒里面拍的照片 这里的代码这个是得不到的,因为这种情况到秒的时间都是一样的,多出来了毫秒加在了秒后面,如果想得到,请自己在塞选那里自行改善,不改善的话,就提醒用户慢点拍,因为一秒钟拍那么多,肯定是一个视角拍出来的,何必呢
- 真的要整合到项目里的话,这里还有个漏洞,比如拍完照片,用户,按Home键,然后改变时间,然后进我们的应用再按返回,应该不会有人这么做的,这里可以提醒一下用户,请拍完照片直接按返回,避免失败。
以上是自己的踩得坑,测试手机有限,问题肯定是有的,代码已经总结、全部上传github,点击查看 。
很多刚入手安卓的朋友,面对一个功能、需求的时候,有时候很迷茫,无从下手,其实我们一定要善于思考,知其然,知其所以然,才能把学到的知识糅合到一起,用哪里的时候知道去哪找,不用高端的东西,咱也能实现一些意想不到的功能。本篇文章的需求,应该还有其他方法,有兴趣的朋友可以探索一下。 有问题欢迎指正,一起进步,谢谢各位!
安卓交流群:661614986