首页 > 代码库 > android瀑布流照片墙实现代码详解

android瀑布流照片墙实现代码详解

照片墙的实现,是需要往手机里面添加很多图片的,如果没有对资源进行合理的释放,程序很快就会出现OOM.所以需要用到LruCache算法来缓存图片.

1,首先是图片资源类,这个类中包含了很多图片链接.
public class AllImages {
    public final static String[] imageUrls = new String[] {
            "http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg" };
}
2.有了图片,还需要一个图片的工具类.新建一个ImageTools.
//图片处理的工具类
public class ImageTools {
    //图片缓存,用于缓存所有下载好的图片
    private static LruCache<String, Bitmap> mMemoryCache;
    
    //ImageTools实例
    private static ImageTools imageTools;
    
    public ImageTools() {
        //获取应用程序的最大可用内存
        int maxMemory=(int)Runtime.getRuntime().maxMemory();
        int cacheMemory=maxMemory/8;
        mMemoryCache=new LruCache<String, Bitmap>(cacheMemory){
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };
    }
    //获取ImageTools的实例
    public static ImageTools getInstance(){
        if(imageTools==null){
            imageTools=new ImageTools();
        }
        return imageTools;
    }
    //将图片放入LruCache中
    public void addBitmapToMemoryCache(String key,Bitmap bitmap){
        if(getBitmapFromMemoryCache(key)==null){
            mMemoryCache.put(key, bitmap);
        }
    }
    //从LruCache中获取一张图片,如果不存在就返回null
    public Bitmap getBitmapFromMemoryCache(String key){
        return mMemoryCache.get(key);
    }
    //求出图片需要压缩的比例
    public static int calculateInSampleSize(BitmapFactory.Options option,int reqWidth){
        //得到原图片的宽度
        final int width=option.outWidth;
        int inSampleSize=1;
        if(width>reqWidth){
            //计算出实际宽度与目标宽度的比例
            final int widthRadio=Math.round((float)width/(float)reqWidth);
            inSampleSize=widthRadio;
        }
        return inSampleSize;
    }
    //压缩图片
    public static Bitmap decodeSampedBitmapFromResource(String pathName,int reqWidth){
        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
        final BitmapFactory.Options options=new Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeFile(pathName, options);
        options.inSampleSize = calculateInSampleSize(options, reqWidth);
        // 使用获取到的inSampleSize值再次解析图片
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(pathName, options);
    }
}
这里将ImageTools设为单例,并在构造函数中初始化了LruCache类,提供了操作LruCache的读取和增加方法  以及两个对图片进行处理的方法.
3.接下来就是最重要的MyScrollView类.核心类.
public class MyScrollView extends ScrollView  implements OnTouchListener {
    //每页要加载的图片数量
    public static final int PAGE_SIZE=15;
    //记录当前加载到了第几页
    private int page;
    //每一列的宽度
    private int columnWidth;
    //第一列的高度
    private int firstColumnHeight;
    //第二列的高度
    private int secondColumnHeight;
    //第三列的高度
    private int thirdColumnHeight;
    //是否已经加载过Layout
    private boolean loadOnce;
    //对图片进行管理的工具类
    private ImageTools imageTools;
    //第一列的布局
    private LinearLayout firstColumn;
    //第二列布局
    private LinearLayout secondColumn;
    //第三列布局
    private LinearLayout thirdColumn;
    //MyScrollView下的子布局
    private static View scrollLayout;
    //MyScrollView的高度
    private static int scrollViewHeight;
    //记录上次垂直滚动的距离
    private static int lastScrollY=-1;
    //记录界面上所有的图片
    private List<ImageView> imageList=new ArrayList<ImageView>();
    
    //记录所有正在下载的任务
    private static Set<LoadImageTask> imageTaskList;
    
    private static Handler mh=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            MyScrollView mScrollView=(MyScrollView) msg.obj;
            int scrollY=mScrollView.getScrollY();
            if(scrollY==lastScrollY){  //如果当前滚动位置与上次滚动位置相同,则说明滚动停止
                // 当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片
                if(scrollViewHeight+scrollY>=scrollLayout.getHeight()&&imageTaskList.isEmpty()){
                    mScrollView.loadMoreImage();
                }
                mScrollView.checkVisiblility();
            }
            else{
                lastScrollY=scrollY;
                Message message = new Message();
                message.obj = mScrollView;
                // 5毫秒后再次对滚动位置进行判断
                mh.sendMessageDelayed(message, 5);
            }
        }
    };
    
    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        imageTools=ImageTools.getInstance();
        imageTaskList=new HashSet<MyScrollView.LoadImageTask>();
        setOnTouchListener(this);
    }
    
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(event.getAction()==MotionEvent.ACTION_UP){
            Message msg=new Message();
            msg.obj=this;
            mh.sendMessageDelayed(msg,5);
        }
        return false;
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if(changed&&!loadOnce){
            scrollViewHeight=getHeight();
            scrollLayout=getChildAt(0);
            firstColumn=(LinearLayout) findViewById(R.id.first_column);
            secondColumn=(LinearLayout) findViewById(R.id.second_column);
            thirdColumn=(LinearLayout) findViewById(R.id.third_column);
            columnWidth=firstColumn.getWidth();
            loadOnce=true;
            loadMoreImage();
        }
    }
    
    //加载更多图片
    public void loadMoreImage(){
        if(hasSDCard()){
            int startIndex=PAGE_SIZE*page;
            int endIndex=PAGE_SIZE*page+PAGE_SIZE;
            if(startIndex<AllImages.imageUrls.length){  //判断图片是否已经加载完毕
                Toast.makeText(getContext(), "正在加载",Toast.LENGTH_SHORT).show();
                if(endIndex>AllImages.imageUrls.length){
                    endIndex=AllImages.imageUrls.length;
                }
                for (int i = startIndex; i < endIndex; i++) {
                    LoadImageTask task=new LoadImageTask();
                    imageTaskList.add(task);
                    task.execute(AllImages.imageUrls[i]);
                }
                page++;
            }
            else{
                Toast.makeText(getContext(), "加载完毕",Toast.LENGTH_SHORT).show();
            }
        }
        else{
            Toast.makeText(getContext(), "没有内存卡",Toast.LENGTH_SHORT).show();
        }
    }
    
    //对图片的可见性进行检查,如果已经移出视线外,则将图片设为一张空白图片
    public void checkVisiblility(){
        for (int i = 0; i < imageList.size(); i++) {
            ImageView imageView=imageList.get(i);
            int border_top=(Integer) imageView.getTag(R.string.border_top);
            int border_bottom=(Integer) imageView.getTag(R.string.border_bottom);
            if(border_bottom>getScrollY()&&border_top<getScrollY()+scrollViewHeight){   //检查是不是在可见视图之内
                String imageUrl=(String) imageView.getTag(R.string.image_url);
                Bitmap imageBitmap=imageTools.getBitmapFromMemoryCache(imageUrl);
                if(imageBitmap==null){
                    LoadImageTask task=new LoadImageTask(imageView);
                    task.execute(imageUrl);
                }
                else{
                    imageView.setImageBitmap(imageBitmap);
                }
            }
            else{  //如果已经不再屏幕内
                imageView.setImageResource(R.drawable.ic_launcher);
            }
        }
    }
    
    //判断手机是否有SD卡
    private boolean hasSDCard(){
        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    }
    
    //异步加载图片的任务
    class LoadImageTask extends AsyncTask<String, Void, Bitmap>{
        //图片地址Url
        private String imageUrl;
        //可重复使用的ImageView
        private ImageView mImageView;
        
        public LoadImageTask() {
            
        }
        //将可重复使用的ImageView传入
        public LoadImageTask(ImageView imageView) {
            this.mImageView=imageView;
        }
        
        @Override
        protected Bitmap doInBackground(String... params) { //根据imageUrl作为key值查找Bitmap,如果没有,调用loadImage去加载
            imageUrl=params[0];
            Bitmap imageBitmap=imageTools.getBitmapFromMemoryCache(imageUrl);
            if(imageBitmap==null){
                imageBitmap=loadImage(imageUrl);
            }
            return imageBitmap;
        }
        @Override
        protected void onPostExecute(Bitmap imageBitmap) {
            if(imageBitmap!=null){
                double ratio=imageBitmap.getWidth()/(columnWidth*1.0);   //得到宽度的压缩比
                int scaleHeight=(int)(imageBitmap.getHeight()/ratio);    //根据宽度的压缩比  得到这张图片的高度
                addImage(imageBitmap, columnWidth, scaleHeight);
            }
            imageTaskList.remove(this);
        }
        
        //根据图片的Url去加载图片   首先判断该图片是否在内存卡里面,如果没有,则去下载,如果有,则经过压缩处理后返回
        private Bitmap loadImage(String imageUrl){
            File imageFile=new File(getImagePath(imageUrl));
            if(!imageFile.exists()){
                downloadImage(imageUrl);
            }
            if(imageUrl!=null){
                Bitmap bitmap=ImageTools.decodeSampedBitmapFromResource(imageFile.getPath(), columnWidth);
                if(bitmap!=null){
                    imageTools.addBitmapToMemoryCache(imageUrl, bitmap);
                    return bitmap;
                }
            }
            return null;
        }
        
        
        //向ImageView中添加一张图片
        private void addImage(Bitmap bitmap,int imageWidth,int imageHeight){
            LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(imageWidth, imageHeight);
            if(mImageView!=null){
                mImageView.setImageBitmap(bitmap);
            }
            else{
                ImageView imageView=new ImageView(getContext());
                imageView.setLayoutParams(params);
                imageView.setImageBitmap(bitmap);
                imageView.setScaleType(ScaleType.FIT_XY);
                imageView.setPadding(5, 5, 5, 5);
                imageView.setTag(R.string.image_url,imageUrl);
                findColumnToAdd(imageView, imageHeight).addView(imageView);
                imageList.add(imageView);
                }
        }
        
        
        //找到应该添加图片的一列,选取三列中高度最小的一列返回.
        private LinearLayout findColumnToAdd(ImageView imageView,int imageHeight){
            if(firstColumnHeight<=secondColumnHeight){
                if(firstColumnHeight<=thirdColumnHeight){
                    imageView.setTag(R.string.border_top,firstColumnHeight);
                    firstColumnHeight+=imageHeight;
                    imageView.setTag(R.string.border_bottom,firstColumnHeight);
                    return firstColumn;
                }
                else{
                    imageView.setTag(R.string.border_top,thirdColumnHeight);
                    thirdColumnHeight+=imageHeight;
                    imageView.setTag(R.string.border_bottom,thirdColumnHeight);
                    return thirdColumn;
                }
            }
            else{
                if(secondColumnHeight<=thirdColumnHeight){
                    imageView.setTag(R.string.border_top,secondColumnHeight);
                    secondColumnHeight+=imageHeight;
                    imageView.setTag(R.string.border_bottomsecondColumnHeight);
                    return secondColumn;
                }
                else{
                    imageView.setTag(R.string.border_top,thirdColumnHeight);
                    thirdColumnHeight+=imageHeight;
                    imageView.setTag(R.string.border_bottom,thirdColumnHeight);
                    return thirdColumn;
                }
            }
        }
        
        //将图片下载到SD卡缓存
        private void downloadImage(String imageUrl){
            HttpURLConnection con=null;
            FileOutputStream fos=null;
            BufferedOutputStream bos=null;
            BufferedInputStream bis=null;
            File imageFile=null;
            try {
                URL url=new URL(imageUrl);
                con=(HttpURLConnection) url.openConnection();
                con.setConnectTimeout(5*1000);
                con.setReadTimeout(15*1000);
                con.setDoInput(true);
                con.setDoOutput(true);
                bis=new BufferedInputStream(con.getInputStream());
                imageFile =new File(getImagePath(imageUrl));//获得了图片的路径
                fos=new FileOutputStream(imageFile);
                bos=new BufferedOutputStream(fos);
                byte[] b=new byte[1024];
                int length;
                while((length=bis.read(b))!=-1){
                    bos.write(b, 0, length);
                    bos.flush();
                }
                
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            finally{
                    try {
                        if(bis!=null){
                            bis.close();
                        }
                        if(bos!=null){
                            bos.close();
                        }
                        if(con!=null){
                            con.disconnect();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
            }
            if(imageFile!=null){
                //裁剪图片
                Log.e("info""path---"+imageFile.getPath());
                Bitmap bitmap=ImageTools.decodeSampedBitmapFromResource(imageFile.getPath(), columnWidth);
                if(bitmap!=null){
                    imageTools.addBitmapToMemoryCache(imageUrl, bitmap);
                }
            }
        }
        
    }
    //获取图片的本地缓存路径
    private String getImagePath(String imageUrl){
        int lastIndex=imageUrl.lastIndexOf("/");
        String imageName=imageUrl.substring(lastIndex+1);
        String imageDir=Environment.getExternalStorageDirectory().getPath()+"/PhotoWallFall/";
        File file=new File(imageDir);
        if(!file.exists()){
            file.mkdirs();
        }
        String imagePath=imageDir+imageName;
        return imagePath;
    }
}  
这个自定义控件是继承自ScrollView类的,这样就允许用户可以通过滚动的方式浏览更多的图片,loadMoreImage()是专门用来浏览下一页图片的.


看一看loadMoreImages()方法的内部细节了。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一
个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如果还没
存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩
后的图片添加进去就可以了。
另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心思
想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证
程序始终不会占用过高的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从
LruCache中被移除了,就会开启一个LoadImageTask,将这张图片重新加载到内存中.

4.打开activity_main.xml
<com.example.photodemo.MyScrollView 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:id="@+id/scroll_view"
    tools:context=".MainActivity" >
    
    <LinearLayout 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <LinearLayout 
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/first_column"
            android:orientation="vertical"
            ></LinearLayout>
                <LinearLayout 
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/second_column"
            android:orientation="vertical"
            ></LinearLayout>
                        <LinearLayout 
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/third_column"
            android:orientation="vertical"
            ></LinearLayout>
        
    </LinearLayout>
</com.example.photodemo.MyScrollView>

5.当然,因为是获取网络图片,所以必须加上联网权限 ,以及将图片缓存到SD卡,所以需要写权限.
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" /> 
 




来自为知笔记(Wiz)


android瀑布流照片墙实现代码详解