Android ListView优化之局部刷新(更新)(非notifyDataSetChanged)

简介: Android ListView优化之局部刷新(更新)(非notifyDataSetChanged)

在Android开发中我们经常会用到listview的数据和界面刷新动作,我们每次可能会用到的都是Adapter.notifyDataSetChanged()方法。这个方法的原理是利用观察者模式对我们的数据源进行监听,当我们的数据源发生变化的时候,会调用Adapter的getView()方法进行整个界面的刷新。这样的话我们发现,getview()会调用多次,刷新了好多个不需要刷新的item,这样的话相对而言,降低了效率。但是,我们有的情况下是只需要对某个item的数据进行刷新就可以了。这样的话,当数据很多的时候,会提高效率。

有的人可能会说,没有必要去优化这个。怎么说呢,至少这样会让我们更深入的去了解listview的特性。


1.先看效果图

image.png

2.先看一般的Adapter.notifyDataSetChanged()方法刷新界面

  • 1.主界面的布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context="cn.bluemobi.dylan.listviewupdate.MainActivity">
<ListView
    android:id="@+id/listview"
    android:divider="#666666"
    android:dividerHeight="1px"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
</RelativeLayout>

2.listview中的item的布局文件item.xml

1.<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
    android:text="万能适配器测试"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:gravity="center"
    android:textSize="18sp"
    android:textColor="#000000"
    android:id="@+id/textView" />
</LinearLayout>

3.activity中的代码

这里面用到了万能ViewHolder,不了解可以去这里http://blog.csdn.net/linglongxin24/article/details/52808656 了解详情

同时也用到了万能适配器,不了解可以去这里http://blog.csdn.net/linglongxin24/article/details/52813227 了解详情

package cn.bluemobi.dylan.listviewupdate;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import cn.bluemobi.dylan.listviewupdate.adapter.CommonAdapter;
import cn.bluemobi.dylan.listviewupdate.adapter.CommonViewHolder;
public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private List<String> datas;
    private CommonAdapter commonAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        updateTest();
    }
    /**
     * 一般的更新界面
     */
    private void updateTest() {
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.listview);
        datas = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            datas.add("万能适配器测试" + i);
        }
        final CommonAdapter commonAdapter = new CommonAdapter<String>(this, datas, R.layout.item) {
            @Override
            protected void convertView(View item, String s) {
                TextView textView = CommonViewHolder.get(item, R.id.textView);
                textView.setText(s);
            }
        };
        listView.setAdapter(commonAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                datas.set(position, "update 万能适配器测试" + position);
                commonAdapter.notifyDataSetChanged();
            }
        });
    }
    }

image.png

3.ListView局部刷新方法一:更新对应view的内容

这种方法先通过listView.getChildAt(position)拿到要更新的对应的item布局文件,然后再通过findViewById找到对应的控件进行设置。

1.package cn.bluemobi.dylan.listviewupdate;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import cn.bluemobi.dylan.listviewupdate.adapter.CommonAdapter;
import cn.bluemobi.dylan.listviewupdate.adapter.CommonViewHolder;
public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private List<String> datas;
    private CommonAdapter commonAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        updateOneTest();
    }
    /**
     * 只是局部更新某个界面
     */
    private void updateOneTest() {
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.listview);
        datas = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            datas.add("万能适配器测试" + i);
        }
        commonAdapter = new CommonAdapter<String>(this, datas, R.layout.item) {
            @Override
            protected void convertView(View item, String s) {
                TextView textView = CommonViewHolder.get(item, R.id.textView);
                textView.setText(s);
            }
        };
        listView.setAdapter(commonAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                datas.set(position, "update 万能适配器测试" + position);
                updateSingle(position);
            }
        });
    }
      /**
     * 第一种方法 更新对应view的内容
     *
     * @param position 要更新的位置
     */
    private void updateSingle(int position) {
        /**第一个可见的位置**/
        int firstVisiblePosition = listView.getFirstVisiblePosition();
        /**最后一个可见的位置**/
        int lastVisiblePosition = listView.getLastVisiblePosition();
        /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/
        if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
            /**获取指定位置view对象**/
            View view = listView.getChildAt(position - firstVisiblePosition);
            TextView textView = (TextView) view.findViewById(R.id.textView);
            textView.setText(datas.get(position));
        }
    }
}

4.ListView局部刷新方法二:通过ViewHolder去设置值

通过Item找出对应的ViewHolder,然后通过ViewHolder去设置值

1. 
    /**
     * 第二种方法 找出对应的ViewHolder,通过ViewHolder去设置值
     *
     * @param position 要更新的位置
     */
    private void updateOne(int position) {
        /**第一个可见的位置**/
        int firstVisiblePosition = listView.getFirstVisiblePosition();
        /**最后一个可见的位置**/
        int lastVisiblePosition = listView.getLastVisiblePosition();
        /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/
        if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
            /**获取指定位置view对象**/
            View view = listView.getChildAt(position - firstVisiblePosition);
            /**通过ViewHolder找出缓存的对应控件**/
            TextView textView = CommonViewHolder.get(view, R.id.textView);
            textView.setText(datas.get(position));
        }
    }

5.ListView局部刷新方法三:调用一次getView()方法

这种方法是调用适配器对应的getView方法,用它里面的代码对界面进行刷新。这也是google在IO大会上推荐的做法

package cn.bluemobi.dylan.listviewupdate;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import cn.bluemobi.dylan.listviewupdate.adapter.CommonAdapter;
import cn.bluemobi.dylan.listviewupdate.adapter.CommonViewHolder;
public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private List<String> datas;
    private CommonAdapter commonAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        updateOneTest();
    }
    /**
     * 只是局部更新某个界面
     */
    private void updateOneTest() {
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.listview);
        datas = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            datas.add("万能适配器测试" + i);
        }
        commonAdapter = new CommonAdapter<String>(this, datas, R.layout.item) {
            @Override
            protected void convertView(View item, String s) {
                TextView textView = CommonViewHolder.get(item, R.id.textView);
                textView.setText(s);
            }
        };
        listView.setAdapter(commonAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                datas.set(position, "update 万能适配器测试" + position);
               updateItem(position);
            }
        });
    }
     /**
     * 第三种方法 调用一次getView()方法;Google推荐的做法
     *
     * @param position 要更新的位置
     */
    private void updateItem(int position) {
        /**第一个可见的位置**/
        int firstVisiblePosition = listView.getFirstVisiblePosition();
        /**最后一个可见的位置**/
        int lastVisiblePosition = listView.getLastVisiblePosition();
        /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/
        if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
            /**获取指定位置view对象**/
            View view = listView.getChildAt(position - firstVisiblePosition);
            commonAdapter.getView(position, view, listView);
        }
    }
    }

image.png

6.最后封装在万能适配器当中

package cn.bluemobi.dylan.listviewupdate.adapter;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import java.util.List;
/**
 * Created by yuandl on 2016-10-13.
 * 万能适配器
 */
public abstract class CommonAdapter<T> extends BaseAdapter {
    private Context context;
    private List<T> datas;
    private int layoutId;
    public CommonAdapter(Context context, List<T> datas, int layoutId) {
        this.context = context;
        this.datas = datas;
        this.layoutId = layoutId;
    }
    @Override
    public int getCount() {
        return datas == null ? 0 : datas.size();
    }
    @Override
    public T getItem(int position) {
        return datas.get(position);
    }
    @Override
    public long getItemId(int position) {
        return position;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(layoutId, null);
        }
        Log.d("listview", "---------getView()-----------");
        T t = getItem(position);
        convertView(convertView, t);
        return convertView;
    }
    /**
     * 局部更新数据,调用一次getView()方法;Google推荐的做法
     *
     * @param listView 要更新的listview
     * @param position 要更新的位置
     */
    public void notifyDataSetChanged(ListView listView, int position) {
        /**第一个可见的位置**/
        int firstVisiblePosition = listView.getFirstVisiblePosition();
        /**最后一个可见的位置**/
        int lastVisiblePosition = listView.getLastVisiblePosition();
        /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/
        if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
            /**获取指定位置view对象**/
            View view = listView.getChildAt(position - firstVisiblePosition);
            getView(position, view, listView);
        }
    }
    /**
     * 需要去实现的对item中的view的设置操作
     *
     * @param item
     * @param t
     */
    protected abstract void convertView(View item, T t);
}

这样的话,我们每次更新的时候只需要调用notifyDataSetChanged(ListView listView, int position),传入对应的要更新的listview和要更新的位置position即可


7.总结

这三种方法的核心就是找出你要更新Item的contentView.然后再去操作。因为ListView默认只会加载一屏的数据,所以要判断其可见范围。不可见的在滑动的时候getView会自动调用更新数据。最后要强调的一点就是关于布局优化,最好将item的高度设置为一个固定的值,这样能减少getView的调用次数。因为一个不确定的值,ListView会频繁调用多次getView去确定其高度和渲染。



目录
相关文章
|
4月前
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
67 10
|
5月前
|
Java Android开发
Android面试题经典之Glide取消加载以及线程池优化
Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。
146 2
|
3月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
68 20
Android经典面试题之图片Bitmap怎么做优化
|
2月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
51 4
|
23天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
3月前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
203 9
|
3月前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
64 2
|
3月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
4月前
|
Ubuntu Android开发
安卓系统调试与优化:(一)bootchart 的配置和使用
本文介绍了如何在安卓系统中配置和使用bootchart工具来分析系统启动时间,包括安装工具、设备端启用bootchart、PC端解析数据及分析结果的详细步骤。
197 0
安卓系统调试与优化:(一)bootchart 的配置和使用
|
3月前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
75 0