Android图片选择

大家都知道网上有很多第三方的图片选择器,但是到了自己真正的项目中,可能会有不同的需求,需要自己去修改。因此我自己根据鸿洋大神的慕课网视频写了一个图片选择器,又对代码进行了修改,方便大家进行使用。

本项目主要设计思路就是:一个图片加载类(单例)+利用ContentProvider扫描手机的图片+GridView显示图片 +RecyclerView在界面上显示图片。

本项目的主要步骤有以下几步:

1.图片加载类

2.扫描手机中的图片

3.选择图片展示在recyclerview中

当然最重要的是:

1.尽可能的去避免内存溢出
2.用户操作UI控件必须充分的流畅
3.用户预期显示的图片尽可能的快(图片的加载策略的选择)LIFO

本项目的主要功能:

1.扫描手机中的图片,默认显示图片最多的文件夹,在底部显示文件夹的名字以及图片的数量。

2.点击底部,弹出popupWindow,此popupWindow显示所有包含图片的文件夹以及文件夹的名字。

3.选择文件夹,进入图片选择界面,点击选择图片,再次点击取消。

4.点击右上方的“选择”按钮,将选择的图片呈现在recyclerView中。

展示一下效果:

代码展示及讲解

1.图片加载类 ImageLoader(核心类)

本类是图片选择器的核心类,该类为单例,可以设置图片加载打方式:
1.FIFO(先进先加载)
2.LILO(后进先加载)。 
调用该方法:ImageLoader.getInstance(最大图片加载并发数,ImageLoader.Type.FIFO(图片加载的方式))

.LoadImage(图片的本地存放路径(绝对路径), 要显示图片的ImageView布局);

public class ImageLoader {private static ImageLoader mInStance;

    //图片缓存的核心对象
    private LruCache<String,Bitmap> mLruCache;

    //线程池
    private ExecutorService mThreadPool;
    private  static  final  int DEAFULT_THREAD_COUNT=1;
    //队列的调度方式
    private  Type mType=Type.LIFO;
    //任务队列
    private LinkedList<Runnable> mTaskQueue;
    //后台轮询线程
    private  Thread mPoolThread;
    private Handler mPoolThreadHandler;
    //UI线程中的Handler
    private Handler mUIHandler;
    private Semaphore  mSemaphorePoolThreadHandler=new Semaphore(0);
    private Semaphore  mSemaphoreThreadPool;
    public  enum  Type{FIFO,LIFO;
    }public ImageLoader(int threadCount,Type type) {init(threadCount,type);
    }/**
     * 初始化
     * @param threadCount
     * @param type
     */
    private void init(int threadCount, Type type) {//后台轮询线程
        mPoolThread=new Thread(){@Override
            public void run() {Looper.prepare();
                mPoolThreadHandler=new Handler(){@Override
                    public void handleMessage(Message msg) {//线程池取出一个任务进行执行
                        mThreadPool.execute(getTask());
                        try {mSemaphoreThreadPool.acquire();
                        } catch (InterruptedException e) {e.printStackTrace();
                        }}};
                //释放一个信号量
                mSemaphorePoolThreadHandler.release();
                Looper.loop();
            }};
        mPoolThread.start();
        //获取我们应用的最大可用内存
        int maxMemory= (int) Runtime.getRuntime().maxMemory();
        int cacheMemory = maxMemory / 8;
        mLruCache=new LruCache<String,Bitmap>(cacheMemory){@Override
            protected int sizeOf(String key, Bitmap value) {return value.getRowBytes()*value.getHeight();
            }};
            mThreadPool= Executors.newFixedThreadPool(threadCount);
            mTaskQueue=new LinkedList<>();
            mType=type==null?Type.LIFO:type;
            mSemaphoreThreadPool=new Semaphore(threadCount);

    }/**
     * 从任务队列取出一个方法
     * @return
     */
    private Runnable getTask() {if (mType==Type.FIFO){return mTaskQueue.removeFirst();
        }else if (mType==Type.LIFO){return mTaskQueue.removeLast();
        }return  null;
    }public  static  ImageLoader getInStance(){if (mInStance==null){synchronized (ImageLoader.class){if (mInStance==null){mInStance=new ImageLoader(DEAFULT_THREAD_COUNT,Type.LIFO);
                }}}return  mInStance;
    }public  static  ImageLoader getInStance(int threadCount,Type type){if (mInStance==null){synchronized (ImageLoader.class){if (mInStance==null){mInStance=new ImageLoader(threadCount,type);
                }}}return  mInStance;
    }/**
     * 根据path为imageview设置图片
     * @param path
     * @param imageView
     */
    public  void loadImage(final String path, final ImageView imageView){imageView.setTag(path);
        if (mUIHandler==null){mUIHandler=new Handler(){@Override
                public void handleMessage(Message msg) {//获取得到的图片,为imageview回调设置图片
                    ImgBeanHolder holder= (ImgBeanHolder) msg.obj;
                    Bitmap bitmap = holder.bitmap;
                    ImageView imageview= holder.imageView;
                    String path = holder.path;
                    if (imageview.getTag().toString().equals( path)){imageview.setImageBitmap(bitmap);
                    }}};
        }//根据path在缓存中获取bitmap
        Bitmap bm=getBitmapFromLruCache(path);
        if (bm!=null){refreshBitmap(bm, path, imageView);
        }else{addTask(new Runnable(){@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)@Override
                public void run() {//加载图片
                    //图片的压缩
                    //1.获得图片需要显示的大小
                  ImageSize imageSize=  getImageViewSize(imageView);
                    //压缩图片
                    Bitmap bm=decodeSampledBitmapFromPath(imageSize.width,imageSize.height,path);
                    //把图片加入到缓存
                    addBitmapToLruCache(path,bm);
                    //
                    refreshBitmap(bm, path, imageView);
                    mSemaphoreThreadPool.release();
                }});
        }}private void refreshBitmap(Bitmap bm, String path, ImageView imageView) {Message message = Message.obtain();
        ImgBeanHolder holder=new ImgBeanHolder();
        holder.bitmap=bm;
        holder.path=path;
        holder.imageView=imageView;
        message.obj=holder;
        mUIHandler.sendMessage(message);
    }/**
     * 将图片加入到LruCache
     * @param path
     * @param bm
     */
    private void addBitmapToLruCache(String path, Bitmap bm) {if (getBitmapFromLruCache(path)==null){if (bm!=null){mLruCache.put(path,bm);
            }}}/**
     * 根据图片需要显示的宽和高进行压缩
     * @param width
     * @param height
     * @param path
     * @return
     */
    private Bitmap decodeSampledBitmapFromPath(int width, int height, String path) {//获得图片的宽和高,并不把图片加载到内存中
        BitmapFactory.Options options=new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeFile(path,options);

        options.inSampleSize=caculateInSampleSize(options,width,height);

        //使用获得到的InSampleSize再次解析图片
        options.inJustDecodeBounds=false;
        Bitmap bitmap=BitmapFactory.decodeFile(path,options);
        return  bitmap;

    }/**
     * 根据需求的宽和高以及图片实际的宽和高计算SampleSize
     * @param options
     * @param width
     * @param height
     * @return
     */
    private int caculateInSampleSize(BitmapFactory.Options options, int width, int height) {int outWidth = options.outWidth;
        int outHeight = options.outHeight;
        int inSampleSize=1;
        if (outWidth>width||outHeight>height){int widthRadio=Math.round(outWidth*1.0f/width);
            int heightRadio=Math.round(outHeight*1.0f/height);
            inSampleSize=Math.max(widthRadio,heightRadio);
        }return inSampleSize;
    }/**
     *根据ImageView获得适当的压缩的宽和高
     * @param imageView
     */
    private ImageSize getImageViewSize(ImageView imageView) {ImageSize imageSize=new ImageSize();

        DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
        ViewGroup.LayoutParams lp = imageView.getLayoutParams();
     //  int width=(lp.width== ViewGroup.LayoutParams.WRAP_CONTENT?0:imageView.getWidth());
       int width = imageView.getWidth();//获取imageview的实际宽度
        if (width <=0){width=lp.width;//获取imageview再layout声明的宽度
        }if (width<=0){width= getImageViewFieldValue(imageView,"mMaxWidth");//检查最大值
        }if (width<=0){width=displayMetrics.widthPixels;
        }//int height = lp.height == ViewGroup.LayoutParams.WRAP_CONTENT ? 0 : imageView.getHeight();
        int height = imageView.getHeight();//获取imageview的实际高度

        if (height <=0){height=lp.height;//获取imageview再layout声明的高度
        }if (height<=0){height=getImageViewFieldValue(imageView,"mMaxHeight");;//检查最大值
        }if (height<=0){height=displayMetrics.heightPixels;
        }imageSize.width=width;
        imageSize.height=height;
        return imageSize;
    }/**
     * 通过反射获得imageView的某个属性值
     * @return
     */
    private  static  int getImageViewFieldValue(Object object,String fieldName){int value=0;

        try {Field field=ImageView.class.getDeclaredField(fieldName);
            field.setAccessible(true);
            int fieldValue = field.getInt(object);
            if (fieldValue>0&&fieldValue<Integer.MAX_VALUE){value=fieldValue;
            }} catch (NoSuchFieldException e) {e.printStackTrace();
        } catch (IllegalAccessException e) {e.printStackTrace();
        }return  value;
    }private  synchronized void addTask(Runnable runnable)  {mTaskQueue.add(runnable);
        try {if (mPoolThreadHandler==null){mSemaphorePoolThreadHandler.acquire();
            }} catch (InterruptedException e) {e.printStackTrace();
        }mPoolThreadHandler.sendEmptyMessage(0x110);
    }/**
     * 根据path为imageview设置图片
     * @param key
     * @return
     */
    private Bitmap getBitmapFromLruCache(String key) {return mLruCache.get(key);
    }private  class  ImageSize{int width;
       int height;
   }private class ImgBeanHolder{Bitmap bitmap;
        ImageView imageView;
        String path;
    }
}

2.图片的列表

首先扫描手机上的所有图片信息,拿到图片数量最多的文件夹,直接显示在GridView上;并且扫描结束,得到一个所有包含图片的文件夹信息的List;

对于文件夹信息,我们单独创建了一个Bean:

public class FolderBean {private String dir;//当前文件夹路径
    private String firstImamgPath;//第一张图片的路径
    private String name;//文件夹的名字
    private int count; //图片数量

    public String getDir() {return dir;
    }public void setDir(String dir) {this.dir = dir;
        int indexOf = this.dir.lastIndexOf("/")+1;
        this.name=this.dir.substring(indexOf);
    }public String getFirstImamgPath() {return firstImamgPath;
    }public void setFirstImamgPath(String firstImamgPath) {this.firstImamgPath = firstImamgPath;
    }public String getName() {return name;
    }public void setName(String name) {this.name = name;
    }public int getCount() {return count;
    }public void setCount(int count) {this.count = count;
    }
}

其次是扫描手机中的图片:

注意:在6.0系统以上,要手动开启读取内存卡的权限,否则程序是运行不起来的。

if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {if (ContextCompat.checkSelfPermission(SelectPhotoActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(SelectPhotoActivity.this, new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
    }else{aboutScanPhoto();//未开启权限,先开启权限。以开启权限后,直接扫描图片
    }
}else{aboutScanPhoto();//扫描图片的方法
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {switch (requestCode){case 1:if(grantResults.length>0 &&grantResults[0] == PackageManager.PERMISSION_GRANTED){aboutScanPhoto();
            }else {Toast.makeText(this, "请打开权限!", Toast.LENGTH_SHORT).show();
            }break;
        default:}
}

扫描图片的方法

if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){Toast.makeText(this,"当前存储卡不可用!",Toast.LENGTH_LONG);
    return;
}
mProgressDialog= ProgressDialog.show(this,null,"正在加载。。。");
new Thread(){@Override
    public void run() {Uri mImgUri= MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        ContentResolver cr=SelectPhotoActivity.this.getContentResolver();
        Cursor mCursor = cr.query(mImgUri, null,
                MediaStore.Images.Media.MIME_TYPE + "=? or "
                        + MediaStore.Images.Media.MIME_TYPE + "=?",
                new String[] { "image/jpeg", "image/png" },
                MediaStore.Images.Media.DATE_MODIFIED);
        Set<String> mDirPaths=new HashSet<String>();
        while (mCursor.moveToNext()){String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));
            File parentFile=new File(path).getParentFile();
            if (parentFile==null) {continue;
            }String dirPath = parentFile.getAbsolutePath();
            FolderBean folderBean=null;
            if (mDirPaths.contains(dirPath)){continue;
            }else{mDirPaths.add(dirPath);
                folderBean=new FolderBean();
                folderBean.setDir(dirPath);
                folderBean.setFirstImamgPath(path);
            }if (parentFile.list()==null){continue;
            }int picSize=parentFile.list(new FilenameFilter() {@Override
                public boolean accept(File dir, String name) {if (name.endsWith(".jpg")||name.endsWith("jpeg")||name.endsWith("png")){return  true;
                    }return false;
                }}).length;
            folderBean.setCount(picSize);
            mFolderBeans.add(folderBean);

            if (picSize>mMaxCount){mMaxCount=picSize;
                mCurrentDir=parentFile;
            }}mCursor.close();
        handler.sendEmptyMessage(0x110);

    }
}.start();

然后我们通过handler发送消息,在handleMessage里面:

1、创建GridView的适配器,为我们的GridView设置适配器,显示图片;

2、创建我们的popupWindow了

private Handler handler=new Handler(){@Override
    public void handleMessage(Message msg) {if (msg.what==0x110){mProgressDialog.dismiss();
            dataToView();
            initPopupWindow();
        }}
};

dataToView()就是我们为view设置数据

private void dataToView() {if (mCurrentDir==null){Toast.makeText(this,"未扫描到任何图片",Toast.LENGTH_LONG).show();
        return;
    }mImgs= Arrays.asList(mCurrentDir.list());
    adapter = new ImageAdapter(this,mImgs,mCurrentDir.getAbsolutePath());
    mGridView.setAdapter(adapter);
    mTvDirName.setText(mCurrentDir.getName());
    mTvDirCount.setText(mMaxCount+"");
}

我们还用到了一个GridView的adapter

public  class ImageAdapter extends BaseAdapter {private  String mDirPath;
    private List<String> mImgPaths;
    private LayoutInflater mInflater;
    private static List<String> mSelectImg=new LinkedList<>();
    public ImageAdapter(Context context, List<String> mDatas, String dirPath) {this.mDirPath=dirPath;
        this.mImgPaths=mDatas;
        mInflater=LayoutInflater.from(context);
    }@Override
    public int getCount() {return mImgPaths.size();
    }@Override
    public Object getItem(int position) {return mImgPaths.get(position);
    }@Override
    public long getItemId(int position) {return position;
    }@Override
    public View getView(final int position, View convertView, ViewGroup parent) {ViewHolder vh=null;
        if (convertView==null){convertView=  mInflater.inflate(R.layout.item,parent,false);
            vh=new ViewHolder();
            vh.mImg=convertView.findViewById(R.id.iv_item);
            vh.mSelect=convertView.findViewById(R.id.ib_select);
            convertView.setTag(vh);
        }else {vh = (ViewHolder) convertView.getTag();
        }vh.mImg.setImageResource(R.mipmap.default_error);
        vh.mSelect.setImageResource(R.mipmap.btn_unselected);
        vh.mImg.setColorFilter(null);
        final String filePath=mDirPath+"/"+mImgPaths.get(position);
        //   new  ImageLoader(3, ImageLoader.Type.LIFO).loadImage(mDirPath + "/" + mImgPaths.get(position),vh.mImg);
        ImageLoader.getInStance(3, ImageLoader.Type.LIFO).loadImage(mDirPath+"/"+mImgPaths.get(position),vh.mImg);
        final ViewHolder finalVh = vh;
        vh.mImg.setOnClickListener(new View.OnClickListener() {@Override
            public void onClick(View v) {//已经被选择
                if (mSelectImg.contains(filePath)){mSelectImg.remove(filePath);
                    finalVh.mImg.setColorFilter(null);
                    finalVh.mSelect.setImageResource(R.mipmap.btn_unselected);
                }else{//未被选中
                    mSelectImg.add(filePath);
                    finalVh.mImg.setColorFilter(Color.parseColor("#77000000"));
                    finalVh.mSelect.setImageResource(R.mipmap.btn_selected);
                }}});
        if (mSelectImg.contains(filePath)){vh.mImg.setColorFilter(Color.parseColor("#77000000"));
            vh.mSelect.setImageResource(R.mipmap.btn_selected);
        }return convertView;
    }public List<String> selectPhoto(){if (!mSelectImg.isEmpty()){return mSelectImg;
        }return null;
    }private  class  ViewHolder{ImageView mImg;
        ImageButton mSelect;
    }
}

3.到这一步图片就已经显示在GridView中,下一步就是我们的popupWindow

要实现的效果是:点击底部的布局弹出我们的文件夹选择框,并且我们弹出框后面的Activity要变暗;

首先我们创建了一个popupWindow使用的类,和Activity类似:

public class ListImageDirPopupWindow extends PopupWindow {private  int mWidth;
    private int mHeight;
    private View mConvertView;
    private List<FolderBean> mDatas;
    private ListView mListView;

    public  interface  OnDirSelectedListener{void onSelected(FolderBean folderBean);
    }public  OnDirSelectedListener mListener;

    public void setOnDirSelectedListener(OnDirSelectedListener mListener) {this.mListener = mListener;
    }public ListImageDirPopupWindow(Context context, List<FolderBean> datas) {calWidthAndHeight(context);
        mConvertView= LayoutInflater.from(context).inflate(R.layout.popup_main,null);
        mDatas=datas;
        setContentView(mConvertView);
        setWidth(mWidth);
        setHeight(mHeight);
        setFocusable(true);
        setTouchable(true);
        setOutsideTouchable(true);
        setBackgroundDrawable(new BitmapDrawable());

        setTouchInterceptor(new View.OnTouchListener() {@Override
            public boolean onTouch(View v, MotionEvent event) {if (event.getAction()==MotionEvent.ACTION_OUTSIDE){dismiss();
                    return true;
                }return false;
            }});
        initViews(context);
        initEvent();
    }private void initViews(Context context) {mListView=  mConvertView.findViewById(R.id.lv_dir);
        mListView.setAdapter(new ListDirAdapter(context,mDatas));
    }private void initEvent() {mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Override
           public void onItemClick(AdapterView<?> parent, View view, int position, long id) {if (mListener!=null){mListener.onSelected(mDatas.get(position));
               }}});
    }/**
     * 计算popupWindow的宽度和高度
     * @param context
     */
    private void calWidthAndHeight(Context context) {WindowManager wm= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics=new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        mWidth= outMetrics.widthPixels;
        mHeight= (int) (outMetrics.heightPixels*0.7);

    }private class  ListDirAdapter extends ArrayAdapter<FolderBean>{private  LayoutInflater mInflater;
        private  List<FolderBean> mDatas;

        public ListDirAdapter(@NonNull Context context, List<FolderBean> datas) {super(context, 0, datas);
            mInflater= LayoutInflater.from(context);
        }@NonNull
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {ViewHolder vh=null;
            if (convertView==null){vh=new ViewHolder();
                convertView=  mInflater.inflate(R.layout.popup_item,parent,false);
                vh.mDirName=(TextView) convertView.findViewById(R.id.tv_dir_item_name);
                vh.mDirCount=(TextView) convertView.findViewById(R.id.tv_dir_item_count);
                vh.mImg= (ImageView) convertView.findViewById(R.id.iv_dir_image);
                convertView.setTag(vh);
            }else{vh= (ViewHolder) convertView.getTag();
            }FolderBean bean = getItem(position);
            //重置
            vh.mImg.setImageResource(R.mipmap.default_error);

            ImageLoader.getInStance(3, ImageLoader.Type.LIFO).loadImage(bean.getFirstImamgPath(),vh.mImg);
            vh.mDirName.setText(bean.getName());
            vh.mDirCount.setText(bean.getCount()+"");
            return convertView;
        }private  class  ViewHolder{ImageView mImg;
            TextView mDirName;
            TextView mDirCount;
        }}
}

然后我们需要和Activity交互,当我们点击某个文件夹的时候,外层的Activity需要改变它GridView的数据源,展示我们点击文件夹的图片。在这里我们创建一个接口OnDirSelectedListener ,对Activity设置回调;

mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {if (mListener!=null){mListener.onSelected(mDatas.get(position));
        }}
});

4.选择不同的文件夹

前面我们handleMessage中初始化调用popupWindow,通过activity的回调,实现选择文件夹显示图片

mImageDirPopupWindow=new ListImageDirPopupWindow(this,mFolderBeans);
mImageDirPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {@Override
    public void onDismiss() {lightOn();
    }
});
mImageDirPopupWindow.setOnDirSelectedListener(new ListImageDirPopupWindow.OnDirSelectedListener() {@Override
    public void onSelected(FolderBean folderBean) {mCurrentDir=new File(folderBean.getDir());
       mImgs= Arrays.asList(mCurrentDir.list(new FilenameFilter() {@Override
            public boolean accept(File dir, String name) {if (name.endsWith(".jpg")||name.endsWith("jpeg")||name.endsWith("png")){return  true;
                }return false;
            }}));
        adapter=new ImageAdapter(SelectPhotoActivity.this,mImgs,mCurrentDir.getAbsolutePath());
        mGridView.setAdapter(adapter);
        mTvDirCount.setText(mImgs.size()+"");
        mTvDirName.setText(folderBean.getName());
        mImageDirPopupWindow.dismiss();
    }
});

5.图片展示在RecyclerView中

在ImageAdapter中定义了一个方法,获得选择图片的路径,点击“选择”按钮的时候,把这个路径的集合传过去

public List<String> selectPhoto(){if (!mSelectImg.isEmpty()){return mSelectImg;
    }return null;
}

然后通过ImageLoader这个类将图片展示出来,我们在recyclerview设置图片展示方式为网格方式。

ImageLoader.getInStance(3, ImageLoader.Type.LIFO).loadImage(mDatas.get(position),holder.iv);
if (photoSelect!=null) {final SimpleAdapter mAdapter = new SimpleAdapter(this, photoSelect);
    mListView.setAdapter(mAdapter);
    mListView.setLayoutManager(new GridLayoutManager(this,3));
}

其中涉及到了一个adapter,是recyclerview所需要的

public class SimpleAdapter extends RecyclerView.Adapter<SimpleAdapter.MyViewHolder> {private LayoutInflater mInflater;
    private Context mContext;
    protected List<String> mDatas;

    public SimpleAdapter(Context mContext, List<String> mDatas) {this.mContext = mContext;
        this.mDatas = mDatas;
        mInflater=LayoutInflater.from(mContext);
    }@Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = mInflater.inflate(R.layout.list_item, parent, false);
        MyViewHolder viewHolder=new MyViewHolder(view);
        return viewHolder;
    }@Override
    public void onBindViewHolder(final MyViewHolder holder, int position) {ImageLoader.getInStance(3, ImageLoader.Type.LIFO).loadImage(mDatas.get(position),holder.iv);
    }@Override
    public int getItemCount() {return mDatas.size();
    }class  MyViewHolder extends RecyclerView.ViewHolder {ImageView iv;
        public MyViewHolder(View itemView) {super(itemView);
            iv=  itemView.findViewById(R.id.iv_photo);
        }}
}

如果大家想学习RecyclerView的使用,可以看一下我的博客

https://blog.csdn.net/wen_haha/article/details/80775056

总结:本项目的主要代码基本已经贴出来了,感兴趣的朋友可以去下载本人的项目,最后,如有不对的地方,请多多指教.

Demo

CSDN地址:

https://download.csdn.net/download/wen_haha/10499827

Github地址:

https://github.com/kongkongdaren/SelectPhotoDemo

Android中仿微信选择图片并展示在RecyclerView中相关推荐

  1. android 微信相册功能,Android仿微信选择图片和拍照功能

    本文实例为大家分享了 Android微信选择图片的具体代码,和微信拍照功能,供大家参考,具体内容如下 1.Android6.0系统,对于权限的使用都是需要申请,选择图片和拍照需要申请Manifest. ...

  2. Android仿微信选择图片

    因为需要上传图片,选择图片的效果微信效果很好,所以我在网上找了一些仿微信的例子,但是都不是很全,所以我找了几个并和在一起,效果还行,不废话了上代码. 首先,是一个九宫格显示图片的页面.因为我的代码是f ...

  3. android高仿微信的图片查看

    最近改造了别人的git项目(修复了一些bug,和新增了一些功能),实现高仿微信图片查看,传送门在此PicWatcher,效果图如下(gif显示的比较卡顿,实际体验是很OK的):

  4. Android高仿微信微博多图展示

    NineGridLayout 1.简介 这是一个用于实现像微信朋友圈和微博的类似的九宫格图片展示控件,通过自定义viewgroup实现,使用方便. 多图根据屏幕适配,单张图片时需要自己指定图片的宽高: ...

  5. android访问图库,android通过访问相册获取图片并展示在ImageView中

    第一步:添加相应的权限以及属性: ①在manifest中设置权限 ②在中设置相应属性,这一步很关键,解决了我在这一方面最后的一个有关deny的权限问题 android:requestLegacyExt ...

  6. android通过访问相册获取图片并展示在ImageView中

    第一步:添加相应的权限以及属性: ①在manifest中设置权限 <uses-feature android:name="android.hardware.camera" / ...

  7. android仿微信图片上传进度,android高仿微信发布动态(选择图片)

    [实例简介]Android 超高仿微信图片选择器 [实例截图] [核心代码] public class MainActivity extends Activity implements OnImage ...

  8. android 仿微信选取相册_Android模仿微信选择图片

    前言 最近公司需要做一个类似微信那种选择头像和上传图片的功能,本想上github上找的,后来想了想,还是自己做一个,不仅方便以后用(毕竟自己写的修改起来也比较方便),还可以学到一些知识,废话少说,先看 ...

  9. android 仿微信选取相册_Android 实现一个仿微信的图片选择器

    现在大部分的App都上传图片的功能,比如设置用户头像.聊天发送图片.发表动态.论坛帖子等.上传图片需要先从选择手机中选择要上传的图片,所以图片选择器在App中是很常见的组件,一般的手机都会自带一个图片 ...

  10. android仿微信的图片选择器

    PictureSelector 项目地址: arvinljw/PictureSelector  简介:包含:多选.单选.拍照.预览.裁剪:兼容大图,兼容 7.0 更多: 作者    提 Bug    ...

最新文章

  1. [GWCTF 2019]pyre.pyc [CISCN2018]2ex
  2. 求最小生成树-Prim(普里姆算法)
  3. wxIntegerValidator< T > 类模板用法
  4. [数据库] Navicat for Oracle设置唯一性和递增序列实验
  5. 榜单类应用我所喜欢的算法
  6. [PHP 安全] pcc —— PHP 安全配置检测工具
  7. 【TensorFlow官方文档】MNIST机器学习入门
  8. HDU 5410 CRB and His Birthday ——(完全背包变形)
  9. 生产者-消费者 BlockingQueue 运用示例
  10. 删除了电脑硬盘的数据能恢复吗,硬盘数据删除了还能恢复吗
  11. Arduino笔记五三轴陀螺仪L3G4200D
  12. VScode下载安装及使用教程
  13. CSR3026开发问题总结-1
  14. r7 2700X装Linux,R7-2700X配什么主板?AMD锐龙7 2700X主板推荐 (全文)
  15. 浏览器访问IPv6地址
  16. Scala+HuffmanCoding实现无损压缩
  17. 网络数据采集技术snmp/netflow/sflow/network telemetry简介
  18. 尚硅谷python入门
  19. 一图看懂自然资源资金监测监管系统
  20. R语言与克朗巴哈alpha系数

热门文章

  1. [iOS]手把手教你实现微信小视频
  2. 使用js jquery去搭建完成京东购物车
  3. jetbrain工具常见问题汇总
  4. 使用Vue单文件组件添加删除列表
  5. python类和对象的应用:烤地瓜
  6. 监控视频分发转发服务器性能,网络视频监控系统流媒体分发存储服务器软件设计...
  7. Python提取图片中的文字信息
  8. 【ESP32之旅】ESP32C3 Arduino库使用方法
  9. B样条曲线的公式推导及代码实现
  10. python图灵机器人微信号_IT之家学院:让你的微信号变成自动聊天机器人