最简单的实现图片的放大和缩小(就像操作ImageView一样)
简单粗暴,先看看是不是你想要的效果.(转成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
上一篇: 支付宝转错帐怎么办 5大方法帮你追回