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

需求背景: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焦点也不会乱跑的问题,同时性能问题得到解决。

猜你喜欢

转载自blog.csdn.net/jun5753/article/details/79165468
今日推荐