美文网首页简友广场
Android基础(6)—滑动组件之ListView和Recyc

Android基础(6)—滑动组件之ListView和Recyc

作者: 危君子频道 | 来源:发表于2020-06-17 17:09 被阅读0次

ListView

基本认知:

ListView绝对可以称得上是Android中最常用的控件之一,几乎所有应用程序都会用到它。由于手机屏幕空间都比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助ListView来实现。

工作原理:

当屏幕需要显示x个item时,那么ListView只会创建x+1个视图,当第一个item离开屏幕时,此item的view就会被拿来重用(用于显示下一个item(即第x+1个)的内容)。

使用方法:

1.创建一个ListViewTest项目,让Android Studio自动创建好活动。然后修改activity_main.xml中的代码,如下:

<?xml version="1.0" encoding="utf-8"?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    </ListView
</LinearLayout

引入了ListView后,即使你的ListView没有内容,可视化编辑器preview仍然会这样显示:

preview.png

2.为了便于管理,可以在res目录下的values文件夹下新建一个array.xml文件,内容如下:

<?xml version="1.0" encoding="utf-8"?
<resources
    <string-array name="apps"
        <item支付宝</item
        <item微博</item
        <item微信</item
        <itemQQ</item
        <item知乎</item
        <item淘宝</item
    </string-array
</resources

3.在Activity初始化的时候加载资源,内容如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    /**
     * 初始化ListView,内容适配
     */
    private void init(){
        //加载array.xml内容
        Resources resources = this.getResources();
        String[] data = resources.getStringArray(R.array.apps);
        //适配器,适配内容
        ArrayAdapter<String adapter = new ArrayAdapter<String(
                MainActivity.this, android.R.layout.simple_list_item_1, data
        );
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}
添加item.png

4.适配器Adapter介绍:

既然ListView是用于展示大量数据的,这些数据可以是从网上下载的,也可以是从数据库中读取的,应该视具体的应用场景来决定。数组中的数据是无法直接传递给ListView的,还需要借助适配器来完成。Android中提供了很多的适配器的实现类,其中最简单的就是ArrayAdapter(注意不是SimpleAdapter)。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入即可。ArrayAdapter有多个构造函数的重载,根据需要选择最合适的一种。

①ArrayAdapter:简单、易用的Adapter,用于将数组绑定为列表项的数据源,支持泛型操作

②SimpleAdapter:功能强大的Adapter,用于将XML中控件绑定为列表项的数据源

③SimpleCursorAdapter:与SimpleAdapter类似,用于绑定游标(直接从数据数取出数据)作为列表项的数据源

④BaseAdapter:可自定义ListView,通用用于被扩展。扩展BaseAdapter可以对各个列表项进行最大程度的定制。

这里简单介绍其中的一种:new ArrayAdapter<( Context context , @LayoutRes int resource , String[] objects );

Context:context表示上下文对象,参数:MainActivity.this

@LayoutRes:resource表示ListView子项布局的id,参数:android.R.layout.simple_list_item_1(android.R.layout.simple_list_item_1这是Android内置的布局文件,里面只有一个TextView,可用于简单显示一段文本)

List<String | String[]:objects中指定要适配的数据。

适配器构建好之后,还需要调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。

5.定制ListView

首先定义一个实体类,作为ListView适配器的适配类型。新建类Apps,代码如下:

public class Apps {
    private String name;
    private int icon;

    public Apps(String name, int icon) {
        this.name = name;
        this.icon = icon;
    }

    public String getName() {
        return name;
    }

    public int getIcon() {
        return icon;
    }
}

然后需要为ListView的子项指定一个自定义的布局,在layout目录下新建list_view_item_layout.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"

    <ImageView
        android:id="@+id/iv_apps_icon"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        android:scaleType="fitXY" /

    <TextView
        android:id="@+id/tv_apps_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="30dp" /

    <Switch
        android:id="@+id/sh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:layout_marginRight="30dp" /

</LinearLayout

为了简洁,我在ImageView和TextView没有引入内容。其实引入内容并不会对结果造成影响,而且可以在preview中预览,以便调整布局。

接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Apps类。新建类AppsAdapter,代码如下:

public class AppsAdapter extends ArrayAdapter<Apps {
    private int resourceID;  //resourceID指定ListView的布局方式
    //重写AppsAdapter的构造器
    public AppsAdapter(@NonNull Context context, int resource, List<Apps objects) {
        super(context, resource, objects);
        resourceID = resource;
    }

    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        //获取当前Apps实例
        Apps apps = getItem(position);
        //使用LayoutInflater为子项加载传人布局
        View view = LayoutInflater.from(getContext()).inflate(resourceID, null);
        ImageView appsIcon = (ImageView) view.findViewById(R.id.iv_apps_icon);
        TextView appsName = (TextView) view.findViewById(R.id.tv_apps_name);
        //传入Apps对象的属性
        appsIcon.setImageResource(apps.getIcon());
        appsName.setText(apps.getName());
        return view;
    }
}

AppsAdapter重写了父类的一组构造函数,用于将上下文、ListView子项布局的id和数据都传递进来。另外又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。

在getView方法中,首先通过getItem()方法得到当前项的Apps实例,然后使用LayoutInflater来为这个子项加载我们传入的布局,接着调用View的findViewById()方法分别获取到ImageView和TextView的实例,并分别调用它们的setImageResource()和setText()方法来设置显示的图片和文字,最后将布局返回,自定义适配器就完成了。下面修改MainActivity中的代码中的init()为init_Entity(),如下所示:

private void init_Entity() {
        Apps alipay = new Apps("支付宝", R.drawable.ic_alipay);
        Apps weibo = new Apps("微博", R.drawable.ic_weibo);
        Apps wechat = new Apps("微信", R.drawable.ic_wechat);
        Apps QQ = new Apps("QQ", R.drawable.ic_qq);
        Apps taobao = new Apps("淘宝", R.drawable.ic_taobao);
        apps.add(alipay);
        apps.add(weibo);
        apps.add(wechat);
        apps.add(QQ);
        apps.add(taobao);
        AppsAdapter adapter = new AppsAdapter(MainActivity.this, R.layout.list_view_item_layout, apps);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }

效果图:

复杂item.png

提高效率:

说ListView这个控件很难用,就是因为它有很多的细节可以优化,其中运行效率就是很重要的一点。目前我们的ListView运行效率是很低的,但Item量很少,可能体现不出来。但是当我们给上面加载资源代码来个循环,你就会发现ListView滑动卡顿。因为在AppsAdapter的getView()方法中每次都将布局重新加载了一遍,当ListView快速滚动的时候这就会成为性能的瓶颈。

getView()中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。在getView()方法中进行了判断,如果convertView为空,则使用LayoutInflater去加载布局,如果不为空则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候可以表现出更好的性能。

不过,虽然现在已经不会再去重复加载布局,但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例。

我们可以借助一个ViewHolder内部类来对这部分性能进行优化,修改BrowserAdapter中的setView()方法,如下所示:

//使用ConvertView,ViewHolder提高运行效率
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Apps apps = getItem(position);
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceID, null);
            viewHolder = new ViewHolder();
            viewHolder.appsIcon = (ImageView) view.findViewById(R.id.iv_apps_icon);
            viewHolder.appsName = (TextView) view.findViewById(R.id.tv_apps_name);
            viewHolder.sh = (Switch) view.findViewById(R.id.sh);
            view.setTag(viewHolder);
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.appsIcon.setImageResource(apps.getIcon());
        viewHolder.appsName.setText(apps.getName());
        return view;
    }

    class ViewHolder {
        ImageView appsIcon;
        TextView appsName;
        Switch sh;
    }

新增了一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为空的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为空的时候调用View的getTag()方法,把ViewHolder重新取出。这样所有的控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewById()方法来获取控件实例了。经过这两步的优化之后,ListView的运行效率完全可以满足我们的需要了。

优化前后内存对比:

优化前:

优化前内存.png

优化后:

优化后内存.png

可以看出,无论优化前后,刚启动时所需内存都是67M左右,但是当我们快速滑动时,优化后的ListView内存从53M下降到了37M左右,不再卡顿,运行效率得到了提高。

ListView的点击时间

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<? parent, View view, int position, long id) {
                Apps app = apps.get(position);
                Toast.makeText(MainActivity.this, app.getName().toString(), Toast.LENGTH_SHORT).show();
            }
        });

使用setOnItemClickListener()方法来为ListView注册一个监听器,当用户点击了ListView中的任何一个子项时都会回调onItemClick()方法,在这个方法中可以通过position参数判断出用户点击的是哪一个子项,然后执行相应的程序。

RecyclerView

基本认知:

在android5.0,开始出现了一些新特性,其中就包括了非常受欢迎的RecyclerView。RecyclerView是一种新的视图组,其目的是为任何基于适配器的视图提供相似的渲染方式。该控件用于在有限的窗口中展示大量数据集,它被作为ListView和GirdView控件的继承者。RecyclerView高度接耦,异常的灵活,通过设置不同的LayoutManager,ItemDecoration,ItemAnimator来实现令人想象不到的效果。

工作原理:

当屏幕需要显示x个item时,那么ListView只会创建x+1个视图,当第一个item离开屏幕时,此item的view就会被拿来重用(用于显示下一个item(即第x+1个)的内容)。

重要类及优点:

RecyclerView.Adapter :强制使用ViewHolder,性能优化,不需要重写getView(),只需要写好ViewHolder,View 的复用已经封装好。

LayoutManager:管理布局,设置为LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager可以轻易地实现ListView,GridView以及流式布局的列表效果。它还可以管理滚动和循环利用。

ItemAnimator:可实现增删动画

优点:

1.可通过复用ViewHolder实现view的复用,Item复用性高

2.高度解耦,灵活,可定制性、可扩展性高

3.通过布局管理器LayoutManager控制其显示方式

4.通过ItemDecoration控制Item间的间隔

5.通过ItemAnimator控制Item增删动画

使用方法:

1.添加依赖:

在项目的build.gradle中添加相应的依赖库:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //添加依赖
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

2.在布局文件中使用:

<?xml version="1.0" encoding="utf-8"?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/

</LinearLayout

3.为RecyclerView准备一个适配器:(最重要就是这个类了)

public class IconAdapter extends RecyclerView.Adapter<IconAdapter.ViewHolder {
    private List<Icon mIconlist;

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.icon_item, parent, false);
        final ViewHolder holder = new ViewHolder(view);
        holder.IconView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int position = holder.getAdapterPosition();
                Icon icon = mIconlist.get(position);
                Toast.makeText(view.getContext(), "you clicked view is " + icon.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        holder.IconImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int position = holder.getAdapterPosition();
                Icon icon = mIconlist.get(position);
                Toast.makeText(view.getContext(), "you clicked image is " + icon.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Icon icon = mIconlist.get(position);
        holder.IconImage.setImageResource(icon.getImageId());
        holder.IconTxt.setText(icon.getName());
    }

    @Override
    public int getItemCount() {
        return mIconlist.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        View IconView;
        ImageView IconImage;
        TextView IconTxt;

        public ViewHolder(View view) {
            super(view);
            IconView = view;
            IconImage = (ImageView) view.findViewById(R.id.icon_image);
            IconTxt = (TextView) view.findViewById(R.id.icon_txt);
        }
    }


    public IconAdapter(List<Icon IconList) {
        mIconlist = IconList;
    }
}

4.在活动中使用RecyclerView,代码如下:(数据绑定,初始化等步骤和ListView的使用一致,不多说。)

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
IconAdapterHorization adapter = new IconAdapterHorization(iconList);
recyclerView.setAdapter(adapter);

最后贴一张三种布局的运行图:

RecyclerView.png

上一篇:Android基础(5)—自定义View
下一篇:Android基础(7)—异步消息处理机制 Handler

精彩内容不够看?更多精彩内容,请到微信搜索 “危君子频道” 订阅号,每天更新,欢迎大家关注订阅!

微信公众号

相关文章

网友评论

    本文标题:Android基础(6)—滑动组件之ListView和Recyc

    本文链接:https://www.haomeiwen.com/subject/pvretktx.html