首页 > 代码库 > Android瀑布流,解决oom

Android瀑布流,解决oom

这是一个Android瀑布流的实现demo。

瀑布流我的实现是定义三个linearlayout,然后向里面addView(),如果多了会出现oom异常,所以做了一些处理。

1.lrucache缓存

2.只显示当前屏的图片

3.滑动过程中不加载图片

4.大图缩放成小图


直接看代码:

PhotoFallScrollView.java主类 自定义的ScrollView.

package com.pangzaifei.falls;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;

/**
 * 瀑布流类
 * 
 * 原理: 1:创建3个linearlayout,设置他们的宽度,将获得的图片压缩成和3个linearlayout一样的宽度,
 * 然后根据3个linearlayout的高度来判断,将bitmap添加到哪一个linearlayout中
 * 2:翻页处理,根据手势抬起的位置和滑动的末尾处来进行翻页
 * 
 * @author pangzf
 * @date 2014年7月15日 上午10:33:05
 */
public class PhotoFallScrollView extends ScrollView implements OnTouchListener {
    /**
     * 页数
     */
    private static int page;
    /**
     * 每页显示多少张
     */
    private static final int PAGE_SIZE = 8;
    private Context mContext;
    /**
     * 数据源图片
     */
    private Images mImagesThoumb;
    /**
     * task请求集合
     */
    private Set<DownLoadTask> mTasks;
    boolean isFirstEntr = true;
    private LinearLayout mFirstColumn;
    private LinearLayout mSecondColumn;
    private LinearLayout mThirdColumn;
    private int mFirstColumnHeight;
    private int mSecondColumnHeight;
    private int mThirdColumnHeight;
    private int mClolumnWidth;

    private long mDelay = 5;
    /**
     * 上次滑动的最后位置
     */
    private static int lastScrollY = -1;

    /**
     * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次
     */
    private boolean loadOnce;
    /**
     * 存放图片的集合
     */
    private List<ImageView> mImageViewList = new ArrayList<ImageView>();

    public PhotoFallScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mContext = context;
        init();
    }

    public PhotoFallScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        init();
    }

    public PhotoFallScrollView(Context context) {
        super(context);
        this.mContext = context;
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        mImagesThoumb = Images.getInstance();
        mTasks = new HashSet<DownLoadTask>();
        setOnTouchListener(this);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        // 第一次进入就加载第一页的图片
        if (changed && !loadOnce) {
            mScrollViewHeight = this.getHeight();
            mScrollLayout = this.getChildAt(0);
            mFirstColumn = (LinearLayout) findViewById(R.id.first_column);
            mSecondColumn = (LinearLayout) findViewById(R.id.second_column);
            mThirdColumn = (LinearLayout) findViewById(R.id.third_column);
            mClolumnWidth = mFirstColumn.getWidth();
            loadOnce = true;
            loadMoreImages();
        }
    }

    /**
     * 加载图片
     */
    private void loadMoreImages() {
        if (hashSdcard()) {

            // 根据页数加载图片
            int startIndex = page * PAGE_SIZE;
            int endIndex = page * PAGE_SIZE + PAGE_SIZE;

            if (startIndex < mImagesThoumb.imageThumbs.length) {
                if (endIndex > mImagesThoumb.imageThumbs.length) {
                    endIndex = mImagesThoumb.imageThumbs.length;
                }
                for (int i = startIndex; i < endIndex; i++) {
                    String imageUrl = mImagesThoumb.imageThumbs[i].toString();
                    if (imageUrl != null && !"".equals(imageUrl)) {
                        downLoadData(imageUrl);
                    }
                }
                page++;
            } else {
                Toast.makeText(mContext, "没有更多图片了", 0).show();
            }
        } else {
            Toast.makeText(mContext, "无sdcard", 0).show();
        }
    }

    /**
     * 下载
     * 
     * @param imageUrl
     */
    private void downLoadData(String imageUrl) {
        DownLoadTask task = new DownLoadTask();
        mTasks.add(task);
        task.execute(imageUrl);
    }

    public class DownLoadTask extends AsyncTask<String, String, Bitmap> {

        private String mImageUrl;

        @Override
        protected Bitmap doInBackground(String... params) {
            try {
                mImageUrl = params[0];
                Bitmap bitmapFromMemory = mImagesThoumb
                        .getMemoryCache(mImageUrl);
                if (bitmapFromMemory != null) {
                    return bitmapFromMemory;
                }
                if (hashSdcard()) {
                    Bitmap bitmap = loadImage(mImageUrl);
                    return bitmap;
                } else {
                    Toast.makeText(mContext, "无sdcard,无法获取图片", 0).show();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            // 展示图片
            if (bitmap != null) {
                // 1.缩放图片
                // 2.新建ImageView
                // 3.找到需要的linerlayout添加imageView
                float width = bitmap.getWidth();
                float radio = width / mFirstColumn.getWidth();
                float scaleHeight = bitmap.getHeight() / radio;
                addImage(bitmap, mFirstColumn.getWidth(), scaleHeight);
            }
            mTasks.remove(this);
        }

        /**
         * 将图片添加到linearlayout中
         * 
         * @param bitmap
         * @param scaleHeight
         */
        public void addImage(Bitmap bitmap, float width, float scaleHeight) {
            // 生成缩放的iv
            ImageView iv = new ImageView(mContext);
            android.view.ViewGroup.LayoutParams params = new LayoutParams(
                    (int) width, (int) scaleHeight);
            iv.setLayoutParams(params);
            if (bitmap != null) {
                // 解决默认图片有大有小的问题
                iv.setScaleType(ScaleType.FIT_XY);
                iv.setPadding(5, 5, 5, 5);

                iv.setImageBitmap(bitmap);
                iv.setTag(R.string.iamgurl, mImageUrl);
                findColumnToAdd(iv, (int) scaleHeight).addView(iv);
                mImageViewList.add(iv);
            }
        }

    }

    private Bitmap downLoad(String imageUrl) throws IOException {
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        HttpURLConnection conn = null;
        File imageFile = null;
        try {
            URL url = new URL(imageUrl);
            conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(10000);
            conn.setConnectTimeout(5000);
            conn.setDoInput(true);
            conn.setDoOutput(true);
            InputStream is = conn.getInputStream();
            imageFile = new File(getImagePath(imageUrl));
            bis = new BufferedInputStream(is);
            fos = new FileOutputStream(imageFile);
            bos = new BufferedOutputStream(fos);
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
                bos.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                bis.close();
            }
            if (bos != null) {
                bos.close();
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
        // 如果imageFile不为null,将图片添加到memory中
        if (imageFile != null) {
            Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getPath());
            mImagesThoumb.addBitmapToMemoryCache(imageUrl, bitmap);
            return bitmap;
        }
        return null;

    }

    /**
     * 判断图片sdcard是否有图片,如果有就用,没有就下载
     * 
     * @param mImageUrl
     * @return
     */
    public Bitmap loadImage(String mImageUrl) throws Exception {
        File file = new File(getImagePath(mImageUrl));
        if (!file.exists()) {
            downLoad(mImageUrl);
        }

        if (mImageUrl != null) {
            // 处理本地图片,设置大小防止oom
            Bitmap bitmap = mImagesThoumb.decodeSimpleBitMapFromResource(
                    file.getPath(), mClolumnWidth);
            // Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
            if (bitmap != null) {
                mImagesThoumb.addBitmapToMemoryCache(mImageUrl, bitmap);
                return bitmap;
            }
        }
        return null;
    }

    /**
     * 查找要添加的column
     * 
     * @param iv
     */
    private LinearLayout findColumnToAdd(ImageView iv, int imageHeight) {
        if (mFirstColumnHeight <= mSecondColumnHeight) {
            if (mFirstColumnHeight <= mThirdColumnHeight) {
                iv.setTag(R.string.border_top, mFirstColumnHeight);
                mFirstColumnHeight += imageHeight;
                iv.setTag(R.string.border_bottom, mFirstColumnHeight);
                return mFirstColumn;
            }
            iv.setTag(R.string.border_top, mThirdColumnHeight);
            mThirdColumnHeight += imageHeight;
            iv.setTag(R.string.border_bottom, mThirdColumnHeight);
            return mThirdColumn;

        } else {
            if (mSecondColumnHeight <= mThirdColumnHeight) {
                iv.setTag(R.string.border_top, mSecondColumnHeight);
                mSecondColumnHeight += imageHeight;
                iv.setTag(R.string.border_bottom, mSecondColumnHeight);
                return mSecondColumn;
            }
            iv.setTag(R.string.border_top, mThirdColumnHeight);
            mThirdColumnHeight += imageHeight;
            iv.setTag(R.string.border_bottom, mThirdColumnHeight);
            return mThirdColumn;
        }
    }

    /**
     * 获得file地址
     * 
     * @param imageUrl
     * @return
     */
    private String getImagePath(String imageUrl) {
        int lastIndexOf = imageUrl.lastIndexOf("/");
        String imageName = imageUrl.substring(lastIndexOf + 1);
        String imageDir = Environment.getExternalStorageDirectory().getPath()
                + "/pangzaifei/";
        File file = new File(imageDir);
        if (!file.exists()) {
            file.mkdir();
        }
        String imagePath = imageDir + imageName;
        return imagePath;
    }

    /**
     * 获得图片的名字
     * 
     * @param imageUrl
     */

    private boolean hashSdcard() {
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            return true;
        }
        return false;
    }

    @Override
    /**
     * 当手势抬起时,开始每个5毫秒计算位置
     */
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            // 发送handler
            Message msg = mHandler.obtainMessage();
            msg.obj = this;
            mHandler.sendMessageDelayed(msg, mDelay);
        }
        return false;
    }

    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 判断是否已经滑到了最低处,如果滑到了最低处,则加载更多页面,否则继续发送handler扫描
            PhotoFallScrollView scrollView = (PhotoFallScrollView) msg.obj;
            int scrollY = scrollView.getScrollY();
            if (scrollY == lastScrollY) {
                if (mScrollViewHeight + scrollY >= mScrollLayout.getHeight()
                        && mTasks.isEmpty()) {
                    scrollView.loadMoreImages();
                }
                scrollView.checkVisibile();
            } else {
                lastScrollY = scrollY;
                Message message = new Message();
                message.obj = scrollView;
                mHandler.sendMessageDelayed(message, mDelay);
            }
        }

    };
    private int mScrollViewHeight;
    private View mScrollLayout;

    /**
     * 想不可见的变为空图片
     */
    protected void checkVisibile() {
        if (mImageViewList != null && mImageViewList.size() > 0) {
            for (int i = 0; i < mImageViewList.size(); i++) {
                ImageView iv = mImageViewList.get(i);
                int borderTop = (int) iv.getTag(R.string.border_top);
                int borderBottom = (int) iv.getTag(R.string.border_bottom);
                if (borderBottom > getScrollY()
                        && borderTop < getScrollY() + mScrollViewHeight) {
                    String imageUrl = (String) iv.getTag(R.string.iamgurl);
                    if (imageUrl != null && !"".equals(imageUrl)) {
                        Bitmap bitmap = mImagesThoumb.getMemoryCache(imageUrl);
                        if (bitmap != null) {
                            iv.setImageBitmap(bitmap);
                        } else {
                            downLoadData(imageUrl);
                        }
                    }
                } else {
                    iv.setImageResource(R.drawable.empty_photo);
                }

            }
        }
    }
}
上面是主要的东西,思路和注释已经添加。其中imageView中的tag我要解释一下,主要保存了每一列的上边距和下边距和图片的url,这个方法,就是只显示当前页的图片。

布局文件:

<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" >

    <com.pangzaifei.falls.PhotoFallScrollView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <LinearLayout
                android:id="@+id/first_column"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical" >
            </LinearLayout>

            <LinearLayout
                android:id="@+id/second_column"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical" >
            </LinearLayout>

            <LinearLayout
                android:id="@+id/third_column"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical" >
            </LinearLayout>
        </LinearLayout>
    </com.pangzaifei.falls.PhotoFallScrollView>

</LinearLayout>

效果图:



源码地址:

http://download.csdn.net/detail/pangzaifei/7639423