欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

recyclerview实现瀑布流

程序员文章站 2022-07-12 15:20:34
...


瀑布流类似小红书App的界面  如下:

recyclerview实现瀑布流

原理(知识点):

图片宽度相同,但是图片的高度不同,如果后台上传的图片规定了尺寸的话就直接添加到imageview就可以了,如果没有的话就需要自己对图片进行等比压缩,压缩成宽度是屏幕的一半

方法如下:

 /**
     * 图片等比例压缩,按指定宽度压缩
     *
     * @param is
     * @param trgetWidth 期望的宽
     * @param out 用于磁盘缓存的输出流                  
     * @return
     */
    public static boolean compressBitemap(InputStream is, int trgetWidth,OutputStream out) {

        byte[] datas = getBytesFromStream(is);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(datas, 0, datas.length, options);
        //int outHeigh=options.outHeight;
        int outWidth = options.outWidth;
        //计算比例(图片原始高度和目标高度的比例)
        int blw = Math.round(outWidth / trgetWidth);
        //使用比较大的比例
        int bl = blw;
        //如果比例小于等于0,表示图片不进行压缩
        if (bl <= 0) {
            bl = 1;
        }
        //压缩比例
        options.inSampleSize = bl;
        options.inJustDecodeBounds = false;
        //压缩
        Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length, options);
        if (bitmap!= null){
            //计算拉伸比例
            float i = ((float) trgetWidth / bitmap.getWidth());//拉伸比例
            Matrix matrix = new Matrix();
            matrix.postScale(i, i);
            //缩放成指定宽度的图片(newBmp就是压缩后宽度为屏幕一半的图片)
            Bitmap newBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
            /**存储到磁盘===============*/
            //bitmap转换成InputStream
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //无损拉伸/缩放
            newBmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            InputStream isBm = new ByteArrayInputStream(baos.toByteArray());
            BufferedInputStream bis=null;
            BufferedOutputStream bos=null;
            try{
                bis=new BufferedInputStream(isBm);
                bos=new BufferedOutputStream(out);
                int len;
                while ((len=bis.read())!=-1){
                    bos.write(len);
                }
                bos.flush();//刷新缓存
                return true;

            }catch (Exception e){
                e.printStackTrace();
            }
        }else{
            Log.i(tag,"图片为空");
        }
        return false;
    }

其他: 

剩下的就没什么说的了,基本和在recyclerview显示图片一样

我写的时候就2用到2个知识点

1是图片的压缩

2是图片的缓存在这里我用了内存缓存和磁盘缓存,防止oom

完整代码 

Activity代码:

public class Test6Activity extends Activity {
    private Context mContext;

    private RecyclerView rv_falls;
    //图片链接
    private String[] mDatas={
            "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2056059545,3075726884&fm=27&gp=0.jpg",
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1240426408,3396216424&fm=27&gp=0.jpg",
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1765208127,2618259413&fm=27&gp=0.jpg",
            "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1447507835,3654535229&fm=27&gp=0.jpg",
            "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2716881984,3272848008&fm=27&gp=0.jpg",
            "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4056436047,3626959226&fm=27&gp=0.jpg",
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1519994047809&di=5b646d6c9d8fca47ff0749aeebf46fba&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201407%2F20%2F20140720201056_HmZ4d.jpeg",
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1286656969,4109636359&fm=27&gp=0.jpg",
            "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1928187901,3300785708&fm=27&gp=0.jpg",
            "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3941443566,3889552161&fm=27&gp=0.jpg",
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1519994047808&di=af68834c33967cd6a82bd047fbf8f809&imgtype=0&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201310%2F23%2F20131023105257_zNeA3.jpeg",
            "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1820318310,3796454650&fm=27&gp=0.jpg",
            "http://img07.tooopen.com/images/20170316/tooopen_sy_201956178977.jpg",
            "http://img.zcool.cn/community/aaa@qq.com",
            "http://pic71.nipic.com/file/20150610/13549908_104823135000_2.jpg",
            "http://pic2.nipic.com/20090424/1242397_110033072_2.jpg",
            "http://pic2.ooopic.com/12/22/95/08bOOOPICe2_1024.jpg",
            "http://img05.tooopen.com/images/20140326/sy_57640132134.jpg",
            "http://www.taopic.com/uploads/allimg/140421/318743-140421213T910.jpg",
            "http://news.cnhubei.com/ctjb/ctjbsgk/ctjb40/200808/W020080822221006461534.jpg",
            "http://img3.redocn.com/tupian/20150430/mantenghuawenmodianshiliangbeijing_3924704.jpg",
            "http://d.hiphotos.baidu.com/zhidao/pic/item/72f082025aafa40fe871b36bad64034f79f019d4.jpg",
            "http://pic40.nipic.com/20140424/13846002_113008517141_2.jpg",
            "http://i9.download.fd.pchome.net/t_960x600/g1/M00/0B/10/oYYBAFQlOmuIZDQRAALvMZ8mYRIAAB9HAKQEtcAAu9J875.jpg",
            "http://img2.imgtn.bdimg.com/it/u=834235734,679217072&fm=27&gp=0.jpg"};
    private My6Adapter mAdapter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_6);
        mContext=this;
        initView();
        initData();
    }

    private void initView() {
        rv_falls = (RecyclerView) findViewById(R.id.rv_falls);
    }

    private void initData() {
        LinearLayoutManager manager = new LinearLayoutManager(mContext);
        //设置网格布局,实现瀑布流
        rv_falls.setLayoutManager(new StaggeredGridLayoutManager(2,  StaggeredGridLayoutManager.VERTICAL));
        mAdapter = new My6Adapter(mContext,mDatas,rv_falls);
        rv_falls.setAdapter(mAdapter);
    }
    //在Activity关闭的时候清除任务栈
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mAdapter.clearAsync();
    }
}

Activity布局: 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_falls"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

Adapter代码: 

public class My6Adapter extends RecyclerView.Adapter {

    private Context mContext;
    private String tag = "My6Adapter";
    private String[] mDatas;
    private LayoutInflater mInflater;
    private RecyclerView mRlv;
    //内存缓存
    private LruCache<String, Bitmap> mLruCache;
    //图片本地磁盘缓存
    private DiskLruCache mDiskLruCache;
    //管理异步任务
    private Set<BitmapAsync> taskSet;

    //屏幕宽度
    private int width;

    public My6Adapter(Context context, String[] datas, RecyclerView mRlv) {
        mContext = context;
        mDatas = datas;
        mInflater = LayoutInflater.from(context);
        this.mRlv = mRlv;
        width = ((Activity) mContext).getWindowManager().getDefaultDisplay().getWidth();
        //内存缓存初始化
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cache = maxMemory / 8;
        mLruCache = new LruCache<String, Bitmap>(cache) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount() / 1024;
            }
        };
        //磁盘存储
        File file = DiskUtils.getDiskCacheDir(context, "bitmap");
        try {
            mDiskLruCache = DiskLruCache.open(file, DiskUtils.getAppVersion(context), 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
        taskSet = new HashSet<BitmapAsync>();
    }

    //添加,添加前先判断是否存在,不存在则添加
    public void addBitmapForLruCache(String key, Bitmap bitmap) {
        if (getLruCacheBitmap(key) == null) {
            //
            //Log.i(tag,"写入内存缓存");
            mLruCache.put(key, bitmap);
        }
    }

    //获取内存缓存图片
    private Bitmap getLruCacheBitmap(String url) {
        return mLruCache.get(url);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.item6, parent, false);
        return new My6ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        String url = mDatas[position];
        //设置tag
        ((My6ViewHolder) holder).iv_6.setTag(url);
        Bitmap bitmap = mLruCache.get(url);
        ((My6ViewHolder) holder).tv_count.setText(""+position);
        if (bitmap == null) {
            BitmapAsync async = new BitmapAsync();
            async.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, url, width / 2, position);
            taskSet.add(async);
        } else {
            Log.i(tag, "从内存中获取图片");
            if (((My6ViewHolder) holder).iv_6.getTag().equals(url)) {
                ((My6ViewHolder) holder).iv_6.setImageBitmap(bitmap);
            }
        }

    }

    @Override
    public int getItemCount() {
        return mDatas.length;
    }

    //清空任务
    public void clearAsync() {
        if (taskSet != null) {
            for (BitmapAsync b : taskSet) {
                b.cancel(false);
            }
        }
    }

    public void finishCache() {
        if (mDiskLruCache != null) {
            try {
                mDiskLruCache.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private class My6ViewHolder extends RecyclerView.ViewHolder {
        private ImageView iv_6;
        private TextView tv_count;

        My6ViewHolder(View itemView) {
            super(itemView);
            iv_6 = (ImageView) itemView.findViewById(R.id.iv_6);
            tv_count = (TextView) itemView.findViewById(R.id.tv_count);

        }
    }

    /**
     * 异步任务
     */
    private class BitmapAsync extends AsyncTask<Object, Void, Bitmap> {
        private String url;
        private int width;

        @Override
        protected Bitmap doInBackground(Object... params) {
            FileInputStream fileInputStream = null;
            DiskLruCache.Snapshot snapshot;
            FileDescriptor fd = null;
            url = (String) params[0];
            //期望的宽度
            width = (int) params[1];

            // 存储到磁盘缓存
            try {
                //从本地缓存中取图片,如果没有存储snapshot为空
                //对url进行加密
                String key = DiskUtils.hashKeyForDisk(url);
                snapshot = mDiskLruCache.get(key);
                if (snapshot == null) {
                    //未空则去网络上加载
                    DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                    if (editor != null) {
                        //网络加载图片
                        Log.i(tag, "从网络下载");
                        OutputStream outputStream = editor.newOutputStream(0);
                        if (BitmapUtils.loadImg(url, width, outputStream)) {
                            editor.commit();//提交
                        } else {
                            editor.abort();//终止
                        }
                    }
                    //经过上面的获取,已经存储到本地了
                    snapshot = mDiskLruCache.get(key);
                }
                if (snapshot != null) {
                    Log.i(tag, "从磁盘加载,position:" + params[2]);
                    fileInputStream = (FileInputStream) snapshot.getInputStream(0);
                    fd = fileInputStream.getFD();
                }

                Bitmap bitmap = null;
                if (fd != null) {
                    //内存没释放导致
                    bitmap = BitmapFactory.decodeFileDescriptor(fd);
                    //存到内存
                    if (bitmap != null) {
                        addBitmapForLruCache(url, bitmap);
                    }
                }
                return bitmap;
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fileInputStream != null && fd != null) {
                    try {
                        fileInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return null;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            ImageView imageView = (ImageView) mRlv.findViewWithTag(url);
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            } else {
                taskSet.remove(this);
            }
        }
    }
}

item布局:  

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/iv_6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tv_count"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:background="#ccc"/>
</LinearLayout>

工具类1: 

public class BitmapUtils {

    private static String tag = "BitmapUtils";
    /**
     * 图片等比例压缩,按指定宽度压缩
     * 一开始就压缩一次是因为百度上的图片可能太大,导致OOM,第二次才是将图片压缩成合适的宽高
     * @param is
     * @param trgetWidth 期望的宽
     * @param out 用于磁盘缓存的输出流
     * @return
     */
    public static boolean compressBitemap(InputStream is, int trgetWidth,OutputStream out) {

        byte[] datas = getBytesFromStream(is);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(datas, 0, datas.length, options);
        //int outHeigh=options.outHeight;
        int outWidth = options.outWidth;
        //计算比例(图片原始高度和目标高度的比例)
        int blw = Math.round(outWidth / trgetWidth);
        //使用比较大的比例
        int bl = blw;
        //如果比例小于等于0,表示图片不进行压缩
        if (bl <= 0) {
            bl = 1;
        }
        options.inSampleSize = bl;
        options.inJustDecodeBounds = false;
        //等比例压缩
        Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length, options);
        if (bitmap!= null){
            //计算拉伸比例
            float i = ((float) trgetWidth / bitmap.getWidth());//拉伸比例
            Log.i(tag, "缩放比例i:" + i);
            Matrix matrix = new Matrix();
            matrix.postScale(i, i);
            //缩放成指定宽度的图片(newBmp就是压缩后宽度为屏幕一半的图片)
            Bitmap newBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
            /**存储到磁盘===============*/
            //bitmap转换成InputStream
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //无损压缩
            newBmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            InputStream isBm = new ByteArrayInputStream(baos.toByteArray());
            BufferedInputStream bis=null;
            BufferedOutputStream bos=null;
            try{
                bis=new BufferedInputStream(isBm);
                bos=new BufferedOutputStream(out);
                int len;
                while ((len=bis.read())!=-1){
                    bos.write(len);
                }
                bos.flush();//刷新缓存
                return true;
            }catch (Exception e){
                e.printStackTrace();
            }
        }else{
            Log.i(tag,"图片为空");
        }
        return false;
    }

    /**
     * 将输入流转换为字节数组
     *
     * @param is
     * @return
     */
    public static byte[] getBytesFromStream(InputStream is) {
        byte[] datas = null;
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        try {
            while ((len = is.read(buffer, 0, buffer.length)) != -1) {
                os.write(buffer, 0, len);
            }
            datas = os.toByteArray();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                os.close();
                is.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return datas;
    }
    /**
     * 从网络上加载载图片,并存储到缓存
     *
     * @param url
     * @param width 期望的宽
     */
    public static boolean loadImg(String url, final int width, OutputStream outputStream) {
        BufferedInputStream bis=null;
        BufferedOutputStream bos=null;
        try {

            URL url1 = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) url1.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(10 * 1000);
            int code = conn.getResponseCode();
            if (code == 200) {
                InputStream inputStream = conn.getInputStream();
//                bis=new BufferedInputStream(inputStream);
//                bos=new BufferedOutputStream(outputStream);
//                int len;
//                while ((len=bis.read())!=-1){
//                    bos.write(len);
//                }
//                bos.flush();//刷新缓存
                return compressBitemap(inputStream,width,outputStream);
            } else {
                Log.i(tag, "输入流为空");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

}

工具类2: 

public class DiskUtils {
    //文件路径
    public static File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    /**
     * 获取应用的版本号
     * @param context
     * @return
     */
    public static int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }
    /**
     * 用md5算法对字符串加密
     * @param key
     * @return
     */
    public static String hashKeyForDisk(String key){
        String cacheKey;
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(key.getBytes());
            cacheKey=bytesToHexString(md5.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            cacheKey=String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    /**
     * 将bytes数组转换为string类型
     * @param bytes
     * @return
     */
    public static String bytesToHexString(byte[] bytes){
        StringBuilder sb=new StringBuilder();
        for (int i=0;i<bytes.length;i++){
            String hex=Integer.toHexString(0xFF & bytes[i]);
            if (hex.length()==1){//
                sb.append("0");
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}

效果图 

recyclerview实现瀑布流 

其中的DiskLrucache类下载地址(郭霖大佬上传的):
点击下载 

图片都是我直接从百度找的,样式啥的都没改,所以有点丑

代码全贴出来了,就不上传项目了。公司电脑加密了,上传了下载下来也是乱码 ,