一,简介

1. RecyclerView、Adapter和 ViewHolder的关系

我们需要CrimeListFragment向用户展示crime列表,这就要用到RecyclerView类。RecyclerView是ViewGroup的子类,每一个列表项都是作为一个View子对象显示的。这些View子对象既可以是复杂的View对象,也可以是简单的View对象,这取决于我们对列表显示复杂度的需要

RecyclerView的任务仅限于回收和定位屏幕上的TextView。TextView能够显示数据还离不开另外两个类的支持:Adapter子类和ViewHolder子类。ViewHolder要做的事很少,我们首先讨论它。顾名思义,ViewHolder只做一件事:容纳View视图

recyclerView设置多种item recyclerview分组显示_ide


recyclerView设置多种item recyclerview分组显示_android_02


adapter
图9-6进行了简化,实际上隐藏了一些信息。RecyclerView自己不创建ViewHolder。这个任务实际是由adapter来完成的。adapter是个控制器对象,从模型层获取数据,然后提供给RecyclerView显示,起到了沟通的桥梁作用。
adapter负责:
 创建必要的ViewHolder;
 绑定ViewHolder至模型层数据。
要创建adapter,首先要定义RecyclerView.Adapter子类。然后由它封装从CrimeLab获取的crime。

RecyclerView需要显示视图对象时,就会去找它的adapter。图9-7展示了一个RecyclerView可能发起的会话。
首先,通过调用adapter的getItemCount()方法,RecyclerView询问数组列表中包含多少个对象。
接 着 , RecyclerView 调 用adapter的 createViewHolder(ViewGroup, int)方 法 创 建ViewHolder以及ViewHolder要显示的视图。
最后, RecyclerView会传入ViewHolder及其位置,调用onBindViewHolder(ViewHolder,int)方法。adapter会找到目标位置的数据并绑定到ViewHolder的视图上。所谓绑定,就是使用模型数据填充视图。
整个过程执行完毕, RecyclerView就能在屏幕上显示crime列表项了。需要注意的是,相对于onBindViewHolder(ViewHolder, int)方法,createViewHolder(ViewGroup, int)方法
的调用并不频繁。一旦创建了够用的ViewHolder,RecyclerView就会停止调用createViewHolder(...)方法。然后,通过回收利用旧的ViewHolder节约时间和内存。

recyclerView设置多种item recyclerview分组显示_List_03


实质上,recycler类似于安卓内联的listview,只是其是支持库而已

添加支持库:

单击File→Project Structure....菜单项切换至项目结构窗口,选择左边的app模块,然后单击Dependencies选项页。单击+按钮弹出依赖库添加窗口。找到并选择recyclerview-v7支持库,单击OK按钮完成依赖库添加,



例程:该例子是基于fragment显示的

1. RecyclerView的XML,Recycler界面用于容乃列表项:


<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/crime_recycler_view"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

</android.support.v7.widget.RecyclerView>

2. 列表项的布局XML,用于设置每一项的布局:


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

    <TextView
        android:id="@+id/crime_list_LabelText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:padding="4dp"
        android:layout_toLeftOf="@+id/crime_list_CheckBox"
        android:text="TextView"/>

    <TextView
        android:id="@+id/crime_list_DateText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@+id/crime_list_CheckBox"
        android:layout_below="@id/crime_list_LabelText"
        android:padding="4dp"
        android:text="TextView"/>

    <CheckBox
        android:id="@+id/crime_list_CheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="CheckBox"
        android:padding="4dp"/>


</RelativeLayout>

3. Crime类,自定义类,表示每个元素的数据,模型里的单个数据结构:


//基本元素单位类
public class Crime {

    private UUID yID;
    private String yTitle;
    private Date yDate;
    private  boolean CrimeFlag;


    public Crime() {
        yID = UUID.randomUUID();
        yDate = new Date();
    }

    public UUID getyID() {
        return yID;
    }

    public String getyTitle() {
        return yTitle;
    }

    public void setyTitle(String yTitle) {
        this.yTitle = yTitle;
    }

    public Date getyDate() {
        return yDate;
    }

    public void setyDate(Date yDate) {
        this.yDate = yDate;
    }

    public boolean getCrimeFlag() {
        return CrimeFlag;
    }

    public void setCrimeFlag(boolean crimeFlag) {
        CrimeFlag = crimeFlag;
    }
}

4. List类,模型本身:


//元素组合类
public class CrimeListLab {

    private static CrimeListLab yCrimeLab;
    private List<Crime>yCrime;

    private CrimeListLab(Context context)
    {
        yCrime = new ArrayList<>();
        for(int i=0;i<100;i++){
            Crime crime = new Crime();
            crime.setyTitle("Crime #" + i);
            crime.setCrimeFlag(i%2 == 0);
            yCrime.add(crime);

        }
    }

    public static CrimeListLab getyCrimeLab(Context context) {

        if(null == yCrimeLab){
            yCrimeLab = new CrimeListLab(context);
        }

        return yCrimeLab;
    }

    public List<Crime> getyCrime() {
        return yCrime;
    }

    public Crime getCrime(UUID uuid) {
        for (Crime crime:yCrime) {
            if(crime.getyID().equals(uuid)){
                return crime;
            }
        }

        return null;
    }
}

5. fragment的内容,也是决定Recycler如何显示:


//控制RecyclerView的Fragment
public class CrimeListFragment extends Fragment{

    //RecyclerView对象
    private RecyclerView yCrimeRecyclerView;
    //Adapter对象
    private CrimeAdapter yCrimeAdapter;



    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        //绑定布局
        View view = inflater.inflate(R.layout.fragment_crime_list, container, false);

        //绑定并实例化Recycler
        yCrimeRecyclerView = (RecyclerView) view
               .findViewById(R.id.crime_recycler_view);
        yCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        //自定义的将Adapter与Recycler绑定的函数
        updateUI();

        return view;
    }

    //自定义的将Adapter与Recycler绑定的函数
    public void updateUI(){

        CrimeListLab crimelistlab = CrimeListLab.getyCrimeLab(getActivity());
        List<Crime>crimeList = crimelistlab.getyCrime();

        yCrimeAdapter = new CrimeAdapter(crimeList);
        yCrimeRecyclerView.setAdapter(yCrimeAdapter);

    }




    //继承Holder的内部类定义,该类实例化布局内部组件资源
    //此处还继承监听事件接口
    public class CrimeHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        private Crime yCrime;//用于接受Crime元素对象
        private TextView yTitleLabel;
        private TextView yTitleDate;
        private CheckBox yCheckBox;

        public CrimeHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(this);//监听
            //关联布局内的组件资源实例化
            yTitleLabel = (TextView) itemView.findViewById(R.id.crime_list_LabelText);
            yTitleDate = (TextView) itemView.findViewById(R.id.crime_list_DateText);
            yCheckBox = (CheckBox) itemView.findViewById(R.id.crime_list_CheckBox);
        }

        //自定义方法,用于在Adapter的视图和数据绑定函数调用以实现数据绑定
        public void bindCrime(Crime crime){
            yCrime = crime;//通过每个传进来的Crime元素来获取数据
            yTitleLabel.setText(yCrime.getyTitle());
            yTitleDate.setText(yCrime.getyDate().toString());
            yCheckBox.setChecked(yCrime.getCrimeFlag());
        }


        //监听事件的相应
        @Override
        public void onClick(View v){
            Toast.makeText(getActivity(),
                    yCrime.getyTitle() + " clicked!", Toast.LENGTH_SHORT)
                    .show();
        }



    }


    //决定项的布局,项的数目,并将项和数据模型绑定的Adpater类
    public class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder>{

        //用于接收存放List资源的对象
        private List<Crime> yCrimelist;

        //构造类,内部用于接受存放List资源
        public CrimeAdapter(List<Crime>yCrimelist){//可能有内外部类同名隐患
            this.yCrimelist = yCrimelist;
        }


        //创建项视图,关联项布局
        @Override
        public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(getActivity());

            //寻找项的布局文件
            View view = layoutInflater.inflate(R.layout.fragment_crime_list_item,parent,false);

            return new CrimeHolder(view);
        }

        //将视图和数据模型绑定
        @Override
        public void onBindViewHolder(CrimeHolder holder, int position) {
            Crime crime = yCrimelist.get(position);//获取每个crime数据
            holder.bindCrime(crime);//调用CrimeHolder的方法将数据传递并更新

        }

        //返回一共有多少项
        @Override
        public int getItemCount() {
            return yCrimelist.size();
        }
    }


}

6. 托管fragment的Activity


//在Activity建立fragmentmanager来托管xml布局,此时继承的是通用ActivityFragmentManager抽象类
public class CrimeListActivity extends CrimeActivity {

    @Override
    protected Fragment createFragment(){
        return new CrimeListFragment();
    }

}

7. 其他,托管fragment的Activity的超类:


//创建通用的ActivityFragmentManager的抽象类,防止重复代码
public abstract class CrimeActivity extends FragmentActivity {

    protected abstract Fragment createFragment();//抽象创建Fragment布局接口

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

        FragmentManager fm = getSupportFragmentManager();
        Fragment yFragment = fm.findFragmentById(R.id.fragment_container);

        if(null == yFragment){

            yFragment = createFragment();//用抽象接口创建Fragment布局,可以根据需要创建不同接口
            fm.beginTransaction()
                    .add(R.id.fragment_container,yFragment)
                    .commit();
        }

    }

}



深入学习: ListView 和 GridView

Android操作系统核心库包含ListView、 GridView和Adapter类。 Android 5.0之前,创建列表项或网格项都应该优先使用这些类。

这些类的API与RecyclerView非常相似。 ListView和GridView不关心具体的展示项,只负责展示项的滚动。 Adapter负责创建列表项的所有视图。不过,使用ListView和GridView不一定非要使用ViewHolder模式(虽然可以并且应该使用)。

过去传统的实现方式现已被RecyclerView的实现方式取代,因为我们不用再费力地去调整ListView和GridView的工作行为了。举例来说, ListView API不支持创建水平滚动的ListView,我们需要许多额外的定制工作。

使用RecyclerView时,虽然创建定制布局和滚动行为需要额外的工作,但RecyclerView天生支持拓展,所以使用体验还不错。

此外, RecyclerView还有支持列表项动画效果的优点。如果要让ListView和GridView支持添加和删除列表项的动画效果,实施任务既复杂又容易出错;而对于天生支持动画特效的RecyclerView来说,对付这些任务简直是小菜一碟。

口吐莲花,不如直接秀代码。例如,如果crime列表项要从位置0移动到位置5,下面这段代码就可以做到:


mRecyclerView.getAdapter().notifyItemMoved(0, 5);





深入学习:单例
Android开发实践中,经常会用到CrimeLab中使用过的单例模式。然而,单例使用不当的话,会导致应用难以维护,因此它也常遭人诟病。
Android开发常用到单例的一大原因是,它们比fragment或activity活得久。例如,在设备旋转或是在fragment和activity间跳转的场景下,单例不会受到影响,而旧的fragment或activity已经不复存在了。
单例能方便地存储控制模型层对象。假设有个比CriminalIntent更为复杂的CriminalIntent应用,它的许多个activity和fragment会修改crime数据。某个控制单元修改了crime数据之后,怎么保证发送给其他控制单元的是最新数据呢?如果CrimeLab掌控数据对象,所有的修改都由它来处理,是不是数据的一致性控制就容易多了?而且,在控制单元间流转时,我们还可以给每个crime添加ID标识,让控制单元使用ID标识从CrimeLab获取完整的crime数据。
再来谈谈单例的缺点。举个例子,虽然单例能存储数据,活得比控制单元长久,但这并不代表它能永存。在我们切换至其他应用,又逢Android回收内存时,单例连同那些实例变量也就不复存在了。结论很明显:单例无法做到持久存储。(将文件写入磁盘或是发送到Web服务器是不
错的数据持久化存储方案。)
单例还不利于单元测试。例如,如果应用代码直接调用CrimeLab对象的静态方法,测试时以模拟版本的CrimeLab代替实际CrimeLab实例就不太现实。实践中, Android开发人员会使用依赖注入工具解决这个问题。这个工具允许以单例模式使用对象,对象也可以按需替换。
单例使用很方便,因而很容易被滥用。在想用就用,想存就存之前,希望你能深思熟虑:数据究竟用在哪里?用在哪里能真正解决问题?假如不慎重对待这个问题,很可能后来人在查看你的单例代码时,就像打开了一个满是乱糟糟废品的抽屉:废电池、拉链扣、旧照片,等等。它们有什么存在的意义?再强调一次:请确保有充足的理由使用单例模式存储你的共享数据!
使用得当,单例就是拥有优秀架构的Android应用中的关键部件