该demo针对不同机型和不同API进行功能实现,满足大部分手机拍照和相册选取照片功能实现。
1使用知乎相册框架compile 'com.zhihu.android:matisse:0.4.3'进行图库相册选取
2使用compile 'com.github.bumptech.glide:glide:3.7.0'框架(Glide)进行图片加载
如下效果
1点击选择图片按钮弹出popuwindow
2拍照:
3相册选取:
4选择的结果:
SelectPicPopup的代码:
package study.lzl.photoselectdemo;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.PopupWindow;
/**
* @author: Created by lzl on 2017/8/14.
* @function:
* @description:
*/
public class SelectPicPopup implements View.OnClickListener {
/**
* UI
*/
private PopupWindow mPopupWindow;
private Button mCameraBtn;//相册
private Button mPicBtn;//图库
private Button mCancelBtn;//取消
private Context mContext;//上下文环境对象
private OnBtnClickListener click;
public PopupWindow getmPopupWindow() {
return mPopupWindow;
}
/**
* 单参构造
* @param mContext
*/
public SelectPicPopup(Context mContext) {
this.mContext = mContext;
mPopupWindow=new PopupWindow(mContext);
mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
mPopupWindow.setWidth(WindowManager.LayoutParams.MATCH_PARENT);//宽度充满父窗体
mPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);//高度包裹内容
mPopupWindow.setTouchable(true);//设置可以被触摸
mPopupWindow.setFocusable(true);//设置占据焦点
mPopupWindow.setOutsideTouchable(true);//设置可以点击外部区域(点击外部区域将消失)
mPopupWindow.setContentView(initView());
mPopupWindow.setAnimationStyle(R.style.AnimBottom);//设置转场动画
mPopupWindow.getContentView().setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mPopupWindow.setFocusable(false);
dismiss();
return false;
}
});
}
private View initView() {
View view= LayoutInflater.from(mContext).inflate(R.layout.layout_popup,null);
mCameraBtn= (Button) view.findViewById(R.id.camera_btn);
mCameraBtn.setOnClickListener(this);
mPicBtn= (Button) view.findViewById(R.id.pic_btn);
mPicBtn.setOnClickListener(this);
mCancelBtn= (Button) view.findViewById(R.id.cancel_btn);
mCancelBtn.setOnClickListener(this);
return view;
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.camera_btn:
click.getFlag(0);
break;
case R.id.pic_btn:
click.getFlag(1);
break;
case R.id.cancel_btn:
dismiss();
break;
}
}
protected void dismiss(){
if (mPopupWindow!=null&&mPopupWindow.isShowing()){
mPopupWindow.dismiss();
}
}
/**
*
* @param rootView
*/
protected void showPopup(View rootView){
mPopupWindow.showAtLocation(rootView, Gravity.BOTTOM,0,0);
}
protected interface OnBtnClickListener{
void getFlag(int flag);
}
protected void setOnBtnClick(OnBtnClickListener click){
this.click=click;
}
}
自定义的popuwindow使用的style如下:
<!--popuwindow style-->
<style name="AnimBottom" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/popup_alpha_out</item>
<item name="android:windowExitAnimation">@anim/popup_alpha_in</item>
</style>
引用的两个anim文件:
1anim/popup_alpha_out:
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromAlpha="0.0"
android:toAlpha="1.0"
>
</alpha>
2anim/popup_alpha_in:
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromAlpha="1.0"
android:toAlpha="0.0"
>
</alpha>
布局文件:layout_popup.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/half_transparent_black">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentBottom="true"
android:background="@color/page_bg_color">
<Button
android:id="@+id/camera_btn"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#ffffff"
android:text="拍照"
android:textColor="#000"
/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#65000000"/>
<Button
android:id="@+id/pic_btn"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="相册"
android:textColor="#000"
android:background="#ffffff"/>
<Button
android:id="@+id/cancel_btn"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="10dp"
android:background="#ffffffff"
android:textColor="#000"
android:text="取消"/>
</LinearLayout>
</RelativeLayout>
MainActivity的代码:
package study.lzl.photoselectdemo;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.zhihu.matisse.Matisse;
import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.engine.impl.GlideEngine;
import java.io.File;
import java.util.List;
/**
* 演示图片选择来源,并将图片正确显示在控件上,该控件可以对图片进行手势操作:包括大小缩放和平移拖拽
*/
public class MainActivity extends AppCompatActivity {
private ImageView imageV;
private Uri imageUrl;//照片地址uri对象
private int destType = FileHelper.JPEG;
private static final int REQUEST_CODE_TAKE_PIC_CAMERA = 100;//拍照请求码
private static final int REQUEST_CODE_CHOOSE = 233;//图库选取照片请求码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
imageV= (ImageView) findViewById(R.id.image);
findViewById(R.id.select_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final SelectPicPopup popup=new SelectPicPopup(getApplicationContext());
popup.showPopup(findViewById(R.id.activity_main));
popup.setOnBtnClick(new SelectPicPopup.OnBtnClickListener() {
@Override
public void getFlag(int flag) {
if (flag==1){//相册
popup.dismiss();
selectPicFromGallery();
}else if (flag==0){//拍照
popup.dismiss();
if (hasPermission(PerMissionContants.HARDWEAR_CAMERA_PERMISSION)){
openCamera();
}else {
requestPermission(PerMissionContants.HARDWEAR_CAMERA_CODE,PerMissionContants.HARDWEAR_CAMERA_PERMISSION);
Toast.makeText(getApplicationContext(),"权限:"+hasPermission(PerMissionContants.HARDWEAR_CAMERA_PERMISSION),Toast.LENGTH_SHORT).show();
}
}
}
});
}
});
}
/**
* 打开相册进行拍照
*/
private void openCamera() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//创建一个临时文件夹存储拍摄的照片
File file = FileHelper.createFileByType(getApplicationContext(), destType, System.currentTimeMillis()+"");
imageUrl=Uri.fromFile(file);//解析出uri对象
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//版本过高的拍照路径
Log.e("进入1","进入1");
file = new File(getApplicationContext().getCacheDir(), System.currentTimeMillis()+".jpg");
imageUrl = Uri.fromFile(file);
Toast.makeText(getApplicationContext(), "TODO", Toast.LENGTH_SHORT).show();
// 将文件转换成content://Uri的形式
Uri contentUri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".provider", file);
// 申请临时访问权限
takePictureIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
} else {
Log.e("进入2","进入2");
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUrl);
}
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
Log.e("进入3","进入3");
startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PIC_CAMERA);
}
}
/**
* 打开相册选取照片
*/
private void selectPicFromGallery() {
Matisse.from(MainActivity.this)
.choose(MimeType.allOf())
.countable(true)
.spanCount(4)
.maxSelectable(1)
.restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
.thumbnailScale(0.85f)
.imageEngine(new GlideEngine())
.forResult(REQUEST_CODE_CHOOSE);
}
/**
* 发起权限请求
*/
private void requestPermission(int code,String...permissions){
if (Build.VERSION.SDK_INT>=23){//判断是否大于6.0版本(当前手机的安卓系统是否是6.0或以上)
requestPermissions(permissions,code);
}
}
/**
* 校验权限是否通过(安卓6.0以上的动态权限需要调用该方法)
* @param permissions
* @return
*/
private boolean hasPermission(String...permissions){
for (String permission:permissions) {
if (ContextCompat.checkSelfPermission(getApplicationContext(),permission)!= PackageManager.PERMISSION_GRANTED){
return false;
}
}
return true;
}
/**
* 权限请求回调结果处理
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode==PerMissionContants.HARDWEAR_CAMERA_CODE){//拍照权限
if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
Toast.makeText(getApplicationContext(),"权限请求结果",Toast.LENGTH_SHORT).show();
openCamera();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode==REQUEST_CODE_TAKE_PIC_CAMERA&&resultCode==RESULT_OK){//拍照请求回调成功
if (imageUrl!=null){
//根据uri获取图片的真实路径
String path=getRealFilePath(getApplicationContext(),imageUrl);
Bitmap bitmap=BitmapFactory.decodeFile(path);//根据图片的路径字符串解析成bitmap
imageV.setImageBitmap(bitmap);
}
}else if (requestCode==REQUEST_CODE_CHOOSE&&resultCode==RESULT_OK){
List<Uri> uriList=Matisse.obtainResult(data);
if (uriList.size()==1){
String path=getRealFilePath(getApplicationContext(),uriList.get(0));
Bitmap bitmap=BitmapFactory.decodeFile(path);//根据图片的路径字符串解析成bitmap
imageV.setImageBitmap(bitmap);
}
}
}
/**
* 该方法根据图片uri得出其真实路径
* @param context 上下文
* @param uri 图片对应的uri对象
* @return 图片的真实路径字符串
*/
public String getRealFilePath(final Context context, final Uri uri) {
if (null == uri) return null;
final String scheme = uri.getScheme();
String data = null;
if (scheme == null)
data = uri.getPath();
else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
data = uri.getPath();
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
if (null != cursor) {
if (cursor.moveToFirst()) {
int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
if (index > -1) {
data = cursor.getString(index);
}
}
cursor.close();
}
}
return data;
}
}
布局文件:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="study.lzl.photoselectdemo.MainActivity">
<Button
android:id="@+id/select_btn"
android:background="@color/colorAccent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择照片"
android:textColor="@color/colorPrimary"/>
<ImageView
android:id="@+id/image"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"/>
</RelativeLayout>
动态权限常量类:PerMissionContants
package study.lzl.photoselectdemo;
import android.Manifest;
/**
* @author: Created by lzl on 2017/8/15.
* @function:
* @description:
*/
public class PerMissionContants {
/**
* 拍照权限码
*/
public static final int HARDWEAR_CAMERA_CODE=0x01;
/**
* 拍照用到的权限字符串数组:1打开数组的权限 2文件的写入权限(拍照要缓存) 3所拍照片的获取(从本地缓存或者相册中获取)
*/
public static final String[] HARDWEAR_CAMERA_PERMISSION=new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE};
}
文件操作助手类:FileHelper
package study.lzl.photoselectdemo;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by rookie on 2017/2/28.
*/
public class FileHelper {
public static final int JPEG = 1;
static final int PNG = 0;
/**
* 返回系统缓存路径
*
* @param mContext
* @return
*/
public static String getTempDirectoryPath(Context mContext) {
File cachePath;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
cachePath = mContext.getExternalCacheDir();
} else {
cachePath = mContext.getCacheDir();
}
cachePath.mkdirs();
return cachePath.getAbsolutePath();
}
/**
* 在系统缓存中创建一个文件
*
* @param mContext
* @param type
* @param name
* @return
*/
public static File createFileByType(Context mContext, int type, String name) {
if (TextUtils.isEmpty(name)) {
name = ".pic";
}
switch (type) {
case JPEG:
name = name + ".jpg";
break;
case PNG:
name = name + ".png";
break;
default:
break;
}
return new File(getTempDirectoryPath(mContext), name);
}
/**
* Removes the "file://" prefix from the given URI string, if applicable.
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
*
* @param uriString the URI string to operate on
* @return a path without the "file://" prefix
*/
public static String stripFileProtocol(String uriString) {
if (uriString.startsWith("file://")) {
uriString = uriString.substring(7);
}
return uriString;
}
public static String getPicutresPath(int encodingType) {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "IMG_" + timeStamp + (encodingType == JPEG ? ".jpg" : ".png");
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM);
String galleryPath = storageDir.getAbsolutePath() + File.separator + "Camera" + File.separator + imageFileName;
return galleryPath;
}
public static boolean copyResultToGalley(Context context, Uri originUri, Uri galleryUri) {
boolean result = false;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = context.getContentResolver().openInputStream(originUri);
outputStream = context.getContentResolver().openOutputStream(galleryUri);
byte[] buffer = new byte[2048];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
outputStream.flush();
result = true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
}
***
注意事项:
1在app.gradle配置文件中配置使用到的两个框架:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
testCompile 'junit:junit:4.12'
//项目中需要用到的第三方框架
compile 'com.zhihu.android:matisse:0.4.3'
compile 'com.github.bumptech.glide:glide:3.7.0'
}
2安卓6.0以上版本使用的是动态权限管理:因此增加一个类进行权限的申请处理:PerMissionContants