这是一个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
Android瀑布流,解决oom
时间: 2024-10-11 00:04:46