Android仿Instagram图片加载策略(模糊图片占位+下载进度条)

来源:互联网 发布:php反射机制和作用 编辑:程序博客网 时间:2024/05/18 01:56

前言:

Instagram的Android客户端用户体验很棒,我分析了一下大概有这3个要点:

1、屏幕外图片预加载

意思是如果你在注视屏幕中显示的一张图片,但同时屏幕下方你没有拉出来的图片已经在后台下载,此功能我使用RecyclerView的预加载功能很好的模拟出来了。

2、模糊图占位

首先在你获取别人动态的时候,那个人的用户名,头像,图片url和图片的缩略图都同时返回回来,并在屏幕上显示缩略图,而真实的图片是异步下载的,在下载结束之后替换之前的模糊图

3、下载进度提示

Instagram有一个白色的进度条,可以提示当前下载进度的百分比。


对于这三个主要功能,我已经全部模拟出来了相似的效果,本文是在基于我之前两篇博客的基础上,重点描述模糊占位图的实现过程,前两篇博客请看-->《使用okhttp3做Android图片框架Picasso的下载器和缓存器》《为Android图片加载添加百分比进度条(Picasso+Okhttp3)》


实现效果:


项目github地址:https://github.com/AlexZhuo/AlxPicassoProgress


实现思路:

首先第一点是图片的url信息,用户信息,评论信息等等要和缩略图同步获取,实现起来就是服务器在发挥JSON字符串数据的时候,将jpg格式的缩略图(我这里分辨率为25x25)使用Base64压缩成可读字符串放在JSON的某个字段中,相当于把一个图片塞在字符串中发过来,客户端收到该Base64编码的缩略图后进行解码,并在主线程中将该缩略图进行放大+高斯模糊,放在ImageView中显示,然后Piasso在下载完成之后将真实的图片替换掉原来放进去的bitmap,完成效果的显示。

为此,服务器端应该做的准备是:

1、提前将图片裁剪成等比例的缩略图,并使用Base64压缩,存放到数据库中

2、在发送JSON字符串时,将上面的Base64字符串同图片真实的URL一同发送

3、在使用url请求真正的图片时,Http的响应头应添加Content-Length字段提示这个图片的完整大小,以便计算下载百分比

Android端只需添加Picasso2和OkHttp3两个框架和一个自定义进度条控件即可完成这个效果

注意:如果直接将25x25的缩略图放置到ImageView中,理论上也能实现效果,但是模糊出来非常难看,我的方法是先将25x25的缩略图变成300x300的大图,然后高斯模糊这一章300x300的大图,模糊半径尽量的大。在我上面的demo中,虽然已经实现了高斯模糊,但是使用的是一个普通的模糊算法,模糊出来以后没有Ins那么柔和,如果有机会我会更新一下那个模糊算法。

下面贴一些主要代码,其他代码请到github上查看

将Base64字符转转换为Bitmap的方法:

public static Bitmap base64ToBitmap(String sourceBase64){        if(TextUtils.isEmpty(sourceBase64)) sourceBase64 = "/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gOTAK/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwLCgsLDQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0LDRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU/8AAEQgAMgAyAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJD...省略不写";        byte[] sourceBytes = Base64.decode(sourceBase64.getBytes(),Base64.DEFAULT);        if(sourceBytes == null || sourceBytes.length == 0)return null;        Bitmap bitmap = BitmapFactory.decodeByteArray(sourceBytes,0,sourceBytes.length);        if(bitmap == null)return null;        if(bitmap.getHeight()<2)return null;        return bitmap;    }
这里提示一下,一般JPG使用Base64压缩后都会以/9j/开头,用这个可以判断一下有没有转换正确,Base64压缩完后会出现很多'/'字符,要注意不要写成‘\\/’的形式,否则会转换Bitmap出错


将小Bitmap转换为大Bitmap的方法:

 /**     * 传入一个bitmap,根据传入比例进行大小缩放     * @param bitmap     * @param widthRatio 宽度比例,缩小就比1小,放大就比1大     * @param heightRatio     * @return     */    public static Bitmap scaleBitmap(Bitmap bitmap, float widthRatio, float heightRatio) {        Matrix matrix = new Matrix();        matrix.postScale(widthRatio,heightRatio);        return Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);    }

高斯模糊的方法:

 if (Build.VERSION.SDK_INT > 16) {            Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);            final RenderScript rs = RenderScript.create(context);            final Allocation input = Allocation.createFromBitmap(rs, sentBitmap, Allocation.MipmapControl.MIPMAP_NONE,                    Allocation.USAGE_SCRIPT);            final Allocation output = Allocation.createTyped(rs, input.getType());            final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));            script.setRadius(radius /* e.g. 3.f */);            script.setInput(input);            script.forEach(output);            output.copyTo(bitmap);            return bitmap;        }

上面这个是Android原生支持的高斯算法,底层采用c实现,效率比较高,如果SDK版本低于17,那么去github上看if之外的方法,是一个Java实现的模糊算法,用于低版本的机器,我在demo中使用的模糊半径是8。


屏幕外图片预加载方法:

本demo为了效果明显,并没有添加预加载的功能,我在实际的项目中使用的是RecyclerView做为容器,而RecyclerView添加预加载的方法如下:

首先需要自定义一个LayoutManager

mport android.content.Context;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;/** * Created by Alex on 2016/9/18. * 用于预加载recyclerView下一张卡的layoutmanager */public class AlxPreloadLinearManager extends LinearLayoutManager{    private static final int DEFAULT_EXTRA_LAYOUT_SPACE = 1280;    private int extraLayoutSpace = DEFAULT_EXTRA_LAYOUT_SPACE;    public AlxPreloadLinearManager(Context context) {        super(context);    }    public AlxPreloadLinearManager(Context context, int extraLayoutSpace) {        super(context);        this.extraLayoutSpace = extraLayoutSpace;    }    public AlxPreloadLinearManager(Context context, int orientation, boolean reverseLayout) {        super(context, orientation, reverseLayout);    }    public void setExtraLayoutSpace(int extraLayoutSpace) {        this.extraLayoutSpace = extraLayoutSpace;    }    @Override    protected int getExtraLayoutSpace(RecyclerView.State state) {        if (extraLayoutSpace > 0) {            return extraLayoutSpace;        }        return DEFAULT_EXTRA_LAYOUT_SPACE;    }}
LayoutManager layoutManager = new AlxPreloadLinearManager(context);int screenHeigth =getScreenHeigth();if(screenHeigth > 500)((AlxPreloadLinearManager)layoutManager).setExtraLayoutSpace(screenHeight);//设置预加载下一张卡的模式layoutManager.setOrientation(LinearLayoutManager.VERTICAL);recyclerView.setLayoutManager(layoutManager);

调用方法:

只需一行即可:如果想添加模糊图占位功能
AlxPicassoUtils.displayImageProgress(url,imageView,progressWheel,textView,base64Str);
参数分别为:图片url地址,ImageView控件,圆形进度条控件,进度显示TextView,经过Base64压缩的图片缩略图
AlxPicassoUtils.displayImageProgress(url,imageView,progressWheel,textView);
参数分别为:图片url地址,ImageView控件,圆形进度条控件,进度显示TextView

1 0
原创粉丝点击