您的位置:首页 > 移动开发 > Android开发

【Android TV端】RecyclewView中局部更新(实现item下载进度实时更新)

2018-01-25 18:58 676 查看
需求背景:Android中ListView或GridView或RecycleView中 下载时item中的进度条不断更新处理。

性能问题:TV端如果用notifyDatasetChanged()来刷新整个界面,下载刷新比较频繁的话,一量下载中的数量过多,性能极其低下,在TV端硬件盒子性能不佳的情况下,很容易引发ANR,会造成内存消耗和页面卡顿,出现焦点乱跑和操作无反应,用户体验极差。

解决思路:采用局部更新,刷新某个item.

解决方案一:

通过listview.getFirstVisiblePosition()方法获取到显示的item的首个位置 ,再根据position, 计算出view的位置。获取到具体的view后,对view进行操作,就能够实现局部刷新了。

关键代码:

public void updateView(int itemIndex) {
//得到第一个可显示控件的位置,
int visiblePosition = mListView.getFirstVisiblePosition();
//只有当要更新的view在可见的位置时才更新,不可见时,跳过不更新
if (itemIndex - visiblePosition >= 0) {
//得到要更新的item的view
View view = mListView.getChildAt(itemIndex - visiblePosition);
//从view中取得holder
ViewHolder holder = (ViewHolder) view.getTag();
HashMap<String, Object> item = data.get(itemIndex);
//获取到具体的控件,
holder.name = (TextView) view.findViewById(R.id.name);
holder.process = (ProcessBar) view.findViewById(R.id.process);
.......
//对控件进行操作
holder.process.setMax(item.get("max"));
holder.process.setProgress(item.get("progress"));
......
}
}


解决方案二:(方案升级)

用RecyclerView,并可完美替代ListView,GridView

先了解一下用RecyclerView相对于ListView的优缺点:

优点:

1、可以使用布局管理器LayoutManager来管理RecyclerView的显示方式:水平、垂直、网络、网格交错布局;

2、自定义item的分割条,实现自定义

3、可以控制item的添加和删除的动画,非常自由,可以自定义动画,配合具体场景,效果非常棒;

4、可以动态的在指定位置添加和删除某一项,而列表不会回到顶部,动态的更新列表数据;

缺点:

1.没有OnItemClickListenter(),需要自己在RecycleView内部自定义列表项的点击事件或则长按事件(按需求自己添加);

2.不能简单的添加header和footer。

采用recycleView局部刷新的思路选择:

1、直接notifyDataSetChanged()。

RecyclerView不像ListView,只有一个更新notifyDataSetChanged,它不仅保留了ListView的更新特点,

还针对“增加,删除,更新”操作专门进行更新,可以只更新一个item,也可以更新一部分item,所以,用起来效率更高。

因此,RecyclerView的局部刷新,就可以通过修改数据源的方式,调用notifyItemChanged(position)即可。

2、notifyItemChanged(int position)。

可以在平板上或者手机上直接刷新部分内容。但对有焦点需求的机顶盒来说不可行,原因在于一旦刷新,焦点会失控乱飞。

3、根据getTag的方式获取并刷新这个view,做法如下:

if (mFocusRecyclerView != null) {
int firstPosition = mLinearLayoutManager.findFirstVisibleItemPosition();
int lastPosition = mLinearLayoutManager.findLastVisibleItemPosition();

View childrenView;
for (int i = firstPosition; i <= lastPosition; i++) {
childrenView = mLinearLayoutManager.findViewByPosition(i);
if (childrenView != null && childrenView.getTag() != null) {
SpecialAdapter.SpecialHolder holder = (SpecialAdapter.SpecialHolder) childrenView.getTag();
if (holder!=null&&holder.getCurrentPosition() == position) {
holder.changeViewState(holder.getCurrentPosition());
break;
}
}
}
};


该方式对于第二种方法而言,可以避免了焦点乱飞的问题。只需要在holder中赋予他们位置的值,拿出来比较即可。

4.跟第二点差不多

notifyItemInsertChange(int position,int size)

相比前面三种,这方法主要用于刷新分页获取的数据.

注意:getChildAt()有时候返回是null,我们可以使用findViewHolderForAdapterPosition方法,获取其ViewHolder,

然后使用ViewHolder进行查找。

需要优化的地方:

虽然只更新单个item,不会造成闪烁,但是,如果单个item都很复杂,比如,item中需要从网络上加载图片等等。为了避免多次刷新照成的闪烁,我们可以在加载的时候,为ImageView设置一个Tag,比如imageView.setTag(image_url),

下一次再加载之前,首先获取Tag,比如imageUrl = imageView.getTag(),如果此时的地址和之前的地址一样,我们就不需要加载了,如果不一样,再加载。 这样的方案可以再优化。

终极解决方案

通过 notifyItemChanged来调用onBindViewHolder(holder, position, payloads)的方法。

通过notifyDataSetChanged 来调用onBindViewHolder(holder, position)的方法。

实例说明:

在自定义的Adapter中注意两个方法如:

public class RVLoadAdapter extends RecyclerView.Adapter {
//调用通过notifyDataSetChanged会执行的方法,首次加载布局文件时
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int i) {
final int position = holder.getAdapterPosition();
final VideoViewHolder mHolder = (VideoViewHolder) holder;
mHolder.position = position;

//显示下载的进度,并实现更新
if (list.get(position).getStatus() == DownloadEntry.DownloadStatus.downloading) {
mHolder.mProgressBar.setProgress(list.get(position).getPercent());
}
…………
}

//下载过程中,调用notifyItemChanged(int position,Object payload)时会执行的方法
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
VideoViewHolder mHolder = (VideoViewHolder) holder;
//下载进度更新
if (getItem(position).getStatus() == DownloadEntry.DownloadStatus.downloading) {
mHolder.mProgressBar.setProgress(getItem(position).getPercent());
}
…………
}
}

//在需要的地方进行调用,如下载时
private DataWatcher dataWatcher = new DataWatcher() {
@Override
public void onDataChanged(final DownloadEntry downloadEntry) {
int index = downloadEntries.indexOf(downloadEntry);
if (index != -1) {
downloadEntries.remove(index);
downloadEntries.add(index, downloadEntry);
//          adapter.notifyDataSetChanged(); //进度刷新,但是要抢占焦点 ,会不断刷新整个界面
adapter.updateItemProgress(index);
}
}
};
//在RVLoadAdapter 中调用
public void updateItemProgress(int item) {
notifyItemChanged(item, "itemChanged"); //第二个参数不为空 随意即可
}


下载框架推荐:

Android下载框架,支持单线程和多线程断点下载。

https://github.com/guanchao/GHDownload

实现效果如下:



完美解决了局部更新,TV焦点也不会乱跑的问题,同时性能问题得到解决。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐