上次我写了一个关于MVC框架怎么用在项目里,相关基类的封装方法。其实就是为今天的这篇文章做准备。相册,也就是图片选择器,在我们的项目中用的还是比较多的。但就我了解,多数程序员还是通过引用第三方框架实现这个功能。但是如果UI有要求,或者我们自己有什么特殊需要,可能根本无法满足。

所以今天我给大家带来自己做的相册。既给你一个完整的相册,也给你一套做相册的方法。同时我们可以从其中学到很多的知识点。

在开始之前要说明一下,这个相册要依赖之前讲的MVC框架,所以有两种选择,一种是把那个框架先搭建好,然后无感复制粘贴,就可以实现相册效果。或者自己分析,用现有框架也可以实现。

首先在res下建立anim

其中有两个类

slide_down.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="200"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="0"
        android:toYDelta="100%"/>

slide_up.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="200"
        android:fromXDelta="0"
        android:fromYDelta="100%"
        android:toXDelta="0"
        android:toYDelta="0"/>

这两个是选择文件夹的动画,在styles.xml中加入

<style name="dir_popupwindow_anim">
    <item name="android:windowEnterAnimation">@anim/slide_up</item>
    <item name="android:windowExitAnimation">@anim/slide_down</item>
</style>

这样动画就准备好了。接着我们准备布局几个图片

这是我准备的三张图片,左边的两张是图片右上角和文件夹选择弹框的最右边是否选中的标识图标。第三张是一个图片未加载出时的占位图片。

清单文件中我们要加入两条权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

接着我们有四个布局

activity_main.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">
    <GridView
        android:id="@+id/mGridView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:cacheColorHint="@android:color/transparent"
        android:horizontalSpacing="3dp"
        android:listSelector="@android:color/transparent"
        android:numColumns="4"
        android:stretchMode="columnWidth"
        android:verticalSpacing="3dp" />
    <RelativeLayout
        android:id="@+id/mBottomly"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:background="#000">
        <TextView
            android:id="@+id/mDirName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="15dp"
            android:text="文件夹名称"
            android:textColor="#fff"
            android:textSize="14sp" />
        <TextView
            android:id="@+id/mDirCount"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="15dp"
            android:text="图片数量"
            android:textColor="#fff"
            android:textSize="14sp" />
    </RelativeLayout>
</LinearLayout>

如果我们需要修改相册的样式,就在这布局中修改

然后是item_album.xml,这个是每个照片格子的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:id="@+id/id_item_image"
        android:src="@mipmap/noimage"
        android:scaleType="centerCrop"/>
    <ImageButton
        android:layout_width="15dp"
        android:layout_height="15dp"
        android:id="@+id/id_item_select"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginRight="3dp"
        android:layout_marginTop="3dp"
        android:background="@null"
        android:clickable="false"
        android:scaleType="fitXY"
        android:src="@mipmap/ic_unchoose"/>
</RelativeLayout>

接着是item_popup_main.xml,这个是文件夹选择弹框的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp">
    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:id="@+id/id_id_dir_item_image"
        android:layout_alignParentLeft="true"
        android:paddingTop="9dp"
        android:paddingBottom="17dp"
        android:paddingLeft="12dp"
        android:paddingRight="12dp"
        android:scaleType="fitXY"
        android:src="@mipmap/noimage"
        android:layout_centerVertical="true"/>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@id/id_id_dir_item_image"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/id_dir_item_name"
            android:text="所有图片"
            android:textSize="12sp" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/id_dir_item_count"
            android:layout_gravity="center"
            android:text="1212张"
            android:textSize="10sp"
            android:textColor="#444"/>
    </LinearLayout>
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="20sp"
        android:src="@mipmap/ic_choose"/>
</RelativeLayout>

最后是我们的文件夹选项弹框主布局popup_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">
    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/id_list_dir"
        android:divider="#eee3d9"
        android:dividerHeight="1px"/>
</RelativeLayout>

一顿操作猛如虎,终于把我们的资源都整齐了。现在可以开始安心写java代码了。首先是我们的图片加载类

* 图片加载类 * @author WaterWood public class ImageLoader { * 单例对象 private static ImageLoader mInstance; * 图片缓存的核心对象 private LruCache<String,Bitmap> mLruCache; * 线程池 private ExecutorService mThreadPool; * 默认线程个数 private static final int DEAFAULT_THREAD_COUNT = 1; * 队列方式 private Type mType = Type.LIFO; * 任务队列 private LinkedList<Runnable> mTaskQueue; * 后台轮询线程 private Thread mPoolThread; * 与线程绑定的Handler,对线程中的Queue发送消息 private Handler mPoolThreadHandler; * UI线程的一个Handler private Handler mUIHandler; * addTask使用mPoolThreadHandler的信号量,防止mPoolThreadHandler为空 private Semaphore mSemaphorePoolThreadHandler = new Semaphore(0); * 是内部队列执行完,再从TaskQueue中取下一个 private Semaphore mSemaphoreThreadPool; * 私有构造方法 private ImageLoader(int threadCount,Type type){ init(threadCount,type); * 单例获取 * @return public static ImageLoader getInstance(){ if (mInstance == null){ synchronized (ImageLoader.class){ if (mInstance == null) { mInstance = new ImageLoader(DEAFAULT_THREAD_COUNT,Type.LIFO); return mInstance; * 单例获取 * @return public static ImageLoader getInstance(int threadCount,Type type){ if (mInstance == null){ synchronized (ImageLoader.class){ if (mInstance == null) { mInstance = new ImageLoader(threadCount,type); return mInstance; * 队列类型枚举 public enum Type{ FIFO,LIFO * 一系列的初始化操作 * @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<Runnable>(); mType = type; mSemaphoreThreadPool = new Semaphore(threadCount); * 根据path加载图片 * @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 bm = holder.bitmap; ImageView imageview = holder.imageView; String path = holder.path; //将path与getTag存储路径进行比较 if (imageview.getTag().toString().equals(path)){ imageview.setImageBitmap(bm); Bitmap bm = getBitmapFromLruCache(path); if (bm!=null){ //内存中有的情况 refreshBitmap(path,imageView,bm); }else{ //内存中没有的情况 addTask(new Runnable(){ @Override public void run() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { ImageSize imageSize = getImageViewSize(imageView); Bitmap bm = decodeSampledBitmapFromPath(path,imageSize.width,imageSize.height); addBitmapToLruCache(path,bm); refreshBitmap(path,imageView,bm); mSemaphoreThreadPool.release(); private Bitmap getBitmapFromLruCache(String key){ return mLruCache.get(key); private class ImgBeanHolder{ Bitmap bitmap; ImageView imageView; String path; private synchronized void addTask(Runnable runnable){ mTaskQueue.add(runnable); try { if (mPoolThreadHandler==null) { mSemaphorePoolThreadHandler.acquire(); } catch (InterruptedException e) { e.printStackTrace(); mPoolThreadHandler.sendEmptyMessage(0x110); private Runnable getTask(){ if (mType == Type.FIFO){ return mTaskQueue.removeFirst(); }else if (mType == Type.LIFO){ return mTaskQueue.removeLast(); return null; * 计算压缩尺寸 * @param imageView * @return private ImageSize getImageViewSize(ImageView imageView){ ImageSize imageSize = new ImageSize(); RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) imageView.getLayoutParams(); DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics(); int width = imageView.getWidth(); if (width == 0){ width = lp.width;//获取imageview在layout中声明的宽度 if (width<=0){ width = getImageViewFieldValue(imageView,"mMaxWidth"); if (width<=0){ width = displayMetrics.widthPixels; int height = imageView.getHeight(); 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; private class ImageSize{ int width; int height; * 将path转换为bimap带压缩 * @param path * @param width * @param height * @return protected Bitmap decodeSampledBitmapFromPath(String path,int width,int height){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path,options); options.inSampleSize = caculateInSampleSize(options,width,height); options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFile(path,options); return bitmap; * 计算压缩比 * @param options * @param reqWidth * @param reqHeight * @return private int caculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){ int width = options.outWidth; int height = options.outHeight; int inSampleSize = 1; if (width>reqWidth || height > reqHeight){ int widthRadio = Math.round(width*1.0f/reqWidth); int heightRadio = Math.round(height*1.0f/reqHeight); inSampleSize = Math.max(widthRadio,heightRadio); return inSampleSize; * 将bitmap加入内存缓存中 * @param path * @param bm protected void addBitmapToLruCache(String path,Bitmap bm){ if (getBitmapFromLruCache(path) == null){ if (bm!=null){ mLruCache.put(path,bm); * 刷新图片 * @param path * @param imageView * @param bitmap private void refreshBitmap(String path,ImageView imageView,Bitmap bitmap){ Message message = Message.obtain(); ImgBeanHolder holder = new ImgBeanHolder(); holder.bitmap = bitmap; holder.path = path; holder.imageView = imageView; message.obj = holder; mUIHandler.sendMessage(message); * 通过反射获取任何对象的任何属性值 * @param object * @param fieldName * @return private static int getImageViewFieldValue(Object object,String fieldName){ try { int value = 0; Field field = ImageView.class.getDeclaredField(fieldName); field.setAccessible(true); int fieldValue = field.getInt(object); if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE){ value = fieldValue; return value; } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); return 0;

接着是图片文件夹实体

public class FolderBean {
     * 文件夹路径
    private String dir;
     * 第一张图片路径
    private String firstImgPath;
     * 文件夹名称
    private String name;
     * 文件数量
    private int count;
    public String getDir() {
        return dir;
    public void setDir(String dir) {
        this.dir = dir;
        int lastIndexOf = this.dir.lastIndexOf("/");
        this.name = this.dir.substring(lastIndexOf+1);
    public String getFirstImgPath() {
        return firstImgPath;
    public void setFirstImgPath(String firstImgPath) {
        this.firstImgPath = firstImgPath;
    public String getName() {
        return name;
    public int getCount() {
        return count;
    public void setCount(int count) {
        this.count = count;

图片主界面GridView的Adapter

public class ImageAdapter extends BaseAdapter {
    private String mDirPath;
    private List<String> mImgPaths;
    private LayoutInflater mInflater;
    private Context context;
    private static Set<String> mSeletedImg = new HashSet<String>();
    public ImageAdapter(Context context, List<String> mDatas, String dirPath){
        this.context = context;
        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 viewHolder = null;
        if (convertView == null){
            convertView = mInflater.inflate(R.layout.item_album,parent,false);
            viewHolder = new ViewHolder();
            viewHolder.mImg = convertView.findViewById(R.id.id_item_image);
            viewHolder.mSelect = convertView.findViewById(R.id.id_item_select);
            //这里要根据手机尺寸修改一下图片的大小
            int widthAndHeight = (CommonUtils.getScreenWidth(context) - CommonUtils.dp2px(context,9))/4;
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) viewHolder.mImg.getLayoutParams();
            params.width = widthAndHeight;
            params.height = widthAndHeight;
            viewHolder.mImg.setLayoutParams(params);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder) convertView.getTag();
        //重置状态
        viewHolder.mImg.setImageResource(R.mipmap.noimage);
        viewHolder.mSelect.setImageResource(R.mipmap.ic_unchoose);
        viewHolder.mImg.setColorFilter(null);
        ImageLoader.getInstance(3, ImageLoader.Type.LIFO).loadImage(mDirPath+"/"+mImgPaths.get(position),viewHolder.mImg);
        final String filePath = mDirPath+"/"+mImgPaths.get(position);
        final ViewHolder finalViewHolder = viewHolder;
        viewHolder.mImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSeletedImg.contains(filePath)){
                    //已经被选择
                    mSeletedImg.remove(filePath);
                    finalViewHolder.mImg.setColorFilter(null);
                    finalViewHolder.mSelect.setImageResource(R.mipmap.ic_unchoose);
                }else{
                    //未被选择
                    mSeletedImg.add(filePath);
                    finalViewHolder.mImg.setColorFilter(Color.parseColor("#77000000"));
                    finalViewHolder.mSelect.setImageResource(R.mipmap.ic_choose);
//                notifyDataSetChanged();
        if (mSeletedImg.contains(filePath)){
            viewHolder.mImg.setColorFilter(Color.parseColor("#77000000"));
            viewHolder.mSelect.setImageResource(R.mipmap.ic_choose);
        return convertView;
    private class ViewHolder{
        ImageView mImg;
        ImageButton mSelect;

文件夹弹框的Adapter

public class ListImageDirPopupWindow extends PopupWindow{
    private int mWidth;
    private int mHeight;
    private View mConvertView;
    private ListView mListView;
    private List<FolderBean> mDatas;
    private OnDirSelectedListener 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;
        initView(context);
        initEvent();
    private void initView(Context context) {
        mListView = mConvertView.findViewById(R.id.id_list_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));
    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(Context context,List<FolderBean> objects) {
            super(context,0,objects);
            mInflater = LayoutInflater.from(context);
        @NonNull
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null){
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.item_popup_main,parent,false);
                holder.mImg = convertView.findViewById(R.id.id_id_dir_item_image);
                holder.mDirName = convertView.findViewById(R.id.id_dir_item_name);
                holder.mDirCount = convertView.findViewById(R.id.id_dir_item_count);
                convertView.setTag(holder);
            }else{
                holder = (ViewHolder) convertView.getTag();
            FolderBean bean = getItem(position);
            holder.mImg.setImageResource(R.mipmap.noimage);
            ImageLoader.getInstance().loadImage(bean.getFirstImgPath(),holder.mImg);
            holder.mDirCount.setText(bean.getCount()+"");
            holder.mDirName.setText(bean.getName());
            return convertView;
        private class ViewHolder{
            ImageView mImg;
            TextView mDirName;
            TextView mDirCount;
    public interface OnDirSelectedListener{
        void onSelected(FolderBean folderBean);
    public void setmListener(OnDirSelectedListener mListener) {
        this.mListener = mListener;

主界面Activity

public class MainActivity extends WaterPermissionActivity<MainModel> implements MainCallback {
     * 控件四个
    private GridView mGridView;
    private RelativeLayout mBottomly;
    private TextView mDirName;
    private TextView mDirCount;
     * 图片数据集
    private List<String> mImgs;
     * 对应当前文件夹的File对象
    private File mCurrentDir;
     * 当前文件夹中的文件个数
    private int mMaxCount;
     * 文件夹列表
    private List<FolderBean> mFolderBeans = new ArrayList<>();
     * 进度条弹框
    private ProgressDialog mProgressDialog;
    private ListImageDirPopupWindow mDirPopupWindow;
     * 该参数负责子线程查询图片后通知主线程更新UI
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0x110){
                mProgressDialog.dismiss();//消失对话框
                data2View();
                initDirPopupWindow();
     * 图片适配器
    private ImageAdapter mImgAdapter;
    @Override
    protected MainModel getModelImp() {
        return new MainModel(this,this);
    @Override
    protected int getContentLayoutId() {
        return R.layout.activity_main;
    @Override
    protected void initWidget() {
        mGridView = findViewById(R.id.mGridView);
        mBottomly = findViewById(R.id.mBottomly);
        mDirName = findViewById(R.id.mDirName);
        mDirCount = findViewById(R.id.mDirCount);
    @Override
    protected void initData() {
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            Toast.makeText(this,"当前存储卡不可用!",Toast.LENGTH_SHORT).show();
            return;
        requestPermission(READ_EXTERNAL_STORAGE);
        mBottomly.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDirPopupWindow.setAnimationStyle(R.style.dir_popupwindow_anim);
                mDirPopupWindow.showAsDropDown(mBottomly,0,0);
                lightOff();
    @Override
    protected void doSDRead() {
        requestPermission(WRITE_EXTERNAL_STORAGE);
    @Override
    protected void doSDWrite() {
        mProgressDialog = ProgressDialog.show(this,null,"正在加载...");
        new Thread(){
            @Override
            public void run() {
                Uri mImgUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                ContentResolver cr = MainActivity.this.getContentResolver();
                Cursor cursor = 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 (cursor.moveToNext()){
                    String path = cursor.getString(cursor.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.setFirstImgPath(path);
                    if (parentFile.list() == null){
                        continue;
                    int picSize = parentFile.list(new FilenameFilter() {
                        @Override
                        public boolean accept(File dir, String filename) {
                            if (filename.endsWith(".jpg")
                                    || filename.endsWith(".jpeg")
                                    || filename.endsWith(".png")){
                                return true;
                            return false;
                    }).length;
                    folderBean.setCount(picSize);
                    mFolderBeans.add(folderBean);
                    if (picSize > mMaxCount){
                        mMaxCount = picSize;
                        mCurrentDir = parentFile;
                cursor.close();
                mHandler.sendEmptyMessage(0x110);
        }.start();
    protected void data2View(){
        if (mCurrentDir == null){
            Toast.makeText(this,"未扫描到任何图片",Toast.LENGTH_SHORT).show();
            return;
        mImgs = Arrays.asList(mCurrentDir.list());
        mImgAdapter = new ImageAdapter(this,mImgs,mCurrentDir.getAbsolutePath());
        mGridView.setAdapter(mImgAdapter);
        mDirCount.setText(mMaxCount+"");
        mDirName.setText(mCurrentDir.getName());
    private void initDirPopupWindow(){
        mDirPopupWindow = new ListImageDirPopupWindow(this,mFolderBeans);
        mDirPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                lightOn();
        mDirPopupWindow.setmListener(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 filename) {
                        if (filename.endsWith(".jpg")
                                ||filename.endsWith(".jpeg")
                                ||filename.endsWith(".png")){
                            return true;
                        return false;
                mImgAdapter = new ImageAdapter(MainActivity.this,mImgs,mCurrentDir.getAbsolutePath());
                mGridView.setAdapter(mImgAdapter);
                mDirCount.setText(mImgs.size()+"");
                mDirName.setText(folderBean.getName());
                mDirPopupWindow.dismiss();
    private void lightOn(){
        WindowManager.LayoutParams lp = getWindow().getAttributes();
        lp.alpha = 1.0f;
        getWindow().setAttributes(lp);
    private void lightOff(){
        WindowManager.LayoutParams lp = getWindow().getAttributes();
        lp.alpha = 0.3f;
        getWindow().setAttributes(lp);

Callback和Model这里先省略了。

这样我们就可以无感制作出一个完整的相册,是不是很方便。时间有限,大家可以自己分析其中的知识点。能用到的很多,之后我会一一拆出来分别写出博客以供使用。

之前没有贴上资源地址,所以很多小伙伴不知道问我,可以在这里下载。不过我更建议各位小伙伴按照上述代码一个类一个类考一下,这样可以加深我们对于相册制作的了解。

上次我写了一个关于MVC框架怎么用在项目里,相关基类的封装方法。其实就是为今天的这篇文章做准备。相册,也就是图片选择器,在我们的项目中用的还是比较多的。但就我了解,多数程序员还是通过引用第三方框架实现这个功能。但是如果UI有要求,或者我们自己有什么特殊需要,可能根本无法满足。所以今天我给大家带来自己做的相册。既给你一个完整的相册,也给你一套做相册的方法。同时我们可以从其中学到很多的知识点。在开始之前要说明一下,这个相册要依赖之前讲的MVC框架,所以有两种选择,一种是把那个框架先搭建好,然后无感复制粘 activity_main.xml文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools...
该项目实现的功能如下: 获取手机相册,点击每个相册之后进入该相册的图片列表界面,在图片列表界面可以实现图片多选,然后进入所选择的图片界面,在该界面内可以实现所选图片的上传等功能。 该项目最大特色: 1、获取相册列表,目前网络上面介绍获取相册的项目很少,本文专门讲述相册的获取。 2、使用Android-Universal-Image-Loader集成框架-第三方jar包加载本地图片,熟悉这个jar的开发者肯定不陌生,该jar包十分强大,除了可以获取网络图片,本地图片也是可以的。同时,通过引用第三方jar可以有效解决OOM异常问题。 博文地址:http://blog.csdn.net/u010156024/article/details/44136543 欢迎访问!【握手】
做java开发的也会接触到android开发。前段时间我在做android开发时候遇到一个问题就是自定义相册。 其实简单来说分两步:第一,先查询出本机所有图片的路径。第二,把查询出的路径放到GridView控件里面。 接下来上具体的代码了: 1.取图片路径: * 图片文件路径 private ArrayList<String> filePath = new Array...
最近找工作,所以闲余时间还是比较充足的,今晚刚好没有睡意,抽取了一个之前项目中自己写过的一个功能。更换头像,可以拍照,可以从相册选取照片,有裁剪功能。其中的一个坑是PopupWindow的显示位置以及是否全屏显示适配不同手机。 话不多少,先上效果。
这个demo虽然实现了圆形头像加载,但是我们今天重点不是说原型头像,重点说加载手机相册图片,相信大家项目中肯定会用到; 如果大家想了解圆形头像加载可以直接下载源码查看,或者看这篇博客圆形头像讲解 首先说下思路: 大家可以看到展示相册的布局使用的GridView, 我们需要知道两点就可以很容易的完成这个demo; 第一:怎么加载系统相册? 第二:怎么讲GridView的item宽高设置成
import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawa... 1. 在 AndroidManifest.xml 文件中添加以下权限: <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> 2. 在需要调起相册的地方,使用以下代码: Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(intent, REQUEST_CODE); 其中,REQUEST_CODE 是您自定义的请求码,用于在 onActivityResult() 方法中接收相册返回的数据。 3. 在 onActivityResult() 方法中,获取相册返回的数据: @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE && resultCode == RESULT_OK && data != null) { Uri uri = data.getData(); // 处理获取到的图片 uri 以上就是调起相册的基本步骤,希望能对您有所帮助。