RecyclerView瀑布流优化方案探讨(1),一线互联网移动架构师Android框架体系架构

if (adapter.isFadeTips() && lastVisibleItem + 2 == adapter.getItemCount()) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
updateRecyclerView(adapter.getRealLastPosition(),
adapter.getRealLastPosition() + PAGE_COUNT);
}
}, 2500);
}
}
}

@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 在滑动完成后,拿到最后一个可见的item的位置
int positions[] = staggeredGridLayoutManager.findLastVisibleItemPositions(null);
for(int pos : positions){
if(pos > lastVisibleItem){
lastVisibleItem = pos;//得到最后一个可见的item的position
}
}
}
});

@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) layoutParams;
int position = holder.getLayoutPosition();
//如果是上拉加载更多类型,则设置setFullSpan为true,那么它就会占一行
if (getItemViewType(position) == footType) {
params.setFullSpan(true);
}
}
}

04.给瀑布流设置分割线

  • 先来看看出现错位,分割线出现问题的代码。下面这种方式根据childCount来判断奇数和偶数设置的不同间距。

  • 那么比如说当为奇数时,设置该item到左为20,到右为5;当为偶数时,该item到左为5,到右为20。

  • 如果奇数的item都在左边,偶数的item都在右边,那么间距就没有问题。

  • 如果第一个item在左边【高度很高】,第2个,第3个,第4个item都在右边,第5个在左边……那么思考一下,这个时候第3个item在右边,那么就会造成间距不规则。

  • 显然不能根据奇数或者偶数来设置item左右间距的大小的,会出现错位。

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
int spanCount = 0;
int spanIndex = 0;
RecyclerView.Adapter adapter = parent.getAdapter();
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (adapternull || layoutManagernull){
return;
}
if (layoutManager instanceof StaggeredGridLayoutManager){
spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
}
//普通Item的尺寸
//TODO 会出现错位的问题
int itemCount = adapter.getItemCount();
int childCount = layoutManager.getChildCount();
RefreshLogUtils.d(“SpaceViewItemLine–count–”+itemCount + “-----”+childCount+“—索引–”+position+“—”+spanIndex);
if (position<itemCount && spanCount2) {
if (childCount % 2 == 0){
//这个是右边item
outRect.left = 5;
outRect.right = 20;
} else {
//这个是左边item
outRect.left = 20;
outRect.right = 5;
}
if (childCount
1 || childCount==2){
outRect.top = 0;
} else {
outRect.top = 20;
}
RefreshLogUtils.d(“SpaceViewItemLine–间距–”+childCount+“----”+outRect.left+“-----”+outRect.right);
}
}
});

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
int spanCount = 0;
int spanIndex = 0;
RecyclerView.Adapter adapter = parent.getAdapter();
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (adapternull || layoutManagernull){
return;
}
if (layoutManager instanceof StaggeredGridLayoutManager){
spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
}
//普通Item的尺寸
int itemCount = adapter.getItemCount();
int childCount = layoutManager.getChildCount();
RefreshLogUtils.d(“SpaceViewItemLine–count–”+itemCount + “-----”+childCount+“—索引–”+position+“—”+spanIndex);
if (position<itemCount && spanCount2) {
if (spanIndex != GridLayoutManager.LayoutParams.INVALID_SPAN_ID) {
//getSpanIndex方法不管控件高度如何,始终都是左右左右返回index
if (spanIndex % 2 == 0) {
//这个是左边item
outRect.left = 20;
outRect.right = 5;
} else {
//这个是右边item
outRect.left = 5;
outRect.right = 20;
}
if (childCount
1 || childCount==2){
outRect.top = 0;
} else {
outRect.top = 20;
}
}
//outRect.top = space;
RefreshLogUtils.d(“SpaceViewItemLine–间距–”+spanIndex+“----”+outRect.left+“-----”+outRect.right);
}
}
});

05.自定义Manager崩溃

  • RecyclerView莫名的Inconsistency detected崩溃;

  • 出现这个异常原因:

  • 使用RecyclerView加官方下拉刷新的时候,如果绑定的List对象在更新数据之前进行了clear,而这时用户紧接着迅速上滑RV,就会造成崩溃,而且异常不会报到你的代码上,属于RV内部错误。

  • 自定义一个CustomStaggeredGridLayoutManager 在onLayoutChildren对异常进行捕获:

public class CustomStaggeredGridLayoutManager extends StaggeredGridLayoutManager {
private static final String TAG = “LOG_CustomStaggered”;
public CustomStaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

public CustomStaggeredGridLayoutManager(int spanCount, int orientation) {
super(spanCount, orientation);
}

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
}catch (Exception e){
Log.i(TAG, "onLayoutChildren: e " + e.getMessage());
}
}
}

  • 关于StaggeredGridLayoutManager异常说明

06.如何避免刷新抖动

public void setDatas(List mDatas ) {
this.dataList = mDatas;
notifyDataSetChanged();
}

/**

  • 注意这个是未关注时候的瀑布流刷新数据
  • 上拉加载更多时,建议刷新上拉加载那一部分数据即可,不用刷新所有数据
  • @param puBuList 瀑布流集合
    */
    public void setMoreData(List puBuList) {
    int start = mayContentList.size();
    if (puBuList!=null && puBuList.size()!=0){
    mayContentList.addAll(puBuList);
    int end = mayContentList.size();
    mRecommendPuBuAdapter.notifyItemRangeInserted(start,end);
    }
    }

07.为何有时出现跳动

  • 由于我们加载的图片高度不确定(宽度确定因为可以根据屏幕宽度和每行Item数目进行等分),而当我们向RecyclerView下方滑动一段距离后,由于ViewHolder的回收机制,item的尺寸并不确定,滑回到上方时Item需要重新自行绘制,于是这个又导致重绘,所以会有闪烁、跳动、空白等问题。说到底,只要我们在重绘前确定了Item的尺寸,那么就可以避免Item去重新计算自己的尺寸,就可以避免重绘导致的诸多问题。

08.瀑布流图片优化

  • 具体优化方案
  • 第一步:减少布局嵌套,并且在拿到服务器的尺寸后,在onBindViewHolder中给图片控件设置宽高时,避免创建大量临时的LayoutParams对象
  • 第二步:使用glide加载,绑定activity或者fragment的生命周期,尽量不用用全局上下文或者静态上下文。注意with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。
  • 第三步:对于list条目,尤其是瀑布流,不建议使用TransitionOptions来加载设置的动画,尤其是不要使用自己自定义的动画
  • 避免使用圆角的ImageView
  • 在实际项目内,经常会用到一些带圆角的图片,或者直接就是圆形的图片。圆形的图片,多数用于一些用户的头像之类的显示效果。
  • 而在 Android 下,也有大量的类似 XxxImageView 的开源控件,用于操作 Bitmap 以达到一个圆角图片的效果,例如 Github 上比较火的 RoundedImageView。
  • 它们大部分的原理,是接收到你传递的 Bitmap ,然后再输出一个与原来 Bitmap 等大的新 Bitmap ,在此基础之上,进行圆角的一些处理,这就导致了,实际上会在内存中,多持有一个 Bitmap ,一下一张图片占用的内存就被加倍了。
  • 所以既然已经选择使用Glide,推荐使用glide-transformations这个开源库配合使用,glide-transformations 利用 Glide 的 bitmapTransfrom() 接口,实现对加载的 Bitmap 的进行一些变换操作。glide-transformations提供一系类对加载的图片的变换操作,从形状变换到色彩变换,全部支持,基本上满足大部分开发需要,并且它会复用 Glide 的 BitmapPool ,来达到节约内存的目的。

09.onBindViewHolder优化

  • 在这个方法中,主要是做数据绑定到View视图。由于瀑布流会有多种不同type类型的视图,有些需要设置宽高,有的则要从服务器拿到宽高数据然后动态修改视图属性。因此关于宽高的计算其实还可以做一些优化。

  • 先来看一下最初的代码,这里只展示动态设置宽高的代码。如下所示

  • 看了下面代码,会发现这些问题。第一,频繁计算瀑布流中不同类型的宽度,该宽度是屏幕宽度减去间距,然后是除以2,瀑布流为两行。第二,频繁创建LayoutParams对象,然后设置宽高的属性。

@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
int width = 0;
int height = 0;
switch (holder.getItemViewType()) {
case BasePubuBean.content_type9:
break;
//发布的短视频的宽高也是限制为3:4
case BasePubuBean.content_type2:
int videoWidth = (SysUtils.getScreenWidth((Activity) mContext)-SysUtils.Dp2Px(mContext,43)) / 2;
int videoHeight = (int) (videoWidth * (4/3.0f));
RelativeLayout.LayoutParams puBuParams2 = ViewUtils.getPuBuParams(mContext, videoWidth, videoHeight);
holder.getView(R.id.mImageView).setLayoutParams(puBuParams2);
break;
//活动头图,自适应拿到图片宽度,然后宽高比是4:3
case BasePubuBean.content_type4:
int imageWidth = (SysUtils.getScreenWidth((Activity) mContext)-SysUtils.Dp2Px(mContext,43)) / 2;
int imageHeight = (int) (imageWidth * (3/4.0f));
RelativeLayout.LayoutParams puBuParams4 = ViewUtils.getPuBuParams(mContext,imageWidth, imageHeight);
holder.getView(R.id.mImageView).setLayoutParams(puBuParams4);
break;
//推荐车系,宽高比是4:3
case BasePubuBean.content_type10:
int imageWidth10 = (SysUtils.getScreenWidth((Activity) mContext)-SysUtils.Dp2Px(mContext,43)) / 2;
int imageHeight10 = (int) (imageWidth10 * (3/4.0f));
ImageView imageView = holder.getView(R.id.mImageView);
ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
layoutParams.height = imageHeight10;
layoutParams.width = imageWidth10;
imageView.setLayoutParams(layoutParams);
break;
case BasePubuBean.content_type1:
case BasePubuBean.content_type3:
case BasePubuBean.content_type5:
case BasePubuBean.content_type6:
case BasePubuBean.content_type7:
case BasePubuBean.content_type8:
int imageWidth = (SysUtils.getScreenWidth((Activity) mContext)-SysUtils.Dp2Px(mContext,43)) / 2;
width = mData.get(position).getWidth();
height = mData.get(position).getHeight();
if( width!=0 && height!=0){
RelativeLayout.LayoutParams puBuParams1 = ViewUtils.getPuBuParams(mContext,imageWidth, height);
holder.getView(R.id.mImageView).setLayoutParams(puBuParams1);
}else {
int video_width = mData.get(position).getVideo_width();
int video_height = mData.get(position).getVideo_height();
if(video_width!=0 && video_height!=0){
holder.getView(R.id.mImageView).setLayoutParams(ViewUtils.getPuBuParams(
mContext,imageWidth, video_height));
}else {
holder.getView(R.id.mImageView).setLayoutParams(ViewUtils.getPuBuParams(
mContext,imageWidth , 125));
}
}
break;
default:
break;
}
}

  • 然后看一下优化后的代码。

  • 那么下面这种代码,就可以极大减少频繁的动态计算宽高等属性。通过imageView34.getLayoutParams()方式获取layoutParams,就可以避免上面那种通过new创建大量的对象。要知道,平时像上面代码那么用也没问题,但是在recyclerView瀑布流中,也可以细微优化一下。

@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
//获取屏幕宽度
if (screenWidth0){
screenWidth = SysUtils.getScreenWidth((Activity) mContext);
}
//16+11+16
if (padding
0){
padding = SysUtils.Dp2Px(mContext,43);
}
if (imageWidth==0){
imageWidth = (screenWidth-padding) / 2;
}
//宽高为3:4的图片高度
if (imageHeight34 ==0){
imageHeight34 = (int) (imageWidth * (4/3.0f));
}
//宽高4:3的图片高度
if (imageHeight43 ==0){
imageHeight43 = (int) (imageWidth * (3/4.0f));
}
//长宽为16:9的图片高度
if (imageHeight169 ==0){
imageHeight169 = (int) (imageWidth * (16/9.0f));
}
switch (holder.getItemViewType()) {
case BasePubuBean.content_type9:
break;
//发布的短视频的是限制为3:4
case BasePubuBean.content_type2:
int width34 = imageWidth;
int height34 = imageHeight34;
ImageView imageView34 = holder.getView(R.id.mImageView);
ViewGroup.LayoutParams layoutParams34 = imageView34.getLayoutParams();
layoutParams34.height = height34;
layoutParams34.width = width34;
imageView34.setLayoutParams(layoutParams34);
break;
//行情文章也是4:3
case BasePubuBean.content_type8:
//活动头图,自适应拿到图片宽度,然后是4:3
case BasePubuBean.content_type4:
//文章详情也是4:3
case BasePubuBean.content_type5:
//推荐车系是4:3
case BasePubuBean.content_type10:
int width43 = imageWidth;
int height43 = imageHeight43;
ImageView imageView43 = holder.getView(R.id.mImageView);
ViewGroup.LayoutParams layoutParams43 = imageView43.getLayoutParams();
layoutParams43.height = height43;
layoutParams43.width = width43;
imageView43.setLayoutParams(layoutParams43);
break;
//长视频16:9
case BasePubuBean.content_type3:
int width169 = imageWidth;
int height169 = imageHeight169;
ImageView imageView169= holder.getView(R.id.mImageView);
ViewGroup.LayoutParams layoutParams169 = imageView169.getLayoutParams();
layoutParams169.height = height169;
layoutParams169.width = width169;
imageView169.setLayoutParams(layoutParams169);
break;
case BasePubuBean.content_type1:
case BasePubuBean.content_type6:
case BasePubuBean.content_type7:
ImageView imageView= holder.getView(R.id.mImageView);
int width = mData.get(position).getWidth();
int height = mData.get(position).getHeight();
if( width!=0 && height!=0){
//这种方式会创建大量的对象
//RelativeLayout.LayoutParams puBuParams1 = ViewUtils.getPuBuParams(mContext,width, height);
//holder.getView(R.id.mImageView).setLayoutParams(puBuParams1);
ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
layoutParams.height = height;
layoutParams.width = imageWidth;
imageView.setLayoutParams(layoutParams);
}else {
int videoWidth = mData.get(position).getVideo_width();
int videoHeight = mData.get(position).getVideo_height();
if(videoWidth!=0 && videoHeight!=0){
ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
layoutParams.height = videoHeight;
layoutParams.width = imageWidth;
imageView.setLayoutParams(layoutParams);
//holder.getView(R.id.mImageView).setLayoutParams(ViewUtils.getPuBuParams(
// mContext,imageWidth, video_height));
}else {
ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
layoutParams.height = 125;
layoutParams.width = imageWidth;
imageView.setLayoutParams(layoutParams);
//holder.getView(R.id.mImageView).setLayoutParams(ViewUtils.getPuBuParams(
// mContext,imageWidth,125));
}
}
break;
default:
break;
}
}

10.瀑布流item点击事件优化

  • 关于rv设置item条目点击事件有两种方式:1.在onCreateViewHolder中写;2.在onBindViewHolder中写;3.在ViewHolder中写。那么究竟是哪一种好呢?

@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(mContext).inflate(R.layout.item_me_gv_grid, parent, false);
final MyViewHolder holder = new MyViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClick(view, holder.getLayoutPosition());
}
}
});
return holder;
}

@Override
public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClick(holder.itemView, holder.getAdapterPosition());
}
}
});
}

class MyViewHolder extends RecyclerView.ViewHolder {
MyViewHolder(final View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClick(itemView, getAdapterPosition());
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:
[外链图片转存中…(img-kLfVWB6a-1712794697687)]

[外链图片转存中…(img-zVkbblMX-1712794697688)]

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 25
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值