欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

最简单的实现图片的放大和缩小(就像操作ImageView一样)

程序员文章站 2022-04-13 12:03:36
...

简单粗暴,先看看是不是你想要的效果.(转成gif时,有点失真,凑合着看吧)

最简单的实现图片的放大和缩小(就像操作ImageView一样)

Demo工程结构:

1.权限处理(针对android 6.0动态权限的申请)

2.同步相册中的图片(并以列表的形式显示)

3.ViewPager用于展示点击的图片,并能左右滑动查看

4.点击产看图片的缩放功能(重点)

友情提示:如果你想直接查看imagezoom实现图片缩放的逻辑,可直接跳转到第4步

1.权限处理(针对android 6.0动态权限的申请)

由于demo中涉及到读取手机相册的部分,因此针对android 6.0以上设备的运行时权限获取是必须的.

先演示一下小米(Android 7.0)手机上的权限获取的效果图

最简单的实现图片的放大和缩小(就像操作ImageView一样)

动图中着重演示的是,拒绝权限申请(包括勾选了"不再提示")的效果.照着代码中的权限申请步骤来,肯定没问题

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'

最简单的实现图片的放大和缩小(就像操作ImageView一样)

值得一提的是,引入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'

最简单的实现图片的放大和缩小(就像操作ImageView一样)

(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



相关标签: android开发