欢迎使用CSDN-markdown编辑器

Android相册实现

主要原理:

  • LruCache缓存技术
  • ImageLoad异步加载技术
  • ContentProvider获取手机所有图片
  • GridView显示图片
  • CommonAdapter实现Adapter的通用适配

ContentProvider获取手机所有图片

  • 使用ContentProvider获取所有图片
  • MediaStore.Images.Media.MIME_TYPE为Image格式的MIME类型

    private void getImages() {
        imgs = new ArrayList<String>();
        Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            String key_MIME_TYPE = MediaStore.Images.Media.MIME_TYPE;
            String key_DATA = MediaStore.Images.Media.DATA;
            ContentResolver mContentResolver = getContentResolver();
            Cursor cursor = mContentResolver.query(mImageUri, new String[] { key_DATA },
                    key_MIME_TYPE + "=? or " + key_MIME_TYPE + "=? or " + key_MIME_TYPE + "=?",
                    new String[] { "image/jpg", "image/jpeg", "image/png" }, MediaStore.Images.Media.DATE_MODIFIED);
            if (cursor != null) {
                if (cursor.moveToLast()) {
                    imgs = new ArrayList<String>();
                    while (true) {
                        String path = cursor.getString(0);
                        imgs.add(path);
                        if (imgs.size() >= 10000 || !cursor.moveToPrevious()) {
                            break;
                        }
                    }
                }
                cursor.close();
            }
    }
    

使用LruCache缓存技术

  • LruCache类是该技术的核心,他的创建方法如下:

    int maxMemory = (int) Runtime.getRuntime().maxMemory();
    int cacheSize = maxMemory / 8;
    LruCache lc = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
    
  • cacheSize为使用的最大内存,一般使用应用程序所允许的最大内存的1/8

  • 当内存满了的时候他能自动地删除最近不常使用的资源
  • 他的主要方法和Map类似为 put()和get(),保存图片资源时可以使用图片的路径作为key,Bitmap作为value。当想要使用某一张图片时,先通过路径索引lc,若没有获得则根据路径创建bitmap然后再把bitmap存入lc中

Bitmap的压缩技术

  • 用一个小分辨率的ImageView来显示大分辨率的Bitmap不会有什么改变反而浪费了宝贵的内存,所以可以先把Bitmap压缩一定大小就可以节省这一部分内存
  • 实现Bitmap压缩的API是BitmapFactory.Options,它可以在Bitmap加载的时候传进去,当我们把option.inJustDecodeBounds = true时,BitmapFactory加载Bitmap时不会生成Bitmap对象,但是可以获得Bitmap的宽高,我们就是根据这个宽高来进行压缩。option还有一个参数inSampleSize,当它是1时Bitmap大小不会改变,它是2时,Bitmap大小减少一倍,以此类推。这样,我们就可以得到压缩Bitmap的方法,下面是实现任意比例压缩的方法

    public static int calculateInSampleSize(BitmapFactory.Options option, int reqWidth, int reqHeight) {
        int inSampleSize = 1;
        int width = option.outWidth;
        int height = option.outHeight;
        if (width > reqWidth || height > reqHeight) {
            int halfWidth = width / 2;
            int halfHeight = height / 2;
            while ((halfWidth / inSampleSize) > reqWidth && (halfHeight / inSampleSize) > reqHeight) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
    
    public static Bitmap decodeSampleBitmapFromPath(String path, int reqWidth, int reqHeight) {
        Bitmap bitmap = null;
        BitmapFactory.Options option = new Options();
        //获取Bitmap的宽高
        option.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, option);
        int inSampleSize = calculateInSampleSize(option, reqWidth, reqHeight);
        option.inJustDecodeBounds = false;
        option.inSampleSize = inSampleSize;
        bitmap = BitmapFactory.decodeFile(path, option);
        return bitmap;
    }
    

结合Lrucache实现异步缓存加载Bitmap的ImageLoad

  • 异步加载就是开启一个线程去加载bitmap,实现一个回调接口在加载完成时调用便达到异步加载
  • 关键的API为 Handler的异步回调机制
  • 我们先来定义一个接口用来回调时调用

    public interface ImageCallback {
        public void imageLoaded(Bitmap imageDrawable);
    }
    
  • 当需要加载Bitmap时,我们先在lc中找

    if (lc.get(item) != null) {
        return lc.get(item);
    }
    
  • 如果没有找到就开启一个线程在通过路径加载

    new Thread() {
        public void run() {
            try {
                final Bitmap bmp = BitmapUtils.decodeSampleBitmapFromPath(item, 100, 100);
                lc.put(item, bmp);
                mHander.post(new Runnable() {//这个运行在主线程
    
                    @Override
                    public void run() {
                        callback.imageLoaded(bmp);//加载完成后回调
    
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }.start();
    
  • 当然要先实现回调接口

    new ImageCallback() {
    
        @Override
        public void imageLoaded(Bitmap bmp) {
            if (imageView.getTag().equals(filePath)) {
                if (bmp != null) {
                    imageView.setImageBitmap(bmp);
                } else {
                    Log.d("asd", "imageLoaded");
                    imageView.setImageResource(R.drawable.empty_photo);
                }
            }
        }
    }
    
  • 下面是所有的代码

    public class ImageLoader {
        private LruCache<String, Bitmap> lc;
        private int screenW, screenH;
        private Handler mHander = new Handler();
    
        public ImageLoader() {  
            int maxMemory = (int) Runtime.getRuntime().maxMemory();
            int cacheSize = maxMemory / 8;
            lc = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getRowBytes() * value.getHeight();
                }
            };
        }
    
        private Bitmap loadDrawable(final String item, final ImageCallback callback) {
    
            if (lc.get(item) != null) {
                return lc.get(item);
            }
    
            new Thread() {
                public void run() {
                    try {
                        final Bitmap bmp = BitmapUtils.decodeSampleBitmapFromPath(item, 100, 100);
                        lc.put(item, bmp);
                        mHander.post(new Runnable() {
    
                            @Override
                            public void run() {
                                callback.imageLoaded(bmp);
    
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();
    
            return null;
        }
    
        public void loadImage(final String filePath, final ImageView imageView) {
    
            Bitmap bmp = loadDrawable(filePath, new ImageCallback() {
    
                @Override
                public void imageLoaded(Bitmap bmp) {
                    if (imageView.getTag().equals(filePath)) {
                        if (bmp != null) {
                            imageView.setImageBitmap(bmp);
                        } else {
                            Log.d("asd", "imageLoaded");
                            imageView.setImageResource(R.drawable.empty_photo);
                        }
                    }
                }
            });
    
            if (bmp != null) {
                if (imageView.getTag().equals(filePath)) {
                    imageView.setImageBitmap(bmp);
                }
            } else {
                imageView.setImageResource(R.drawable.empty_photo);
            }
    
        }
    
        public interface ImageCallback {
            public void imageLoaded(Bitmap imageDrawable);
        }
    
    }
    
  • 到处,异步加载图片的方法就介绍完成了,用的时候只要创建ImageLoad对象,然后调用它的
    public void loadImage(final String filePath, final ImageView imageView)方法就可以了

实现一个通用的ViewHolder

  • 在编写Adapter的时候,一般都会用到ViewHolder的方法,所以,实现一个通用的ViewHolder可以减少开发时间
  • 因为每个ViewHolder的控件不同,所以我们需要一个集合在保存这些控件,又为了能获得响应的控件,我们有需要可以根据标记提取控件,所以使用map的机制能很好的实现这一点。在key的选择上,我们可以选择控件的id,这样可以做到key的唯一,也能根据id来获取控件。此外,ViewHolder应该和view相联系,所以需要使用view.setTag()方法保存ViewHolder,以下是全部代码

    public class ViewHolder {
        private View mConverView;
        private SparseArray<View> mView;//使用整形数据来作为key
    
        private ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
            mView = new SparseArray<View>();
            mConverView = LayoutInflater.from(context).inflate(layoutId, parent, false);
            mConverView.setTag(this);
        }
    
        public static ViewHolder getViewHolder(Context context, ViewGroup parent,
                View convertView, int layoutId, int position) {
            if(convertView == null){
                return new ViewHolder(context, parent, layoutId, position);
            }else{
                return (ViewHolder) convertView.getTag();
            }
        }
    
        public <T extends View> T getView(int viewId) {
            View view = mView.get(viewId);
            if(view == null){
                view = mConverView.findViewById(viewId);
                mView.put(viewId, view);
            }
            return (T) view;
        }
    
        public View getConvertView() {
            return mConverView;
    
        }
    }
    
  • 使用通用的ViewHolder的方法就是在Adapter的getview中使用getViewHolder方法获得viewholder,在通过getview获得要控件,然后就可以执行了

实现一个通用的Adapter

  • 在构造方法中传入context、布局id和list,因为要适配所有的bean,所以要使用泛型。这样就可以得到一个初步的通用Adapter

    public abstract class CommontAdapter<T> extends BaseAdapter {
        private Context mContext;
        private List<T> mList;
        private int id;
        private LayoutInflater inflater;
    
        public CommontAdapter(Context context, List<T> list, int layouId) {
            mContext = context;
            mList = list;
            id = layouId;
            inflater = LayoutInflater.from(context);
        }
    
        @Override
        public int getCount() {
            return mList.size();
        }
    
        @Override
        public T getItem(int position) {
            return mList.get(position);
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    }
    
  • 可以看到我们的通用Adapter是个抽象类,我们并没有实现getView()方法,因为我们在使用Adapter的时候他的其他三个方法通常都是固定的,只有getview方法需要实现多一点的逻辑,所以我们把他留给创建对象的时候使用,这样我们就可以直接创建一个内部类实现getview方法就可以轻松地实现一个Adapter。

  • 接下来我们再看看getview还是有可以改进的部分,一般的getview基本上都是这样:

    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = getItem(position);// 获得Fruit对象
        View view;// 加载布局
        ViewHolder viewHolder = new ViewHolder();
        if(convertView == null){
            view = LayoutInflater.from(getContext()).inflate(resourceId, null);
            viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_iv);
            viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_tv);
            view.setTag(viewHolder);
        }else{
            view=convertView;
            viewHolder=(ViewHolder) view.getTag();
        }
    
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
    
        return view;
    }
    
  • 我们可以看到获得bean对象和viewholder和return都是固定不需要改变的,所以我们可以写死这一点,我们再创建一个convert的抽象方法,然后在getview中调用就可以减少很多代码

    public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder holder = ViewHolder.getViewHolder(mContext, parent, convertView, id, position);
        convert(holder, getItem(position), position);
        return holder.getConvertView();
    }
    
  • 这样我们可以直接在使用通用Adapter的时候直接内部类实现convert方法就可以了

     mListView.setAdapter(mAdapter = new CommonAdapter<String>(  
            getApplicationContext(), mDatas, R.layout.item_single_str) {  
                @Override  
                public void convert(ViewHolder c, String item)  
                {  
                    TextView view = viewHolder.getView(R.id.id_tv_title);  
                    view.setText(item);  
                }  
    
             });     
    

实现相片选择器

  • 在经过一系列的准备后,我们现在已经可以去实现一个相片选择器了
  • 我们先获得手机的所有相片和做一些初始化,如ImageLoad

    getImages();
    imageLoader = new ImageLoader();
    
  • 然后我们获得GridView并设置适配器,我们可以选择通用的适配器

    gv_photo.setAdapter(new CommontAdapter<String>(getApplicationContext(), imgs, R.layout.item_photo){
    
        @Override
        public void convert(ViewHolder holder, String item, int position) {
            if (position == 0) {
                ImageView iv = holder.getView(R.id.iv_photo);
                CheckBox cb = holder.getView(R.id.cb_photo);
                ImageView iv_cramer = holder.getView(R.id.iv_cramer_photo);
                iv_cramer.setVisibility(View.VISIBLE);
                cb.setVisibility(View.INVISIBLE);
                iv.setVisibility(View.INVISIBLE);
                iv.setOnClickListener(null);
                iv_cramer.setOnClickListener(new OnClickListener() {
    
                    @Override
                    public void onClick(View v) {
                        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                        startActivityForResult(intent, 200);
                    }
                });
                return;
            }
            ImageView iv = holder.getView(R.id.iv_photo);
            CheckBox cb = holder.getView(R.id.cb_photo);
            ImageView iv_cramer = holder.getView(R.id.iv_cramer_photo);
            iv_cramer.setVisibility(View.INVISIBLE);
            cb.setVisibility(View.VISIBLE);
            iv.setVisibility(View.VISIBLE);
            cb.setTag(R.id.cb_photo, position);
            cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    sparesMap.put((Integer) buttonView.getTag(R.id.cb_photo), isChecked);
                }
            });
            cb.setChecked(sparesMap.get(position));
            iv.setTag(item);
            imageLoader.loadImage(item, iv);
        }
    
    });
    
  • 这样我们就可以实现一个相片选择器了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值