Android实现本地图片选择及预览缩放效果仿春雨医生

Posted lylodlig

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android实现本地图片选择及预览缩放效果仿春雨医生相关的知识,希望对你有一定的参考价值。

在做项目时经常会遇到选择本地图片的需求,以前都是懒得写直接调用系统方法来选择图片,但是这样并不能实现多选效果,最近又遇到了,所以还是写一个demo好了,以后也方便使用。还是首先来看看效果


显示的图片使用RecyclerView实现的,利用Glide来加载;下面弹出的图片文件夹效果是采用PopupWindow实现,这里比采用PopupWindow更方便,弹出显示的左边图片是这个文件夹里的第一张图片;选中的图片可以进行预览,使用网上一个大神写的来实现的;至于图片的获取是用ContentProvider。


看看主界面的布局文件,上面一栏是一个返回按钮和一个跳转预览界面的按钮,根据是否有选中的图片来设置它的点击和显示状态;中间就是一个用于显示图片的RecyclerView,左下角是显示文件夹的名字可点击切换,右下角就是确定按钮。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.cdxsc.imageselect_y.ImageSelecteActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:background="@android:color/white">

        <ImageButton
            android:id="@+id/ib_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:background="@mipmap/action_bar_back_normal" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@id/ib_back"
            android:text="选择图片"
            android:textColor="#000"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/tv_preview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="10dp"
            android:enabled="false"
            android:text="预览"
            android:textColor="#BEBFBF"
            android:textSize="16sp" />
    </RelativeLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#eeeeee" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"></android.support.v7.widget.RecyclerView>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp">

        <TextView
            android:id="@+id/tv_allPic"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:clickable="true"
            android:gravity="center_vertical"
            android:text="所有图片"
            android:textColor="@android:color/black"
            android:textSize="16sp" />

        <Button
            android:id="@+id/bt_confirm"
            android:layout_width="wrap_content"
            android:layout_height="35dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="10dp"
            android:background="@drawable/shape_disable"
            android:enabled="false"
            android:text="确定"
            android:textColor="#676767"
            android:textSize="16sp" />
    </RelativeLayout>
</LinearLayout>


好了,现在看主界面的代码

public class ImageSelecteActivity extends AppCompatActivity {

    private static final String TAG = "lzy";
    @BindView(R.id.ib_back)
    ImageButton mButtonBack;
    @BindView(R.id.tv_preview)
    TextView mTextViewPreview;
    @BindView(R.id.rv)
    RecyclerView mRecyclerView;
    @BindView(R.id.tv_allPic)
    TextView mTextViewAllPic;
    @BindView(R.id.bt_confirm)
    Button mButtonConfirm;
    private GalleryPopupWindow mPopupWindow;
    //存储每个目录下的图片路径,key是文件名
    private Map<String, List<String>> mGroupMap = new HashMap<>();
    private List<ImageBean> list = new ArrayList<>();
    //当前文件夹显示的图片路径
    private List<String> listPath = new ArrayList<>();
    //所选择的图片路径集合
    private ArrayList<String> listSelectedPath = new ArrayList<>();


    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //扫描完成后
            getGalleryList();
            listPath.clear();
            listPath.addAll(mGroupMap.get("所有图片"));
            adapter.update(listPath);
            if (mPopupWindow != null)
                mPopupWindow.notifyDataChanged();
        }
    };
    private ImageSelectAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_selecte);
        ButterKnife.bind(this);
        init();
    }

    private void init() {
        getImages();
        mRecyclerView.setLayoutManager(new GridLayoutManager(ImageSelecteActivity.this, 3));
        adapter = new ImageSelectAdapter(this, listPath);
        mRecyclerView.setAdapter(adapter);
        adapter.setOnCheckedChangedListener(onCheckedChangedListener);
    }

    @OnClick({R.id.ib_back, R.id.tv_preview, R.id.tv_allPic, R.id.bt_confirm})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.ib_back:
                finish();
                break;
            case R.id.tv_preview://跳转预览界面
                Intent intent = new Intent(ImageSelecteActivity.this, ImagePreviewActivity.class);
                //把选中的图片集合传入预览界面
                intent.putStringArrayListExtra("pic", listSelectedPath);
                startActivity(intent);
                break;
            case R.id.tv_allPic://选择图片文件夹
                if (mPopupWindow == null) {
                    //把文件夹列表的集合传入显示
                    mPopupWindow = new GalleryPopupWindow(this, list);
                    mPopupWindow.setOnItemClickListener(new GalleryPopupWindow.OnItemClickListener() {
                        @Override
                        public void onItemClick(String fileName) {
                            //切换了文件夹,清除之前的选择的信息
                            setButtonDisable();
                            listPath.clear();
                            listSelectedPath.clear();
                            //把当前选择的文件夹内图片的路径放入listPath,更新界面
                            listPath.addAll(mGroupMap.get(fileName));
                            adapter.update(listPath);
                            mTextViewAllPic.setText(fileName);
                        }
                    });
                }
                mPopupWindow.showAtLocation(mRecyclerView, Gravity.BOTTOM, 0, dp2px(50, ImageSelecteActivity.this));
                break;
            case R.id.bt_confirm://确定
                for (int i = 0; i < listSelectedPath.size(); i++) {
                    //这里可通过Glide把它转为Bitmap
                    Glide.with(this).load("file://" + listSelectedPath.get(i)).asBitmap().into(new SimpleTarget<Bitmap>() {
                        @Override
                        public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
                            Log.i(TAG, "onResourceReady: " + resource);
                        }
                    });
                }
                break;
        }
    }

    /**
     * dp转px
     */
    public static int dp2px(int dp, Context context) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                context.getResources().getDisplayMetrics());
    }

    //选择图片变化的监听
    private ImageSelectAdapter.OnCheckedChangedListener onCheckedChangedListener = new ImageSelectAdapter.OnCheckedChangedListener() {
        @Override
        public void onChanged(boolean isChecked, String path, CheckBox cb, int position) {
            if (isChecked) {//选中
                if (listSelectedPath.size() == 9) {
                    Toast.makeText(ImageSelecteActivity.this, "最多选择9张图片", Toast.LENGTH_SHORT).show();
                    //把点击变为checked的图片变为没有checked
                    cb.setChecked(false);
                    adapter.setCheckedBoxFalse(position);
                    return;
                }
                //选中的图片路径加入集合
                listSelectedPath.add(path);

            } else {//取消选中
                //从集合中移除
                if (listSelectedPath.contains(path))
                    listSelectedPath.remove(path);
            }
            //如果没有选中的按钮不可点击
            if (listSelectedPath.size() == 0) {
                setButtonDisable();
            } else {
                setButtonEnable();
            }
        }
    };

    //选中图片时的按钮状态
    private void setButtonEnable() {
        mButtonConfirm.setBackgroundResource(R.drawable.selector_bt);
        mButtonConfirm.setTextColor(Color.parseColor("#ffffff"));
        mButtonConfirm.setEnabled(true);
        mTextViewPreview.setEnabled(true);
        mTextViewPreview.setTextColor(getResources().getColor(R.color.colorAccent));
        mButtonConfirm.setText("确定" + listSelectedPath.size() + "/9");
    }

    //没有选择时按钮状态
    private void setButtonDisable() {
        mButtonConfirm.setBackgroundResource(R.drawable.shape_disable);
        mButtonConfirm.setTextColor(Color.parseColor("#676767"));
        mButtonConfirm.setEnabled(false);
        mTextViewPreview.setEnabled(false);
        mTextViewPreview.setTextColor(Color.parseColor("#BEBFBF"));
        mButtonConfirm.setText("确定");
    }

    /**
     * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中
     */
    private void getImages() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                ContentResolver mContentResolver = ImageSelecteActivity.this.getContentResolver();
                //只查询jpeg和png的图片
//                Cursor mCursor = mContentResolver.query(mImageUri, null,
//                        MediaStore.Images.Media.MIME_TYPE + "=? or "
//                                + MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",
//                        new String[]{"image/jpeg", "image/png", "image/jpg"}, MediaStore.Images.Media.DATE_MODIFIED);
                Cursor mCursor = mContentResolver.query(mImageUri, null, null, null,
                        MediaStore.Images.Media.DATE_MODIFIED);
                if (mCursor == null) {
                    return;
                }
                //存放所有图片的路径
                List<String> listAllPic = new ArrayList<String>();
                while (mCursor.moveToNext()) {
                    //获取图片的路径
                    String path = mCursor.getString(mCursor
                            .getColumnIndex(MediaStore.Images.Media.DATA));

                    //获取该图片的父路径名
                    String parentName = new File(path).getParentFile().getName();
                    listAllPic.add(path);

                    //根据父路径名将图片放入到mGruopMap中
                    if (!mGroupMap.containsKey(parentName)) {
                        List<String> chileList = new ArrayList<String>();
                        chileList.add(path);
                        mGroupMap.put(parentName, chileList);
                    } else {
                        mGroupMap.get(parentName).add(path);
                    }
                }
                //添加所有图片
                mGroupMap.put("所有图片", listAllPic);
                //通知Handler扫描图片完成
                mHandler.sendEmptyMessage(0);
                mCursor.close();
            }
        }).start();

    }

    //获取相册文件夹列表
    private void getGalleryList() {
        Iterator<Map.Entry<String, List<String>>> iterator = mGroupMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, List<String>> next = iterator.next();
            ImageBean imageBean = new ImageBean();
            imageBean.setFileName(next.getKey());
            imageBean.setFirstPicPath(next.getValue().get(0));
            imageBean.setCount(next.getValue().size());
            if (next.getKey().equals("所有图片"))
                list.add(0, imageBean);
            else
                list.add(imageBean);
        }
    }
}

·mGroupMap:这个是以文件夹名为key,文件夹内的图片路径集合为value,也就是按照文件夹来分别存储了所有图片的路径。

·listPath:保存的是当前显示在界面上的文件夹内的图片路径集合

·listSelectedPath:保存用户选中的图片路径

·list:保存的是ImageBean的集合,ImageBean保存了文件夹名、里面首张图片的路径以及里面所包含图片的数量,当切换文件夹时用于显示

·getImages():这个方法就是用来扫描手机里图片并保存的,这是在子线程中执行的,显示这可能是一个耗时的任务。通过ContentProvider获取到一个包含所有图片的Cursor,然后遍历这个Cursor把所需的数据就保存在mGroupMap里面,最后利用Handler通知界面更新。
·getGalleryList():这个方法就是mGroupMap里面的数据来给list赋值,也就是产生一个现实文件夹列表所需的数据集合。

·GalleryPopupWindow也就是我们用于显示文件列表的,在67--84行就是一些GalleryPopupWindow的设置,调用showAtLocation方法把PopupWindow显示在距离底部50dp的位置,并设置了点击的回调,当切换了一个文件夹后要做的相关操作就在这里进行。GalleryPopupWindow再待会再具体看看


接下来再看看中间RecyclerView的Adapter

public class ImageSelectAdapter extends RecyclerView.Adapter<ImageSelectAdapter.NViewHolder> {

    private Context context;
    private List<String> list = new ArrayList<>();
    private OnCheckedChangedListener onCheckedChangedListener;
    private List<Boolean> listChecked = new ArrayList<>();

    public ImageSelectAdapter(Context context, List<String> list) {
        this.context = context;
        this.list.addAll(list);
        setListCheched(list);
    }

    public void update(List<String> list) {
        this.list.clear();
        this.list.addAll(list);
        setListCheched(list);
        notifyDataSetChanged();

    }

    /**
     * 设置listChecked的初始值
     *
     * @param list
     */
    private void setListCheched(List<String> list) {
        listChecked.clear();
        for (int i = 0; i < list.size(); i++) {
            listChecked.add(false);
        }
    }

    //当点击超过了九张图片,再点击的设置为false
    public void setCheckedBoxFalse(int pos) {
        listChecked.set(pos, false);
    }

    public interface OnCheckedChangedListener {
        /**
         * @param isChecked 是否选中
         * @param path      点击的图片路径
         * @param cb        点击的CheckBox
         * @param pos       点击的位置
         */
        void onChanged(boolean isChecked, String path, CheckBox cb, int pos);
    }

    public void setOnCheckedChangedListener(OnCheckedChangedListener onCheckedChangedListener) {
        this.onCheckedChangedListener = onCheckedChangedListener;
    }

    @Override
    public NViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new NViewHolder(LayoutInflater.from(context).inflate(R.layout.item_image_select, parent, false));
    }

    @Override
    public void onBindViewHolder(final NViewHolder holder, final int position) {
        Glide.with(context).load("file://" + list.get(position)).into(holder.iv);
        holder.cb.setChecked(listChecked.get(position));
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                holder.cb.setChecked(!holder.cb.isChecked());
                if (holder.cb.isChecked()) {
                    listChecked.set(position, true);
                } else {
                    listChecked.set(position, false);
                }
                if (onCheckedChangedListener != null) {
                    onCheckedChangedListener.onChanged(holder.cb.isChecked(), list.get(position), holder.cb, position);
                }
            }
        });
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    public class NViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.iv_itemImageSelect)
        ImageView iv;
        @BindView(R.id.cb_itemImageSelect)
        CheckBox cb;

        public NViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

}

这里Item的布局文件就是一个ImageView加一个CheckBox,根据选中状态改变CheckBox的状态,这里就不贴出来了。

·listChecked:这个集合是用来存储每个位置是否Check的,如果在onBindViewHolder里面不设置CheckBox的状态的话,由于复用问题会出问题,所以想出了用一个集合来保存它们状态的方法,不知道大家有没有其他更好的方法。

·OnCheckedChangedListener:向外暴露的接口,把点击的位置等参数都传到Activity中去。

·update():这个方法用来更新界面的,没有采用直接调notifyDataSetChanged方法是因为,如果数据的数量变化了那么listChecked的数量也要发生变化才行这样才能对应,所以写了这个方法。


再接着看看GalleryPopupWindow

/**
 * Created by lzy on 2017/2/8.
 */
public class GalleryPopupWindow extends PopupWindow {
    private static final String TAG = "lzy";

    RecyclerView mRecyclerView;

    private Activity activity;
    private GalleryPopupWindow.OnItemClickListener onItemClickListener;
    private List<ImageBean> list;
    private GalleryAdapter adapter;


    public GalleryPopupWindow(Activity context, List<ImageBean> list) {
        super(context);
        this.activity = context;
        this.list = list;
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View contentView = inflater.inflate(R.layout.popu_gallery, null);
        initView(contentView);

        int h = context.getWindowManager().getDefaultDisplay().getHeight();
        int w = context.getWindowManager().getDefaultDisplay().getWidth();
        this.setContentView(contentView);
        this.setWidth(w);
        this.setHeight(ImageSelecteActivity.dp2px(350, context));
        this.setFocusable(false);
        this.setOutsideTouchable(true);
        this.update();

        setBackgroundDrawable(new ColorDrawable(000000000));
    }

    public void notifyDataChanged() {
        adapter.notifyDataSetChanged();
    }

    private void initView(View contentView) {
        mRecyclerView = (RecyclerView) contentView.findViewById(R.id.rv_gallery);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
        adapter = new GalleryAdapter(list, activity);
        adapter.setOnItemClickListener(new GalleryAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(String fileName) {
                if (onItemClickListener != null) {
                    onItemClickListener.onItemClick(fileName);
                    dismiss();
                }
            }
        });
        mRecyclerView.setAdapter(adapter);

    }

    //暴露点击的接口
    public interface OnItemClickListener {
        /**
         * @param keyValue
         */
        void onItemClick(String keyValue);
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }
}


这个PopupWindow的布局文件就是一个RecyclerView,所以这里面也没什么,也就是设置RecyclerView,然后向外暴露一个点击的接口,用于Activity接收是点击了哪个文件夹,所以接口参数也就是文件夹名,再看看这个PopupWindow的Adapter

/**
 * Created by lzy on 2017/2/8.
 */
public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.NViewHolder> {

    private Context context;
    private List<ImageBean> list;
    private OnItemClickListener onItemClickListener;
    //用于记录是选中的哪一个文件夹
    private int selectedPos;

    public GalleryAdapter(List<ImageBean> list, Context context) {
        this.list = list;
        this.context = context;
    }

    public interface OnItemClickListener {
        void onItemClick(String fileName);
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public NViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new NViewHolder(LayoutInflater.from(context).inflate(R.layout.item_gallery, parent, false));
    }

    @Override
    public void onBindViewHolder(NViewHolder holder, final int position) {
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                selectedPos = position;
                notifyDataSetChanged();
                if (onItemClickListener != null) {
                    onItemClickListener.onItemClick(list.get(position).getFileName());
                }
            }
        });
        if (position == selectedPos) {
            holder.ivCheck.setVisibility(View.VISIBLE);
        } else {
            holder.ivCheck.setVisibility(View.GONE);
        }
        holder.tvCount.setText(list.get(position).getCount() + "张");
        holder.tvName.setText(list.get(position).getFileName());
        Glide.with(context).load("file://" + list.get(position).getFirstPicPath()).into(holder.iv);
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    public class NViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.iv_itemGallery)
        ImageView iv;
        @BindView(R.id.tv_itemGallery_name)
        TextView tvName;
        @BindView(R.id.tv_itemGallery_count)
        TextView tvCount;
        @BindView(R.id.iv_itemGallery_check)
        ImageView ivCheck;

        public NViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

}

这里有个接口是把点击的文件名传递给PopupWindow,然后再给Activity,selectedPos是用来记录选择的是哪一个文件夹,显示对应的CheckBox。


这里就差不多完成了,感兴趣的可以下载Demo来看看。再说一下,这里显示图片都是采用的Glide,使用也很方便,我们获取的图片路径都是文件路径,如果要转化为Bitmap也可以直接调用Glide的方法就可以轻松实现,如下所示:

Glide.with(this).load("file://" + listSelectedPath.get(i)).asBitmap().into(new SimpleTarget<Bitmap>() {
                        @Override
                        public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
                            Log.i(TAG, "onResourceReady: " + resource);
                        }
                    });

其中找寻控件都没有使用findViewById,而是采用的ButterKnife,节约了大量的时间,顺便说说导入的方法

在app下面的build.gradle中加入以下:

apply plugin: 'com.neenbedankt.android-apt'
apt 'com.jakewharton:butterknife-compiler:8.1.0'
    compile 'com.github.bumptech.glide:glide:3.5.2'

项目下面的build.gradle

 //添加apt插件
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

添加插件

File->Setting->Plugins  搜索zelezny,如下所示


当需要使用的时候,直接在光标移动到布局文件,点击Alt+Insert,选择Generate ButterKnife Injections

就出现如下界面,可以自动生成了



源码地址:http://download.csdn.net/detail/lylodyf/9768761


以上是关于Android实现本地图片选择及预览缩放效果仿春雨医生的主要内容,如果未能解决你的问题,请参考以下文章

android 仿头条 微信大图预览动画 双击缩放 保存至相册

Android 仿QQ新浪相册的实现

如何优雅地在Android上实现iOS的图片预览

Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等

Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等

Android 仿QQ新浪相册的实现