瀑布流类似小红书App的界面 如下:
原理(知识点):
图片宽度相同,但是图片的高度不同,如果后台上传的图片规定了尺寸的话就直接添加到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/01638059302785a8012193a36096b8.jpg@2o.jpg",
"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();
}
}
效果图
其中的DiskLrucache类下载地址(郭霖大佬上传的):
点击下载
图片都是我直接从百度找的,样式啥的都没改,所以有点丑
代码全贴出来了,就不上传项目了。公司电脑加密了,上传了下载下来也是乱码 ,