PS:最近看到很多人都开始写年终总结了,时间过得飞快,又到年底了,又老了一岁。
学习内容:
1. 进度条
2. 缩放
3.ControllerBuilder,ControllerListener,PostProcesser,Image Request
4. 渐进式 JPEG 与动图的显示
最近这两天把 Fresco 的官方文档算是看了个差不多,就剩下 Fresco 的基本原理还有结合 okHttp 等类库如何使用的问题,虽然官方文档给出的功能比较的多,比如说自定义 View,缩略图显示等等,这些我也基本就看了个大概,觉得实际需求应该没有那么高的要求,因此有些东西我这里就不介绍了,详细的情况可以参考官方文档,我这里只是针对一些使用情况比较多的功能进行一个简单的介绍。
1. 进度条
进度条也算是 Fresco 的一个功能,Fresco 内部本身提供了一个 ProgressBarDrawable 类,效果其实就是一个矩形的蓝色进度条, 当图片处于加载状态时,进度条会跟着进行加载,当图片加载完毕之后,那么进度条也随之消失,但是这个进度并不是实时更新的,如果我们想要精确的加载进度,那么我们需要重写内部的方法。
- class CustomProgressBar extends Drawable{
- @Override
- protected boolean onLevelChange(int level) {
- //doSomething
- return super.onLevelChange(level);
- }
- }
自定义进度条我们就需要实现 onLevelChange 方法,这里只做一个简单的介绍,如果大家想自定义进度条,那么可以参照 ProgressBarDrawable 去重写一个进度条,个人感觉这个功能一般般,与其在加载图片的时候使用进度条,不如使用 ProgressBarImage 属性为图片加载时设置一个进度图片,同时还支持旋转属性。
2. 缩放
DraweeView 的缩放和 ImageView 的缩放类型基本上是相同的,fitXY,centerCrop,唯一与 ImageView 有区别的就是,他不支持 matrix 属性,但是追加了一个 focusCrop 属性来替代 matrix 属性,这里在设置属性的时候,xml 使用 fresco:actualScaleType 来设置 DraweeView 的缩放属性或者是使用 GenericDraweeHierarchy 属性去设置。
- GenericDraweeHierarchyBuilder progressHierarchyBuilder = new GenericDraweeHierarchyBuilder(getResources());
- GenericDraweeHierarchy progressHierarchy = progressHierarchyBuilder
- .setProgressBarImage(new ProgressBarDrawable(), ScalingUtils.ScaleType.CENTER_INSIDE)
- .build();
- progressImageDraweeView.setHierarchy(progressHierarchy);
这里不要使用 setScaleType() 或者是在 xml 中 android:scaleType 设置缩放属性,这是无效的。官方给出 focusCrop 去替代 matrix 是有一定道理的,虽然说效果会比 matrix 要好,不过到底效果如何这个确实无法评判。先说说这个属性的具体作用。
我们在实际需求中可能会遇到这样的情况,在显示人脸图片的时候,尽量的将图片居中显示,以前我也遇到过这个需求,不过当时是用 matrix 属性实现的,服务器会传递给我们人脸的重心坐标位置,然后客户端需要根据人脸重心位置将图像进行平移,同时需要将图片进行缩放,是用 matrix 的话就需要使用 matrix.postScale() 和 matrix.postTranslate() 两个方法去实现。简单贴一下当时的处理方式。
- @Override
- public void onLoadingComplete(String s, View view, Bitmap bitmap) {
- float bitmapWidth = bitmap.getWidth();
- float bitmapHeight = bitmap.getHeight();
- Scale[o] = (params[o].height / bitmapHeight >= params[o].width / bitmapWidth) ? params[o].height / bitmapHeight : params[o].width / bitmapWidth;
- float scaleBitmapWidth = Scale[o] * bitmapWidth;
- float scaleBitmapHeight = Scale[o] * bitmapHeight;
- Matrix matrix = new Matrix();
- matrix.postScale(Scale[o], Scale[o]);
- if (scaleBitmapWidth > scaleBitmapHeight) { //宽度图
- if (imagedata.get(o).getFace_center_x() == 0 && imagedata.get(o).getFace_center_y() == 0) {
- if(scaleBitmapWidth - params[o].width < 0.5 * scaleBitmapWidth - params[o].width / 2){
- matrix.postTranslate( params[o].width - scaleBitmapWidth ,0);
- }else{
- matrix.postTranslate(-(0.5f * scaleBitmapWidth - params[o].width / 2), 0);
- }
- } else {
- if(scaleBitmapWidth - params[o].width < scaleBitmapWidth * imagedata.get(o).getFace_center_x() - params[o].width / 2) {
- matrix.postTranslate(params[o].width - scaleBitmapWidth, 0);
- } else {
- if (scaleBitmapWidth * imagedata.get(o).getFace_center_x() - params[o].width / 2 < 0) {
- matrix.postTranslate(0, 0);
- } else {
- matrix.postTranslate(-(scaleBitmapWidth * imagedata.get(o).getFace_center_x() - params[o].width / 2), 0);
- }
- }
- }
- } else { //高度图
- if (imagedata.get(o).getFace_center_x() == 0 && imagedata.get(o).getFace_center_y() == 0) {
- if(scaleBitmapHeight - params[o].height < 0.5 * scaleBitmapHeight - params[o].height / 2){
- matrix.postTranslate(0, params[o].height - scaleBitmapHeight);
- }else{
- matrix.postTranslate(0,-(0.5f * scaleBitmapHeight - params[o].height / 2));
- }
- } else {
- if (scaleBitmapHeight - params[o].height < scaleBitmapHeight * imagedata.get(o).getFace_center_y() - params[o].height / 2) {
- matrix.postTranslate(0, params[o].height - scaleBitmapHeight);
- } else {
- if (scaleBitmapHeight * imagedata.get(o).getFace_center_y() - params[o].height / 2 < 0) {
- matrix.postTranslate(0, 0);
- } else {
- matrix.postTranslate(0, -(scaleBitmapHeight * imagedata.get(o).getFace_center_y() - params[o].height / 2));
- }
- }
- }
- }
- vh.image[o].setImageMatrix(matrix);
- }
这格式整的真蛋疼,这就是当时我们实际的项目需求,当时是一个 GridView, 其中一行有三张图片,需要同时对三张图片进行控制,如果是人像图片,那么需要将头像平移到中央,如果不是人像图片,那么就显示中央位置就可以了,当时实现的还是挺麻烦的,对宽度图和高度图进行了一个判断,因为图片分为宽度图和高度图,那么在计算缩放比例的时候,就需要对二者进行判断,缩放比例的计算 = 实际显示的尺寸 / 图片真正的尺寸,由于图片的不同,我们需要取二者的最大值来设置缩放比例,才不会导致出现缩放过度的问题,同时平移的尺寸不能过度。
举个例子,比如说我们 ImageView 的实际显示宽度是 100,我们对图片进行了缩放,缩放之后图片的宽度是 110,那么我们可以平移的最大距离就是 10 个单位,不能超过 10 个单位,否则显示的时候就会出现问题。并且我们需要将图片尽可能的居中显示,也就是说尽可能的使图片损失的单位要小一点,那么我们就只能讲图片平移 5 个单位,也就是说,左右两边各损失 5 个单位,这样的显示要比完全偏向一边损失 10 个单位要好得多。这里可以看到使用 matrix 还是挺复杂的,也是这里的代码是可以进行优化的,但是优化完之后其实还是比较的麻烦。
如果我们使用 Fresco 的 focusCrop 属性的话,那么事情就会变得很简单。
- fresco:actualImageScaleType="focusCrop"
xml 中设置缩放类型为 focusCrop,然后在 Java 代码中设置:
- PointF focusPoint;
- // your app populates the focus point
- mSimpleDraweeView
- .getHierarchy()
- .setActualImageFocusPoint(focusPoint);
这里 focusPoint 就是我们中心点的相对位置,float 类型,(0.5,0.5) 就相当于 centerCrop 中央位置,(1.0,1.0) 也就是图片的最下角位置,这样如果在显示人像图片的时候问题就非常的轻松了,只需要传递头像重心的相对位置,那么 Fresco 就会自动的以坐标点为显示中心。看起来好像确实是蛮简单的。
有时候现有的 ScaleType 不符合你的需求,我们允许你通过实现
来拓展它,这个接口里面只有一个方法:
- ScalingUtils.ScaleType
,它会基于以下参数来计算转换矩阵, 简单解释一下官方给出的例子。
- getTransform
官方的例子其实就是这样, 给了一个实际的 View 显示尺寸,然后给了一个图片的显示尺寸,如果直接将图片铺上去,那么图片确实可以显示完整但是却损失了一些像素点,那么这时需要对宽度进行缩放,将图片缩放为 400, 那么这时横向就能够完全的显示在屏幕上了,但是高度却变低了图片从 210 变成了 200,然而实际显示的高度为 300,这时使用 fitCenter 保持宽高比,缩小或者放大,使得图片完全显示在显示边界内,且宽或高契合显示边界。居中显示。总体差不多就是这个意思。但是自我感觉没什么意义,高度确实缩放了,但是宽度也缩放了,那么宽度还是会损失像素的。只不过是放大后的像素而已。
这里就是官方给出的例子,这里 min 表示的最小才对,官方翻译为最大的一个。没太具体的研究下面的方法,感觉和使用 matrix 差不多,但是确实是简化了不少。
- public static abstract class AbstractScaleType implements ScaleType {
- @Override
- public Matrix getTransform(Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY) {
- // 取宽度和高度需要缩放的倍数中最小的一个
- final float sX = (float) parentRect.width() / (float) childWidth;
- final float sY = (float) parentRect.height() / (float) childHeight;
- float scale = Math.min(scaleX, scaleY);
- // 计算为了均分空白区域,需要偏移的x、y方向的距离
- float dx = parentRect.left + (parentRect.width() - childWidth * scale) * 0.5f;
- float dy = parentRect.top + (parentRect.height() - childHeight * scale) * 0.5f;
- // 最后我们应用它
- outTransform.setScale(scale, scale);
- outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
- return outTransform;
- }
- }
3.ControllerBuilder,ControllerListener,PostProcesser,Image Request
ControllerBuilder 是用来构建 Controller 的,上一次已经简单的介绍过 Controller,主要是控制器,设置图片的 uri, 能否重新加载等等,那么 ControllerBuilder 就是使用 build 模式来构建 Controller 的。
ControllerListener 则是用来控制下载的监听事件的,如果我们需要在图片下载完成或者之后需要设置一切属性,那么 ControllerListener 可以帮助我们实现这个功能。但是这个监听事件中是无法修改图片的,如果我们需要修改图片,那么就需要使用到 PostProcesser 后处理器去修改图片。ImageRequest 用于配置更多的属性。也可以设置相关的 uri,是否支持渐进式加载,或者设置后处理器。可以看到这几者是存在必然的联系的,因此将这三个功能放在一起进行介绍。
这里我们为 gif 图片设置了一个 ControllerListener, 如果图片获取成功,并且图片存在动画效果,那么播放动画效果,否则 toast 消息。
- ControllerListener controllerListener = new BaseControllerListener(){
- @Override
- public void onFinalImageSet(String id, Object imageInfo, Animatable animatable) {
- if(animatable!=null){
- animatable.start();
- }
- }
- @Override
- public void onFailure(String id, Throwable throwable) {
- Toast.makeText(context,"图片加载失败",Toast.LENGTH_SHORT).show();
- }
- };
- DraweeController gifController = Fresco.newDraweeControllerBuilder()
- .setUri(Uri.parse("http://img.huofar.com/data/jiankangrenwu/shizi.gif"))
- .setOldController(gifImageView.getController())
- .setControllerListener(controllerListener)
- .build();
- gifImageView.setController(gifController);
理解起来都比较的简单。那么这里再使用后处理器简单处理一下。
- Postprocessor redMeshPostProcessor = new BasePostprocessor() {
- @Override
- public void process(Bitmap bitmap) {
- for (int x = 0; x < bitmap.getWidth(); x+=2) {
- for (int y = 0; y < bitmap.getHeight(); y+=2) {
- bitmap.setPixel(x, y, Color.TRANSPARENT);
- }
- }
- }
- @Override
- public String getName() {
- return super.getName();
- }
- };
- ImageRequest processorImageRequest = ImageRequestBuilder
- .newBuilderWithSource(Uri.parse("http://avatar.csdn.net/4/E/8/1_y1scp.jpg"))
- .setPostprocessor(redMeshPostProcessor)
- .build();
- DraweeController processorController = Fresco.newDraweeControllerBuilder()
- .setImageRequest(processorImageRequest)
- .setOldController(processImageView.getController())
- .build();
- processImageView.setController(processorController);
这里为图片上绘制了一些小圆点,同时这个属性的设置需要使用 ImageRequest 来进行配置后处理器。
ImageRequest 的最低请求级别
允许设置一个最低请求级别,请求级别和上面对应地有以下几个取值:
- setLowestPermittedRequestLevel
- BITMAP_MEMORY_CACHE
- ENCODED_MEMORY_CACHE
- DISK_CACHE
- FULL_FETCH
如果你需要立即取到一个图片,或者在相对比较短时间内取到图片,否则就不显示的情况下,这非常有用。
4. 渐进式 JPEG 的设置,动图显示。
渐进式 JPEG 表示的是当我们加载一张图片的时候,如果网络比较缓慢,那么图片会从模糊到清晰渐渐呈现,这被称之为渐进式 JPEG。具体的使用如下:
- /**
- * 设置渐进式JPEG Config
- * */
- ProgressiveJpegConfig config = new ProgressiveJpegConfig() {
- @Override
- public int getNextScanNumberToDecode(int scanNumber) {
- return 0;
- }
- @Override
- public QualityInfo getQualityInfo(int scanNumber) {
- return null;
- }
- };
- /**
- * 直接控制ImagePipeline Config
- * */
- ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig.newBuilder(context)
- .setProgressiveJpegConfig(config)
- .setDownsampleEnabled(true)
- .build();
- /**
- * 初始化使得Fresco支持渐进式JPEG的加载
- * */
- Fresco.initialize(this,imagePipelineConfig);
初始化的时候需要做这些配置,否则图片是不会呈现渐进式 JPEG 的。
- ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse("http://pooyak.com/p/progjpeg/jpegload.cgi"))
- .setProgressiveRenderingEnabled(true) //设置支持渐进式JPEG
- .build();
- DraweeController progressiveJPEGController = Fresco.newDraweeControllerBuilder()
- .setImageRequest(request)
- .setOldController(progressiveJpegImageView.getController())
- .build();
- progressiveJpegImageView.setController(progressiveJPEGController);
动图显示其实没什么可说的,Controller 也不需要做过多的配置。
- DraweeController gifController = Fresco.newDraweeControllerBuilder()
- .setUri(Uri.parse("http://img.huofar.com/data/jiankangrenwu/shizi.gif"))
- .setAutoPlayAnimations(true) //使动画自动播放
- .setOldController(gifImageView.getController())
- .build();
- gifImageView.setController(gifController);
只需要 setAutoPlayAnimations() 设置为 true 就可以在加载后自动进行播放,如果希望手动进行控制,那么就用 ControllerListener 进行控制或者直接用 controller 访问 animations 来完成。
- Animatable animatable = mSimpleDraweeView.getController().getAnimatable();
- if (animatable != null) {
- animatable.start();
- // later
- animatable.stop();
- }
注意:动图设置在高版本的 Fresco 需要引入 gradle,使其支持动画属性。
- compile 'com.facebook.fresco:animated-gif:0.14.0'
最后放置一个 Demo:。
来源: