【Android】瀑布流

最近写了一个瀑布流,将其分享给大家.代码分析及源码在后面贴出.
主要使用到的知识点:

  • 图片缓存
  • 弱引用
  • AsyncTask异步任务
  • 懒加载

示例效果图:
这里写图片描述

主要代码如下
MainActivity.class
package com.sg7.waterfallflow;

import android.app.Activity;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends Activity {

    private LazyScrollView waterfall_scroll;
    private LinearLayout waterfall_container;
    private ArrayList<LinearLayout> waterfall_items;
    private Display display;
    private AssetManager assetManager;
    private List<String> image_filenames;
    private final String image_path = "images";

    private int itemWidth;

    private int column_count = 3;// 显示列数
    private int page_count = 15;// 每次加载15张图片

    private int current_page = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        display = this.getWindowManager().getDefaultDisplay();
        // 根据屏幕大小计算每列大小
        itemWidth = display.getWidth() / column_count;
        assetManager = this.getAssets();

        initLayout();
    }

    private void initLayout() {
        waterfall_scroll = (LazyScrollView) findViewById(R.id.waterfall_scroll);
        waterfall_scroll.getView();
        waterfall_scroll.setOnScrollListener(new LazyScrollView.OnScrollListener() {

            @Override
            public void onTop() {
                // 滚动到最顶端
                Log.d("LazyScroll", "Scroll to top");
            }

            @Override
            public void onScroll() {
                // 滚动中
                Log.d("LazyScroll", "Scroll");
            }

            @Override
            public void onBottom() {
                // 滚动到最低端
                AddItemToContainer(++current_page, page_count);
            }
        });

        waterfall_container = (LinearLayout) this
                .findViewById(R.id.waterfall_container);
        waterfall_items = new ArrayList<>();

        for (int i = 0; i < column_count; i++) {
            LinearLayout itemLayout = new LinearLayout(this);
            LinearLayout.LayoutParams itemParam = new LinearLayout.LayoutParams(
                    itemWidth, LayoutParams.WRAP_CONTENT);
            itemLayout.setPadding(2, 2, 2, 2);
            itemLayout.setOrientation(LinearLayout.VERTICAL);

            itemLayout.setLayoutParams(itemParam);
            waterfall_items.add(itemLayout);
            waterfall_container.addView(itemLayout);
        }

        // 加载所有图片路径
        try {
            image_filenames = Arrays.asList(assetManager.list(image_path));
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 第一次加载
        AddItemToContainer(current_page, page_count);
    }

    private void AddItemToContainer(int pageindex, int pagecount) {
        int j = 0;
        int imagecount = image_filenames.size();
        for (int i = pageindex * pagecount; i < pagecount * (pageindex + 1)
                && i < imagecount; i++) {
            j = (j >= column_count) ? 0 : j;
            AddImage(image_filenames.get(i), j++);

        }

    }

    private void AddImage(String filename, int columnIndex) {
        ImageView imageView = (ImageView) LayoutInflater.from(this).inflate(
                R.layout.waterfallitem, null);
        waterfall_items.get(columnIndex).addView(imageView);
        TaskParam param = new TaskParam();
        param.setAssetManager(assetManager);
        param.setFilename(image_path + "/" + filename);
        param.setItemWidth(itemWidth);
        ImageLoaderTask task = new ImageLoaderTask(imageView);
        task.execute(param);
    }
}

第40,42行,首先获取手机屏幕宽度,然后根据列数获取每一列的宽度,43行获取一个AssetManager对象,用于从assets文件夹中获取图片资源(注:本例中所有图片均存放于本地assets目录下)
第51行设置一个滚动监听,68行是当手机滑动到最低端的时候回调.84,84行,创建column_count个的LinearLayout对象,并设置其宽度为itemWidth,并添加到集合waterfall_items中,同时添加到waterfall_container容器当中.
第90行,获取所有图片的地址.98行,每次加载显示column_count个图片.
第117行,使用ImageLoaderTask异步加载的方式加载图片.

TaskParam.class
package com.sg7.waterfallflow;

import android.content.res.AssetManager;

/**
 * 任务参数
 */
public class TaskParam {
    private String filename;
    private AssetManager assetManager;
    private int ItemWidth;

    public String getFilename() {
        return filename;
    }

    public void setFilename(String filename) {
        this.filename = filename;
    }

    public AssetManager getAssetManager() {
        return assetManager;
    }

    public void setAssetManager(AssetManager assetManager) {
        this.assetManager = assetManager;
    }

    public int getItemWidth() {
        return ItemWidth;
    }

    public void setItemWidth(int itemWidth) {
        ItemWidth = itemWidth;
    }
}

这个类不多讲了,存放请求参数的

ImageLoaderTask.class
package com.sg7.waterfallflow;

import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;

import java.lang.ref.WeakReference;

public class ImageLoaderTask extends AsyncTask<TaskParam, Void, Bitmap> {

    private TaskParam param;
    /**
     * 弱引用,防止内存溢出
     */
    private final WeakReference<ImageView> imageViewReference;

    public ImageLoaderTask(ImageView imageView) {
        imageViewReference = new WeakReference<>(imageView);
    }

    @Override
    protected Bitmap doInBackground(TaskParam... params) {
        param = params[0];
        return loadImageFile(param.getFilename(), param.getAssetManager());
    }

    private Bitmap loadImageFile(final String filename, final AssetManager manager) {
        try {
            Bitmap bmp = BitmapCache.getInstance().getBitmap(filename, manager);
            return bmp;
        } catch (Exception e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        }
        return null;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }
        ImageView imageView = imageViewReference.get();
        if (imageView != null) {
            if (bitmap != null) {
                // 获取真实宽高
                int width = bitmap.getWidth();
                int height = bitmap.getHeight();

                LayoutParams lp = imageView.getLayoutParams();
                // 调整高度
                lp.height = (height * param.getItemWidth()) / width;

                imageView.setLayoutParams(lp);

                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

ImageLoaderTask继承AsyncTask,这是一个异步任务,主要耗时处理在27行,在58行将图片显示在ImageView上,完全了使命.
第21行,对ImageView使用弱引用,以防止内在溢出
第32行,使用BitmapCache来加载图片,并对图片进行了缓存

BitmapCache.class
package com.sg7.waterfallflow;

import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Hashtable;

/**
 * 防止溢出
 */
public class BitmapCache {
    /**
     * 用于Chche内容的存储
     */
    private Hashtable<String, BtimapRef> bitmapRefs;
    /**
     * 垃圾Reference的队列(所引用的对象已经被回收,则将该引用存入队列中)
     */
    private ReferenceQueue<Bitmap> queue;

    private static BitmapCache cache;

    /**
     * 取得缓存器实例
     */
    public static BitmapCache getInstance() {
        if (cache == null) {
            cache = new BitmapCache();
        }
        return cache;
    }

    private BitmapCache() {
        bitmapRefs = new Hashtable<>();
        queue = new ReferenceQueue<>();

    }

    /**
     * 继承SoftReference,使得每一个实例都具有可识别的标识。
     */
    private class BtimapRef extends SoftReference<Bitmap> {
        private String key = "";

        public BtimapRef(Bitmap bmp, ReferenceQueue<Bitmap> queue, String key) {
            super(bmp, queue);
            this.key = key;
        }
    }

    /**
     * 依据所指定的文件名获取图片
     */
    public Bitmap getBitmap(String filename, AssetManager assetManager) {

        Bitmap bitmapImage = null;
        // 缓存中是否有该Bitmap实例的软引用,如果有,从软引用中取得。
        if (bitmapRefs.containsKey(filename)) {
            BtimapRef ref = bitmapRefs.get(filename);
            bitmapImage = ref.get();
        }
        // 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,并保存对这个新建实例的软引用
        if (bitmapImage == null) {
            BufferedInputStream buf;
            try {
                buf = new BufferedInputStream(assetManager.open(filename));
                bitmapImage = BitmapFactory.decodeStream(buf);
                this.addCacheBitmap(bitmapImage, filename);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bitmapImage;
    }

    /**
     * 以软引用的方式对一个Bitmap对象的实例进行引用并保存该引用
     */
    private void addCacheBitmap(Bitmap bmp, String key) {
        cleanCache();
        BtimapRef ref = new BtimapRef(bmp, queue, key);
        bitmapRefs.put(key, ref);
    }

    /**
     * 清除垃圾引用
     */
    private void cleanCache() {
        BtimapRef ref = null;
        while ((ref = (BtimapRef) queue.poll()) != null) {
            bitmapRefs.remove(ref.key);
        }
    }
}

这个图片加载类,直接看注释
最后说说这个懒加载类LazyScrollView

LazyScrollView.class
package com.sg7.waterfallflow;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;

/**
 * 懒加载ScrollView
 */
public class LazyScrollView extends ScrollView {
    private static Handler handler;
    private View view;

    public LazyScrollView(Context context) {
        super(context);
    }

    public LazyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public LazyScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
    }

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    /**
                     * getScrollY():已经滚动到屏幕上面的长度
                     * getHeight() : 此控件在布局中的长度,一个固定值
                     * view.getMeasuredHeight() : 子控件的长度值
                     */
                    if (view.getMeasuredHeight() <= getScrollY() + getHeight()) {
                        if (onScrollListener != null) {
                            onScrollListener.onBottom();
                            Log.i("aaaa", getScrollY() + ":" + view.getMeasuredHeight() + ":" + getHeight());
                            /**
                             * 12-16 23:35:57.571 9780-9780/? I/aaaa: 1030:2254:1230
                             * 12-16 23:36:37.161 9780-9780/? I/aaaa: 2784:4014:1230
                             * 12-16 23:37:10.211 9780-9780/? I/aaaa: 4623:5849:1230
                             */
                        }

                    } else if (getScrollY() == 0) {
                        //滚动到最顶端
                        if (onScrollListener != null) {
                            onScrollListener.onTop();
                        }
                    } else {
                        if (onScrollListener != null) {
                            onScrollListener.onScroll();
                        }
                    }
                    break;
                default:
                    break;
            }
        }
    }

    private void init() {
        //给自己设置一个ontouch监听
        this.setOnTouchListener(onTouchListener);
        handler = new MyHandler();
    }

    /**
     * 触摸监听
     */
    OnTouchListener onTouchListener = new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_UP:
                    if (view != null && onScrollListener != null) {
                        handler.sendMessageDelayed(handler.obtainMessage(1), 200);
                    }
                    break;
                default:
                    break;
            }
            return false;
        }
    };

    /**
     * 获得参考的View,主要是为了获得它的MeasuredHeight,然后和滚动条的ScrollY+getHeight作比较。
     */
    public void getView() {
        this.view = getChildAt(0);
        if (view != null) {
            init();
        }
    }

    /**
     * 定义接口
     *
     * @author admin
     */
    public interface OnScrollListener {
        void onBottom();

        void onTop();

        void onScroll();
    }

    private OnScrollListener onScrollListener;

    public void setOnScrollListener(OnScrollListener onScrollListener) {
        this.onScrollListener = onScrollListener;
    }
}

继承于ScrollView类,说明是一个自定义控件
第76行,给本身设置了onTouchListener监听,91行,每一次抬起手指的时候都会通过handler延时200ms发送一个消息给handler去处理,其中处理逻辑在39-66行.其中注释也已经比较清楚了,大家可以自己看一看.
好,瀑布流的示例代码分析到此结束.
源码下载(AndroidStudio项目):http://download.csdn.net/detail/lingwu7/9366161

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值