美文网首页简友广场
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