Android开发--瀑布流效果的实现
对手机App的瀑布流效果一直有所耳闻,却从未自己亲自动手实践,趁着这几天还有些时间,做了些研究,也参考了网络上很多大神的博客,终于写出来自己的瀑布流效果了,先上一图。
正如图所示:瀑布流的原理很简单,就是自己重新写一个ScrollView,添加一个横向排布的LinearLayout,再向这个横向的LinearLayout中添加三个纵向排布的LinearLayout,接着我们就可以向每个一LinearLayout中依次添加图片。原理很容易理解,但实践起来也很困难,需要注意的问题也很多:
1.为了防止OOM,我们应当对那些不可见的图片进行回收,在这里我的思路是:如果以一个屏幕的高度为一页的话,只在当前的程序中缓存下来三页数量的图片,即当前页,前一页和后一页,这样可以提高用户体验。为了回收图片,我们就需要重写一个View来显示瀑布流中的每一图片,异步的去加载Bitmap和回收Bitmap。
2.在ScrollView滑动的过程中监听图片可见性的变化,在这里用两个数组来记录三页中每一个LinearLayout的最上端和最低端的子View的id。
3.利用一个Lrucache来加快图片的响应时间。
下面是我的代码,如果各位大神发现错误,还请多多指教。
WaterFallView瀑布流显示整体布局类
package com.example.waterfalltest.widget; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.Toast; import com.example.waterfalltest.widget.WaterFallItem.LoadCompletedListener; /** * * 需调用setup方法首先对WaterFallView进行配置初始化 * 根布局为一个横向的LinearLayout,然后根据设置的列数依次添加纵向的LinearLayout,每次将图片加进纵向LinearLayout中 * 如果以一个屏幕的高度为一页的话,只在当前的程序中缓存下来三页数量的图片 * @author acer * */ public class WaterFallView extends ScrollView { private static final String TAG = "WaterFallView"; private LinearLayout mContainerLayout; private ArrayList<LinearLayout> mLayoutsList; private final static int PAGE_COUNT = 20; private int currentPage = 0; private int colNum; private int colWidth; private int colHeight[]; private List<String> mImageUrl; private int screenHeight; private int[] topIndexArray;//记录三页中顶部Item在LinearLayout的Id private int[] bottomIndexArray;//记录三页中底部Item在LinearLayout的Id private int[] lastBottomIndexArray;//所有Item中的最低部 public WaterFallView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub init(); } public WaterFallView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub init(); } public WaterFallView(Context context) { super(context); // TODO Auto-generated constructor stub init(); } private void init() { mContainerLayout = new LinearLayout(getContext()); mContainerLayout.setBackgroundColor(Color.WHITE); mContainerLayout.setOrientation(LinearLayout.HORIZONTAL); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); addView(mContainerLayout, layoutParams); mLayoutsList = new ArrayList<LinearLayout>(); screenHeight = getResources().getDisplayMetrics().heightPixels; } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (getScrollY() + getHeight() >= computeVerticalScrollRange()) {// 已经加载到底部 loadOnpage(); return; } if (t > oldt) {// 向下滑动 for (int i = 0; i < colNum; i++) { LinearLayout layout = mLayoutsList.get(i); WaterFallItem topItem = (WaterFallItem) layout .getChildAt(topIndexArray[i]); if (topItem.getPositionY() < getScrollY() - screenHeight) { topItem.recycle(); topIndexArray[i]++; } WaterFallItem bomItem = (WaterFallItem) layout .getChildAt(Math.min(bottomIndexArray[i] - 1, lastBottomIndexArray[i])); if (bomItem.getPositionY() <= getScrollY() + 2 * screenHeight) { bomItem.reloadBitmap(); bottomIndexArray[i] = Math.min( bottomIndexArray[i] + 1, lastBottomIndexArray[i]); } } } else {// 向上滑动 for (int i = 0; i < colNum; i++) { LinearLayout layout = mLayoutsList.get(i); WaterFallItem bomItem = (WaterFallItem) layout .getChildAt(bottomIndexArray[i] - 1); if (bomItem.getPositionY() > getScrollY() + 2 * screenHeight) { bomItem.recycle(); bottomIndexArray[i]--; } WaterFallItem topItem = (WaterFallItem) layout .getChildAt(Math.max(topIndexArray[i] - 1, 0)); if (topItem.getPositionY() >= getScrollY() - screenHeight) { topItem.reloadBitmap(); topIndexArray[i] = Math.max( topIndexArray[i] - 1, 0); } } } } public void setUp(List<String> imgUrl, int col) { colNum = col; colWidth = getResources().getDisplayMetrics().widthPixels / col; mImageUrl = imgUrl; colHeight = new int[col]; bottomIndexArray = new int[col]; topIndexArray = new int[col]; lastBottomIndexArray = new int[col]; for (int i = 0; i < col; i++) { LinearLayout.LayoutParams colLayoutParams = new LinearLayout.LayoutParams( colWidth, LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout layout = new LinearLayout(getContext()); layout.setOrientation(LinearLayout.VERTICAL); mContainerLayout.addView(layout, colLayoutParams); mLayoutsList.add(layout); } loadOnpage(); } private void loadOnpage() { int end; if (mImageUrl.size() > (currentPage + 1) * PAGE_COUNT) { end = (currentPage + 1) * PAGE_COUNT; } else { end = mImageUrl.size(); } for (int i = currentPage * PAGE_COUNT; i < end; i++) { WaterFallItem item = new WaterFallItem(getContext(), colWidth - 10); item.setId(i); item.loadBitmap("images/" + mImageUrl.get(i)); item.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub WaterFallItem item = (WaterFallItem) arg0; Toast.makeText(getContext(), "click:" + item.getId(), Toast.LENGTH_SHORT).show(); } }); final int layoutId = i % colNum; item.setLoadCompletedListener(new LoadCompletedListener() { @Override public void completed(WaterFallItem waterFallItem, int height) { // TODO Auto-generated method stub LinearLayout layout = mLayoutsList.get(layoutId); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); layoutParams.setMargins(5, 10, 5, 0); layout.addView(waterFallItem, layoutParams); colHeight[layoutId] += (height + 10); waterFallItem.setPositionY(colHeight[layoutId]); lastBottomIndexArray[layoutId]++; if (currentPage == 1 && waterFallItem.getPositionY() < 2 * screenHeight) { bottomIndexArray[layoutId]++; } } }); } currentPage++; } }
WaterFallItem类,用来回收和加载图片,即图中的Item
package com.example.waterfalltest.widget; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.os.AsyncTask; import android.view.View; /** * * 瀑布流中的Item自定义View * 负责Bitmap的加载以及回收工作,并记录下所需的数据 * @author acer * */ public class WaterFallItem extends View { private static final String TAG = "WaterFallItem"; private int id; private int height;//View的高度 private int width;//View的宽度 private int positionY;//View中底部距离整个ScrollView最顶部的距离 private Paint mPaint; private Rect dstRect; private boolean isFirstLoad = true; private String mUrlString; private Bitmap mBitmap; private LoadCompletedListener loadCompletedListener; public WaterFallItem(Context context, int width) { super(context); // TODO Auto-generated constructor stub this.width = width; mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.WHITE); } public void loadBitmap(String url){ this.mUrlString = url; new LoadBitmapTask().execute(url); } public void reloadBitmap(){ isFirstLoad = false; loadBitmap(mUrlString); } /** * 防止OOM进行回收 */ public void recycle() { if (mBitmap == null || mBitmap.isRecycled()) return; new Thread(new Runnable() { @Override public void run() { if (mBitmap != null) { mBitmap.recycle(); GetBitmapUtils.removeBitmap(mUrlString); mBitmap = null; } } }).start(); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); if (mBitmap != null) { canvas.drawBitmap(mBitmap, null, dstRect, mPaint); } else if(dstRect != null) { canvas.drawRect(dstRect, mPaint); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(width, height); } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getmHeight(){ return height; } public void setLoadCompletedListener(LoadCompletedListener loadCompletedListener) { this.loadCompletedListener = loadCompletedListener; } public int getPositionY() { return positionY; } public void setPositionY(int positionY) { this.positionY = positionY; } private class LoadBitmapTask extends AsyncTask<String, Void, Void>{ @Override protected Void doInBackground(String... arg0) { // TODO Auto-generated method stub String url = arg0[0]; mBitmap = GetBitmapUtils.getBitmap(getContext(), url); if (mBitmap != null) { int bmpWidth = mBitmap.getWidth(); int bmpHeight = mBitmap.getHeight(); height = (int) (bmpHeight * width / bmpWidth); } dstRect = new Rect(0, 0, width, height); return null; } @Override protected void onPostExecute(Void result) { // TODO Auto-generated method stub super.onPostExecute(result); if (mBitmap != null) { WaterFallItem.this.invalidate(); if (isFirstLoad && loadCompletedListener != null) { loadCompletedListener.completed(WaterFallItem.this, height); } } } } public interface LoadCompletedListener { public void completed(WaterFallItem waterFallItem, int height); } }GetBitmapUtils,加载Bitmap工具类
package com.example.waterfalltest.widget; import java.io.IOException; import java.io.InputStream; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.LruCache; /** * 加载Bitmap的工具类 * @author acer * */ public class GetBitmapUtils { private static LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>( 60); public static Bitmap getBitmap(Context context, String url) { Bitmap bitmap = mLruCache.get(url); if (bitmap == null) {//可以在这里换成网络加载 InputStream inStream = null; try { inStream = context.getAssets().open(url); bitmap = BitmapFactory.decodeStream(inStream); inStream.close(); inStream = null; } catch (IOException e) { e.printStackTrace(); } if (bitmap != null) { mLruCache.put(url, bitmap); } } return bitmap; } public static void removeBitmap(String url) { mLruCache.remove(url); } }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。