最简单的实现图片的放大和缩小(就像操作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



猜你喜欢

转载自blog.csdn.net/zhangqunshuai/article/details/80360643