当前位置: 首页 > 工具软件 > GifView > 使用案例 >

android gifview 代码使用,GifVIew在android的应用指南

孔征
2023-12-01

原标题:GifVIew在android的应用指南

背景

目前,大部分市场应用在展示产品的时候都会选择图片配文字的形式,显得更加直观。随着人们手机设备性能的提高与Wifi以及4G网络的提速,为了能让用户的体验更加立体,很多APP在”秀“自己的产品的时候都会直接展示视频。然而图片和视频之间还是有一定的流量差距,为了让用户可以更好的过渡这一差距,图片展示gifview,点击gifview观看视频这样的用户行为正在慢慢的被接受。

58部落目前是非常大的用户群体,他们也会经常发表一些自己的作品,看法。目前也是列表页展示图片,点击进入后展示详情。那么如果需要有这个过渡,就需要在列表页上增加gifview来达到更好的曝光率。

大众点评&马蜂窝 点评 & 马蜂窝 效果展示 效果分析

我们先来自己想想,如果要是我们自己来实现这个效果应该如何来做:

两种方法:

方案一:

1.使用recyclerview实现列表页用于展示;

2.自定义GifView,包含展示静态图和gif图的功能;

3.进入页面,请求首页,获取json得到gif;

4.解析gif的第一帧,得到Image的比特流,让GifView展示图片;

5.图片展示完成后,自定义GifView播放GifView;

优点:

简化json输出,json里面的返回值返回一套gif就可以,自己解析gif的第一帧用于展示图片;

缺点:

速度慢,本来列表页快速滑动展示大图片都考虑加载时间,如果再去自己解析,成本太高,内存要求大;

方案二:

1.使用recyclerview实现列表页用于展示;

2.自定义ImageView,展示Image;

3.自定义GifView,展示gif图;

4.进入页面,请求首页,获取json得到image和gif;

5.自定义ImageView展示imageview占位,然后紧接着加载gif;

优点:

1.速度快;

2.解耦,一旦出现问题,可以快速降级;

另外,从版本的迭代的上来考虑,我个人更倾向于方案二:

点评效果深入研究

接下来,先上常规操作让我们看一下大众点评是不是酱样婶的吧:

dump一下,你不知道

Running activities (most recent first):

Run #1: ActivityRecord{1b8520 u0 com.dianping.v1/.NovaMainActivityt15792}

Run #0: ActivityRecord{2a47964 u0 com.tencent.mm/.ui.LauncherUIt15793}

Running activities (most recent first):

Run #0: ActivityRecord{945d44 u0 com.miui.home/.launcher.Launchert1}

Running activities (most recent first):

Run #0: ActivityRecord{52d96ba u0 com.android.systemui/.recents.RecentsActivityt15788}

首先,我们来看一下dump信息,NovaMainActivity,是它的首页,但是显然根据这个我们没有任何头绪。正向查一个控件我们要知道哪个布局,哪个控件,哪个View。所以,我想能不能看看Log,结果还真的让我发现了蛛丝马迹。

logcat

2019-09-1410:59:54.30431909-31909/? D/GifImageView: gifIv has already been stoped:

2019-09-1410:59:54.30431909-31909/? D/GifImageView: gifIv has already been stoped:

2019-09-1410:59:54.30431909-31909/? D/GifImageView: gifIv has already been started:

在我快速滑动的时候,我发现居然有这么些可爱的代码在控制台打印出来。于是,我就看到了新的曙光。

万幸的是,我还在logcat里面额外看到了webp格式的图片和动图的日志:

//动图

https://img.xxx.net/coverpic/2d348f2ea08616ab1e8c652800373740.webp

//非动图

https://img.xxx.net/coverpic/4cf4cfa5469f95444986f83f194f6acb35706.jpg%40320w_426h_1e_1c_1l%7Cwatermark%3D0.webp

这个日志初步印证了我的想法,我决定看一下”GifImageVIew”都干了啥。点评是有混淆和做了加壳的,常规的jd-gui查看的代码看不到。通过脱壳,获取其相关代码,为了更好地理解,里面的关键代码做了注释:

publicclassGifImageViewextendsFrameLayout{

publicGifImageView(Context context){

super(context);

}

publicstaticfinalString TAG = "GifImageView";

publicPicassoImageView gifImageView; //Picasso

privateString gifIvGroup;

privatedoublegifPriority; //gif的优先级

privateString gifUrl; //gif的url

publicPicassoImageView imageView; //又来一个Picasso

//构造函数

publicGifImageView(Context context){

this(context, null);

}

//构造函数

publicGifImageView(Context context, AttributeSet attributeSet){

this(context, attributeSet, 0);

}

//构造函数

publicGifImageView(Context context, AttributeSet attributeSet, inti){

super(context, attributeSet, i);

init(context);

}

//初始化 *关键*

privatevoidinit(Context context){

LayoutParams layoutParams = newFrameLayout.LayoutParams(-1, -1);

this.imageView = newPicassoImageView(context);

this.gifImageView = newPicassoImageView(context);

this.gifImageView.setFadeInDisplayEnabled(false);

addView(this.gifImageView, layoutParams);

addView(this.imageView, layoutParams);

//开始进行gif加载的设置

this.gifImageView.setChangeListener(newu {

//gif加载开始

publicvoidonImageLoadStart{

GifImageView.this.imageView.setVisibility(View.VISIBLE);

}

//gif加载完成

publicvoidonImageLoadSuccess(Bitmap bitmap){

StringBuilder stringBuilder = newStringBuilder;

stringBuilder.append("load gif success : ");

stringBuilder.append(GifImageView.this.gifImageView.getURL);

b.a(GifImageView.class, stringBuilder.toString);

GifImageView.this.imageView.setVisibility(View.GONE);

}

//加载失败如何处理

publicvoidonImageLoadFailed{

StringBuilder stringBuilder = newStringBuilder;

stringBuilder.append("load gif failed : ");

stringBuilder.append(GifImageView.this.gifImageView.getURL);

b.a(GifImageView.class, stringBuilder.toString);

GifImageView.this.imageView.setVisibility(View.VISIBLE);

}

});

}

//设置布局

publicvoidsetLayoutParams(LayoutParams layoutParams){

super.setLayoutParams(layoutParams);

setViewParams(this.imageView, layoutParams);

setViewParams(this.gifImageView, layoutParams);

}

//设置布局

privatevoidsetViewParams(View view, LayoutParams layoutParams){

LayoutParams layoutParams2 = view.getLayoutParams;

if(layoutParams2 instanceofFrameLayout.LayoutParams) {

layoutParams2.width = layoutParams.width;

layoutParams2.height = layoutParams.height;

view.setLayoutParams(layoutParams2);

}

}

//开始执行gif播放

publicvoidstartGif{

if(this.gifImageView.isImageAnimating) {

Log.d(TAG, "gifIv has already been started: ");

} else{

this.gifImageView.setAnimatedImageLooping(-1);

this.gifImageView.startImageAnimation;

Log.d(TAG, "gifIv has been started: ");

}

}

//停止执行gif播放

publicvoidstopGif{

if(this.gifImageView.isImageAnimating) {

this.gifImageView.setAnimatedImageLooping(0);

this.gifImageView.stopImageAnimation;

Log.d(TAG, "gifIv has been stoped: ");

} else{

Log.d(TAG, "gifIv has already been stoped: ");

}

}

//设置image和gif图像地址

publicvoidsetGifImage(String str, String str2){

this.imageView.setImage(str);

this.gifImageView.setAnimatedImageLooping(0);

this.gifImageView.setImage(str2);

this.imageView.setVisibility(0);

this.gifUrl = str2;

if(TextUtils.isEmpty(str2)) {

GifImageViewManager.getInstance.addGifIv(this);

} else{

GifImageViewManager.getInstance.removeGifIv(this);

}

}

publicvoidsetAnimatedImageLooping(inti){

this.imageView.setAnimatedImageLooping(i);

}

publicvoidsetScaleType(ImageView.ScaleType scaleType){

this.imageView.setScaleType(scaleType);

this.gifImageView.setScaleType(scaleType);

}

//支持直接设置drawable

publicvoidsetImageDrawable(Drawable drawable){

this.imageView.setVisibility(0);

this.imageView.setImageDrawable(drawable);

}

.....此处省略1000字

}

另外,还发现它的自定义图片PicassoImageView(好像跟git上面的Picasso没什么关系)。

importcom.dianping.imagemanager.DPImageView;

public classPicassoImageViewextendsDPImageViewimplementsClippable{

}

public classDPImageViewextendsImageViewimplementsOnClickListener{

}

还有。。。。咳咳,让我们点到为止吧。

点评效果总结

所以,点评的基本逻辑跟我之前说的第二种方案几乎无差,我们再来回顾一下:

1.自定义View命名为PicassoImageView,可以展示Gif图也可以展示ImageView;

2.封装GifImageView,里面包含两个PicassoImageView;

3.其中一个PicassoImageView展示imageview占位,同时另外一个PicassoImageView进行gif的加载,加载完成后,把第一个PicassoImageView消失;

so,感谢点评为我们提供宝贵的思路,接下来让我们去看看马蜂窝是怎么实现的吧。

马蜂窝效果深入研究 dump之后依然没有有用信息

也不是完全没有用,至少你知道了程序的入口在哪里。

看看logcat

2019-09-1816:53:11.78625404-25919/? D/SoLoader:About to load:libgifimage.so

2019-09-1816:53:11.78725404-25919/? D/SoLoader:libgifimage.so not found on /data/data/com.mfw.roadbook/lib-main

2019-09-1816:53:11.78725404-25919/? D/SoLoader:libgifimage.so found on /data/app/com.mfw.roadbook-U4eSwGYqvGPqtvkBe8R4gw==/lib/arm

2019-09-1816:53:11.78725404-25919/? D/SoLoader:Not resolving dependencies forlibgifimage.so

2019-09-1816:53:11.79525404-25919/? D/SoLoader:Loaded:libgifimage.so

嗯,果然,看看还是有收获的。又Get到一个新知识,可以通过加载so(libgifimage.so)的方式,提升GifView加载速度。

马蜂窝效果总结

在实际体验的过程中,我发现滑动到没有加载的图片时,马蜂窝会用一个加载的灰色占位图占位,然后去下载gif,它旁边的图片都展示出来了,gif还没有下载完,体验不是很好。这点可以借鉴一下大众点评的。

由于马蜂窝也加壳了,脱壳其实是很开(fei)心(shen)的,有了点评的思路,我就没有特别深入的研究马蜂窝的内部实现,其实直接看效果也能看出个大概。

有没有开源的库呢?

我们并不想把点评或者马蜂窝的代码直接拷过来,毕竟人家没有开源,而且也不一定契合我们的风格。在git上找一个demo实现以下?

于是,我看到了这个android-gif-drawable,这个看起来还不错,7K的赞,Fork了1.6K,正合我意,来吧,研究一波。

API解读

android-gif-drawable是通过JNI来渲染帧的,比使用WebView或者Movie性能要好一些。

依赖

dependencies {

implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.19'

}

repositories {

mavenCentral

maven {

url "https://oss.sonatype.org/content/repositories/snapshots"

}

}

dependencies {

implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.+'

}

基本使用

//1. asset文件

GifDrawable gifFromAssets = newGifDrawable( getAssets, "anim.gif");

//2. resource (drawable or raw)

GifDrawable gifFromResource = newGifDrawable( getResources, R.drawable.anim );

//3. byte array

byte[] rawGifBytes = ...

GifDrawable gifFromBytes = newGifDrawable( rawGifBytes );

//4. FileDeor

FileDeor fd = newRandomAccessFile( "/path/anim.gif", "r").getFD;

GifDrawable gifFromFd = newGifDrawable( fd );

//5. file path

GifDrawable gifFromPath = newGifDrawable( "/path/anim.gif");

//6. file

File gifFile = newFile(getFilesDir,"anim.gif");

GifDrawable gifFromFile = newGifDrawable(gifFile);

//7. AssetFileDeor

AssetFileDeor afd = getAssets.openFd( "anim.gif");

GifDrawable gifFromAfd = newGifDrawable( afd );

//8. InputStream (it must support marking)

InputStream sourceIs = ...

BufferedInputStream bis = newBufferedInputStream( sourceIs, GIF_LENGTH );

GifDrawable gifFromStream = newGifDrawable( bis );

//9. direct ByteBuffer

ByteBuffer rawGifBytes = ...

GifDrawable gifFromBytes = newGifDrawable( rawGifBytes );

额外的API

- 停止GIF动画

·stop

- 开始GIF动画

·start

- GIf动画是否在执行

isRunning

- 重置GIF动画

reset

- 控制执行动画的速度

setSpeed(float factor)

- 从该动画的执行位置开始执行

seekTo(int position)

- 动画的持续时间

getDuration

- 当前动画的播放时间

getCurrentPosition

调用方法如下:

try{

GifDrawablegifFromResDrawable = newGifDrawable( mContext.getResources, getIntGifRes(imageData.gifUrl));

viewHolder.gifImageView.setImageDrawable(gifFromResDrawable);

viewHolder.gifImageView.setVisibility(View.VISIBLE);

} catch(Exceptione) {

e.printStackTrace;

}

所以,我们看到,本质上还是这个GifDrawable在起作用,因为GifImageView继承的是ImageView。

效果如下 撸一个Demo

我们看完了大众点评、马蜂窝、github上的实现效果。它们有自己的优点,结合58自己的技术特点,我打算采用的技术架构:FRESCO + RecyclerView的StaggeredGridLayoutManager,具体实现思路如下:

使用StaggeredGridLayoutManager实现瀑布布局;

RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);

StaggeredGridLayoutManager layoutManager = newStaggeredGridLayoutManager(2,

StaggeredGridLayoutManager.VERTICAL);

recyclerView.setLayoutManager(layoutManager);

FrescoAdapter adapter = newFrescoAdapter(this, DataUtils.getFrescoImageData);

recyclerView.setAdapter(adapter);

自定义Adapter加载图片和GIF:

static classViewHolderextendsRecyclerView.ViewHolder{

SimpleDraweeViewdraweeImage;

SimpleDraweeViewdraweeGif;

TextViewtextView;

public ViewHolder(@NonNullViewitemView) {

super(itemView);

draweeImage = (SimpleDraweeView) itemView.findViewById(R.id.item_draweeview);

draweeGif = (SimpleDraweeView) itemView.findViewById(R.id.item_draweeview_gif);

textView = (TextView) itemView.findViewById(R.id.item_draweeview_text);

}

}

加载图片和GIF

/**

* Fresco 加载webp图片

* @param draweeView

* @param imageUrl

*/

publicstaticvoidloadWebpImage(finalContext context, finalSimpleDraweeView draweeView, finalImageData imageData, StringimageUrl, finalbooleanreSize, finalintposition) {

DraweeController controller = Fresco.newDraweeControllerBuilder

.setUri(Uri.parse(imageUrl))

.setAutoPlayAnimations(true)

.setOldController(draweeView.getController)

.setControllerListener(newControllerListener {

@Override

publicvoidonSubmit(Stringid, ObjectcallerContext) {

}

@Override

publicvoidonFinalImageSet(Stringid, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) {

if(imageInfo == null) {

return;

}

if(imageData.getScale == 0){

intwidth= imageInfo.getWidth;

intheight= imageInfo.getHeight;

floatscale= (float) width/ (float) height;

imageData.setScale(scale);

}

finalViewGroup.LayoutParams layoutParams = draweeView.getLayoutParams;

layoutParams.width= DisplayUtils.getScreenWidth((Activity) context) / 2- DisplayUtils.dp2px(context,10);

layoutParams.height= (int) (layoutParams.width/ imageData.getScale);

imageData.setWidth(layoutParams.width);

imageData.setHeight(layoutParams.height);

imageData.setPosition(position);

draweeView.setLayoutParams(layoutParams);

}

@Override

publicvoidonIntermediateImageSet(Stringid, @Nullable ImageInfo imageInfo) {

}

@Override

publicvoidonIntermediateImageFailed(Stringid, Throwable throwable) {

}

@Override

publicvoidonFailure(Stringid, Throwable throwable) {

}

@Override

publicvoidonRelease(Stringid) {

}

})

.build;

draweeView.setController(controller);

}

/**

* Fresco 加载webpGID

* @param imageView

* @param imageUrl

*/

publicstaticvoidloadWebpGif(finalContext context, finalSimpleDraweeView imageView,finalSimpleDraweeView gifView, finalImageData imageData, StringimageUrl, finalbooleanreSize, finalintposition) {

DraweeController controller = Fresco.newDraweeControllerBuilder

.setUri(Uri.parse(imageUrl)).setAutoPlayAnimations(true).setOldController(gifView.getController)

.setControllerListener(newControllerListener {

@Override

publicvoidonSubmit(Stringid, ObjectcallerContext) {

}

@Override

publicvoidonFinalImageSet(Stringid, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) {

if(imageInfo == null) {

return;

}

finalViewGroup.LayoutParams layoutParams = imageView.getLayoutParams;

finalViewGroup.LayoutParams gifLayoutParams = gifView.getLayoutParams;

gifLayoutParams.width= layoutParams.width;

gifLayoutParams.height= layoutParams.height;

gifView.setLayoutParams(gifLayoutParams);

}

@Override

publicvoidonIntermediateImageSet(Stringid, @Nullable ImageInfo imageInfo) {

}

@Override

publicvoidonIntermediateImageFailed(Stringid, Throwable throwable) {

}

@Override

publicvoidonFailure(Stringid, Throwable throwable) {

}

@Override

publicvoidonRelease(Stringid) {

}

})

.build;

gifView.setController(controller);

}

效果如下 缺点与不足

Demo里面还有很多的异常边界情况没有考虑,比如各类的容错判断,性能问题监控,等等。

有兴趣的小伙伴可以看这里:

传送门:https://github.com/Mozziehh/GifView

https://www.jianshu.com/p/057f48df855b

https://github.com/koral–/android-gif-drawable

https://www.dev2qa.com/how-to-play-gif-file-use-android-graphics-movie-class/

https://blog.csdn.net/feather_wch/article/details/79558240

责任编辑:

 类似资料: