Android学习笔记——使用Recycleview显示列表

单例与数据集中存储
    单例是特殊的Java类,在创建实例时,一个单例类仅允许创建一个实例。
    应用能在内存里活多久,单例就能活多久。因此将对象列表保存在单例里的话,就能随时获取crime数据,不管activity和fragment周期怎么变化。使用单例时还应该注意一点:Android从内存中清除应用时,单例对象也会随之消失。后文会对单例进一步介绍。

RecyclerView、ViewHolder和Adapter
    RecyclerView是ViewGroup的子类,每一个列表项都是作为一个View子对象来显示的。这些View子对象可简单可复杂,这取决于列表项要显示些什么。
    一次为所有的列表项创建View很容易搞垮应用。RecyclerView只创建刚好可以充满屏幕的N个View,而不是一次创建所有的。用户滑动切换视图时,上一个视图会回收利用。顾名思义,RecyclerView就是回收再利用,循环往复。
RecyclerView的任务仅限于回收和定位屏幕上的View。列表项View能够显示数据还离不开另外两个类的支持:ViewHolder子类和Adapter子类。
    ViewHolder只做一件事:容纳View视图。定义每个列表项中包含的组件并将它们实例化,并定义点击事件等。
RecyclerView不能自己创建ViewHolder,而是通过Adapter来完成的。Adapter是一个控制器对象,从模型层获取数据,然后供给RecyclerView显示,是沟通的桥梁。
Adapter负责:

  1.创建必要的ViewHoler;
  2.绑定ViewHolder至模型层数据。
      RecyclerView需要显示视图对象时,就会去找它的Adapter。
      首先,调用Adapter的getItemCount()方法,RecyclerView会询问数组列表中包含多少个对象。
      接着,RecyclerView调用Adapter的onCreateViewHolder(ViewGroup, int)方法创建ViewHolder及其要显示的视图。
      最后,RecyclerView会传入ViewHolder及其位置,调用onBindViewHolder(ViewHolder, int)方法。Adapter会找到目标位置的数据并将其绑定在ViewHolder视图上。所谓绑定,就是使用模型数据填充视图。
LayoutManager
    RecyclerView类不会亲自摆放屏幕上的列表项。实际上,摆放的任务被委托给了LayoutManager。除了在屏幕上的摆放,LayoutManager还负责定义屏幕滚动行为。除了一些Android操作系统内置版实现,LayoutManager还有很过第三方库实现版本。LinearLayoutManager类使用竖直列表的形式展示列表项。后续还会使用GridLayoutManager类,以网格的形式展示列表项。

ListView和GridView
    Android操作系统核心库包含ListView、GridView和Adapter这3个类。在Android 5.0之前,创建列表项或网各项都应该优先使用这些类。
    这列类的API与RecyclerView的API非常相似。ListView和GridView不关系具体的展示项,只负责展示项的滚动。Adapter负责创建列表项的所有视图。不过,使用ListView和GrisView时不一定非要使用ViewHolder模式(虽然可以并且应该使用)。
现在已经被RecyclerView替代,举例说明替代理由:

    1.ListView的API不支持创建水平滚动的ListView,因此需要许多额外的定制工作。使用RecyclerView时,虽然创建定制布局和滚动行为也需要额外的工作,但RecyclerView天生支持拓展,所以使用体验还不错。
    2.RecyclerView还有支持列表项动画效果的优点。如果要让ListView和GridView支持添加和删除列表项的动画效果,实现起来既复杂又容易出错;

 

挑战练习:RecyclerView ViewType

请在RecyclerView中创建两类列表项:一般性crime,以及需警方介入的crime。要完成这个挑战,你需要用到RecyclerView. Adapter的视图类别功能(view type)。在Crime对象里,再添加一个mRequiresPolice实例变量,使用它并借助getltemViewType(int)方法(developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)), 确定该加载哪个视图到CrimeAdapter。
在onCreateViewHolder(ViewGroup, int)方法里,基于getltemViewType(int)方法返回的viewType值,需要返回不同的ViewHolder。如果是一般性crime,就仍然使用原始布局;如果是需警方介人的crime,就使用一个带联系警方按钮的新布局

思路:
模型层需要在Crime对象里添加一个mRequiresPolice实例变量,用于控制是否需要显示特定的视图;视图层需要新定义一个列表项视图,其中包括报警的按钮;然后控制层对应创建新添列表项的ViewHolder,ViewHolder的选择机制在onCreateViewHolder(ViewGroup, int)方法里完成。

更改Crime对象:
Crime.java

……
    private int mRequiresPolice;
……
    public int getRequiresPolice() {
        return mRequiresPolice;
    }
 
    public void setRequiresPolice(int requiresPolice) {
        mRequiresPolice = requiresPolice;
    }
……

还要编辑CrimeLab在构造Crime数组时,设定好哪些Crime对象需要报警:
CrimeLab.java

……
private CrimeLab(Context context) {
        // 单例类使用私有的构造方法,使得外部无法直接实例化单例类
        mCrimes = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Crime crime = new Crime();
            crime.setTitle("Crime #" + i);
            crime.setSolved(i % 2 == 0);
            // 初始化哪些Crime对象需要报警
            crime.setRequiresPolice(i % 2);
            mCrimes.add(crime);
        }
    }
……

新建列表项布局文件:
list_item_crime_requires_police.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
 
    <TextView
        android:id="@+id/crime_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Crime 标题"
        app:layout_constraintEnd_toStartOf="@+id/call_police_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
    <TextView
        android:id="@+id/crime_date"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="Crime 日期"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/call_police_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/crime_title" />
 
    <Button
        android:id="@+id/call_police_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="一键报警"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

接下来在CrimeListFragment中改动,新增CrimeRequiresPoliceHolder,修改CrimeAdapter:
CrimeListFragment.java

……
 
    private class CrimeRequiresPoliceHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
 
        private TextView mTitleTextView;
        private TextView mDateTextView;
        private Button mCallPoliceButton;
        private Crime mCrime;
 
        public CrimeRequiresPoliceHolder(LayoutInflater inflater, ViewGroup parent) {
            super(inflater.inflate(R.layout.list_item_crime_requires_police, parent, false));
            itemView.setOnClickListener(this);
            mTitleTextView = itemView.findViewById(R.id.crime_title);
            mDateTextView = itemView.findViewById(R.id.crime_date);
            mCallPoliceButton = itemView.findViewById(R.id.call_police_button);
            mCallPoliceButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(getActivity(), "我要报警啦!", Toast.LENGTH_SHORT).show();
                }
            });
        }
 
        public void bind(Crime crime) {
            mCrime = crime;
            mTitleTextView.setText(mCrime.getTitle());
            mDateTextView.setText(mCrime.getDate().toString());
        }
 
        @Override
        public void onClick(View v) {
            Toast.makeText(getActivity(), mCrime.getTitle() + "Clicked!", Toast.LENGTH_SHORT).show();
        }
    }
……
private class CrimeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private List<Crime> mCrimes;
 
        public CrimeAdapter(List<Crime> crimes) {
            mCrimes = crimes;
        }
 
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
            if (viewType != 0) return new CrimeRequiresPoliceHolder(layoutInflater, parent);
            return new CrimeHolder(layoutInflater, parent);
        }
 
        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
            Crime crime = mCrimes.get(position);
            if (crime.getRequiresPolice() != 0) {
                CrimeRequiresPoliceHolder crimeRequiresPoliceHolder = (CrimeRequiresPoliceHolder) holder;
                crimeRequiresPoliceHolder.bind(crime);
            }else {
                CrimeHolder crimeHolder = (CrimeHolder) holder;
                crimeHolder.bind(crime);
            }
        }
 
        @Override
        public int getItemCount() {
            return mCrimes.size();
        }
 
        @Override
        public int getItemViewType(int position) {
            return mCrimes.get(position).getRequiresPolice();
        }
    }
……

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值