简单粗暴,先看看是不是你想要的效果.(转成gif时,有点失真,凑合着看吧)
Demo工程结构:
1.权限处理(针对android 6.0动态权限的申请)
2.同步相册中的图片(并以列表的形式显示)
3.ViewPager用于展示点击的图片,并能左右滑动查看
4.点击产看图片的缩放功能(重点)
友情提示:如果你想直接查看imagezoom实现图片缩放的逻辑,可直接跳转到第4步.
1.权限处理(针对android 6.0动态权限的申请)
由于demo中涉及到读取手机相册的部分,因此针对android 6.0以上设备的运行时权限获取是必须的.
先演示一下小米(Android 7.0)手机上的权限获取的效果图
动图中着重演示的是,拒绝权限申请(包括勾选了"不再提示")的效果.照着代码中的权限申请步骤来,肯定没问题
Demo中android 6.0运行时权限的申请步骤,在代码中我已经做了详细的注释,如下:
直接贴MainActivity中的代码:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private static final int MY_PERMISSION_REQUEST_CODE = 0x01; private ArrayList<PicBean> list = new ArrayList<>(); private MyAdapter myAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initRecyclerView(); } private void initRecyclerView() { Button getAlbum = findViewById(R.id.get_album); getAlbum.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /** * 第 1 步: 检查是否有相应的权限 */ boolean isAllGranted = checkPermissionAllGranted( new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE } ); // 如果这3个权限全都拥有, 则直接执行同步相册代码 if (isAllGranted) { syncAlum(); return; } /** * 第 2 步: 请求权限 */ ActivityCompat.requestPermissions(// 一次请求多个权限, 如果其他有权限是已经授予的将会自动忽略掉 MainActivity.this, new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, MY_PERMISSION_REQUEST_CODE ); } }); RecyclerView mRecyclerView = findViewById(R.id.recycler); GridLayoutManager layoutManager = new GridLayoutManager(this, 3, LinearLayoutManager.VERTICAL, false); mRecyclerView.setLayoutManager(layoutManager); mRecyclerView.setHasFixedSize(true); //Ctrl+Alt+F local value--->field myAdapter = new MyAdapter(this, list); mRecyclerView.setAdapter(myAdapter); myAdapter.setmItemClickListener(new MyAdapter.OnItemClickListener() { @Override public Void onItemClick(int position) { Intent intent = new Intent(MainActivity.this, ImagePagerActivity.class); Bundle bundle = new Bundle(); bundle.putParcelableArrayList(ImagePagerActivity.EXTRA_IMAGE_LIST, list); bundle.putInt(ImagePagerActivity.EXTRA_IMAGE_INDEX, position); intent.putExtras(bundle); startActivity(intent); return null; } }); } //同步相册 private void syncAlum() { Log.d(TAG, "syncAlum:"); Uri mUri = Uri.parse("content://media/external/images/media"); Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null); if (!list.isEmpty()) { list.clear(); } assert cursor != null; while (cursor.moveToNext()) { //获取图片的名称 String name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)); //获取图片的详细信息 String desc = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DESCRIPTION)); //获取图片路径 String imagePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); //获取图片url int ringtoneID = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID)); String imageUrl = Uri.withAppendedPath(mUri, "" + ringtoneID).toString(); PicBean picBean = new PicBean(name, desc, imagePath, imageUrl); Log.d(TAG, "syncAlum: picBean::" + picBean); list.add(picBean); } myAdapter.notifyDataSetChanged(); } @Override protected void onDestroy() { super.onDestroy(); if (!list.isEmpty()) { list.clear(); } } //*********************************************************** /** * 检查是否拥有指定的所有权限 */ private boolean checkPermissionAllGranted(String[] permissions) { for (String permission : permissions) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { // 只要有一个权限没有被授予, 则直接返回 false return false; } } return true; } /** * 第 3 步: 申请权限结果返回处理 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == MY_PERMISSION_REQUEST_CODE) { Log.w(TAG, " onRequestPermissionsResult permission granted."); boolean isAllGranted = true; // 判断是否所有的权限都已经授予了 for (int grant : grantResults) { if (grant != PackageManager.PERMISSION_GRANTED) { isAllGranted = false; break; } } if (isAllGranted) { // 如果所有的权限都授予了, 则执行备份代码 syncAlum(); } else { // 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮 //点击拒绝(没有勾选"不在询问")后,会直接跳转至设置引导界面 //而如果采取了注释来实现的权限授予的话,则只有在点击拒绝(勾选了"不再询问")后,才会跳转至设置引导界面 openAppDetails(); } } } /** * 打开 APP 的详情设置 */ private void openAppDetails() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("同步相册需要 读写权限,请到'设置'中授予权限."); builder.setPositiveButton("去手动授权", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse("package:" + getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); } }); builder.setNegativeButton("取消", null); builder.show(); } }
需要注意的是,当我们勾选了"不再提示"后,需要引导用户去系统设置中去手动获取相关所需权限.
代码单独在贴出来吧:
/** * 打开 APP 的详情设置 */ private void openAppDetails() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("同步相册需要 读写权限,请到'设置'中授予权限."); builder.setPositiveButton("去手动授权", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse("package:" + getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); } }); builder.setNegativeButton("取消", null); builder.show(); }
顺便吐槽下,国内android 阵营的app下载之后各种权限请求,哪怕是一些根本跟app无关的权限,如果不授予的话,呵呵,用个鸡毛...
不过我想说的是,动图中展示的效果有瑕疵,那就是当我第一次点击拒绝(没有勾选"不再提示")之后,直接及跳转到"引导用户去设置界面",其实这样体验不好..按照正常的权限申请流程的话,此时应该是等勾选了不再提示并且点击了拒绝的话,才会至"引导去系统设置"界面.因此本着精益求精的态度,我更建议大家使用"注解"的方式来进行6.0运行时权限的获取(效果不错,可以很好的解决demo中权限申请的缺点),目前用得比较多的动态权限第三方库PermissionsDispatcher,使用教程也很简单,详情参考:PermissionsDispatcher,Android 6.0 运行时权限
2.同步相册中的图片(并以列表的形式显示)
相册同步并展示的逻辑MainActivity中已经展示出来了,就不再重复展示而了.我着重展示下适配器MyAdapter部分代码:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private static final String TAG = "MyAdapter"; private ArrayList<PicBean> list; private Context context; private final BitmapFactory.Options options; MyAdapter(Context context, ArrayList<PicBean> list) { this.context = context; this.list = list; options = new BitmapFactory.Options(); options.inSampleSize = 2;// 图片宽高都为原来的2分之一,即图片为原来的4分之一 } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = View.inflate(parent.getContext(), R.layout.item_recyclerview, null); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { PicBean picBean = list.get(position); Log.d(TAG, "onBindViewHolder: picBean::" + picBean); /** * 展示获取的相册图片有一下两种方式 */ //1.对bitmap进行缩放处理后再展示(不推荐:显示的图片失真,且列表滑动不顺畅) // Bitmap bitmap = BitmapFactory.decodeFile(picBean.getPicPath(), options); // Log.d(TAG, "onBindViewHolder: bitmap::" + bitmap); // if (bitmap != null) { // // 对原位图进行缩放 // Bitmap scaledBitmap = Bitmap.createScaledBitmap( // bitmap, 165, 165, true); // holder.ivImage.setImageBitmap(scaledBitmap); // holder.itemView.setTag(position); // } //2.通过url加载图片(推荐) Glide.with(context).load(picBean.getPicUrl()) .placeholder(R.drawable.icon_default_thumb).dontAnimate().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).into(holder.ivImage); holder.itemView.setTag(position); } @Override public long getItemId(int position) { return position; } @Override public int getItemCount() { return list.size(); } class ViewHolder extends RecyclerView.ViewHolder { private ImageView ivImage; ViewHolder(final View itemView) { super(itemView); ivImage = itemView.findViewById(R.id.iv_image); ivImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mItemClickListener.onItemClick((int) itemView.getTag()); } }); } } //定义回调接口 private OnItemClickListener mItemClickListener; public void setmItemClickListener(OnItemClickListener mItemClickListener) { this.mItemClickListener = mItemClickListener; } interface OnItemClickListener { Void onItemClick(int position); } }
要知道,手机相册中的图片一般都是几M左右大小,如果直接以列表的形式展示的话,在列表上下滚动时,很容易OOM,
因此一般我们需要对读取到的图片做些处理,再展示.代码中已经贴出了图片处理(显示)的两种方式,以及推荐和不推荐使用的理由.
另外,Glide是一个很优秀的图片加载库,包括手动定义是否跳过缓存,以及对图片加载失败的默认图片的显示等等.
引入也很简单,在build.gradle添加如下:
implementation 'com.github.bumptech.glide:glide:3.8.0'
值得一提的是,引入glide的时候,如果使用较高版本的glide库,程序运行可能会报错,具体原因,我稍后会研究,只是提醒大家,如果遇到这种情况,降低glide的版本即可.
3.ViewPager用于展示点击的图片,并能左右滑动查看
这个没啥技术含量,无非就是ViewPager的基本使用,直接贴代码(方便需要用的同学直接copy).
public class ImagePagerActivity extends AppCompatActivity { private static final String TAG = "ImagePagerActivity"; public static final String EXTRA_IMAGE_INDEX = "image_index"; public static final String EXTRA_IMAGE_LIST = "image_list"; private List<View> mInflateView = new ArrayList<>(); private List<ImageViewTouch> imageVtList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_image_pager); int currentPosition = getIntent().getIntExtra(EXTRA_IMAGE_INDEX, 0); ArrayList<PicBean> list = getIntent().getParcelableArrayListExtra(EXTRA_IMAGE_LIST); initViewPager(list, currentPosition); } private void initViewPager(final ArrayList<PicBean> list, int currentPosition) { Log.d(TAG, "currentPosition:" + currentPosition + " list:" + list); for (int i = 0; i < list.size(); i++) { View view = LayoutInflater.from(this).inflate(R.layout.item_chat_pager_image, null); ImageViewTouch image = view.findViewById(R.id.image_view); TextView picPath = view.findViewById(R.id.pic_path); mInflateView.add(view); imageVtList.add(image); PicBean picBean = list.get(currentPosition); Glide.with(ImagePagerActivity.this).load(picBean.getPicUrl()) .placeholder(R.drawable.icon_default_thumb).dontAnimate().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).into(image); picPath.setText(picBean.getPicPath()); } ViewPager viewPager = findViewById(R.id.view_pager); ViewpagerAdapter adapter = new ViewpagerAdapter(mInflateView); viewPager.setAdapter(adapter); viewPager.setCurrentItem(currentPosition); viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { Log.w(TAG, "onPageSelected:position::" + position); Glide.with(ImagePagerActivity.this).load(list.get(position).getPicUrl()) .placeholder(R.drawable.icon_default_thumb).dontAnimate().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).into(imageVtList.get(position)); } @Override public void onPageScrollStateChanged(int state) { } }); } @Override protected void onDestroy() { super.onDestroy(); mInflateView = null; mInflateView = null; } }
ViewPager适配器代码:
public class ViewpagerAdapter extends PagerAdapter { private List<View> viewContainter; public ViewpagerAdapter(List<View> mViewList) { this.viewContainter = mViewList; } @Override public int getCount() { //必须实现 return viewContainter.size(); } @Override public boolean isViewFromObject(View view, Object object) {//必须实现 return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { //必须实现,实例化 container.addView(viewContainter.get(position)); return viewContainter.get(position); } @Override public void destroyItem(ViewGroup container, int position, Object object) { //必须实现,销毁 container.removeView(viewContainter.get(position)); } @Override public int getItemPosition(Object object) { return super.getItemPosition(object); } }ViewPager中单项布局文件item_chat_pager_image.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="30dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="20dp" android:text="图片路径:" /> <TextView android:id="@+id/pic_path" android:layout_width="match_parent" android:layout_height="20dp" /> </LinearLayout> <it.sephiroth.android.library.imagezoom.ImageViewTouch android:id="@+id/image_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" /> </LinearLayout>
4.点击产看图片的缩放功能(重点)
(1).imagezoom库的引用:
implementation 'it.sephiroth.android.library.imagezoom:imagezoom:2.3.0'
(2)使用
其实,代码第3步中的xml布局文件中已经贴出来了.
<it.sephiroth.android.library.imagezoom.ImageViewTouch android:id="@+id/image_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" />
怎么样,没骗你吧,就是当imageView用,用法一模一样,只不过ImageView不能直接进行缩放操作而已.
OK,收尾,有啥问题就留言吧,相互学习进步才是正道。
附上demo下载链接:
https://download.csdn.net/download/zhangqunshuai/10423022