AdapterView和Adapter及其子类的使用

AdapterView

AdapterView是一组重要的组件,AdapterView本身是一个抽象基类,它派生的子类在用法上十分相似,只是显示界面有一定的区别。
AdapterView具有如下特征

  • AdapterView继承ViewGrop,它的本质是容器

  • AdapterView可以包括多个"列表项"并将多个"列表项"以合适的形式显示出来

  • AdapterView 显示的多个"列表项"由Adapter提供。调用AdapterView的setAdapter(Adapter)的方法设置Adapter即可
    AdapterView及其子类的继承关系图如下图所示

在这里插入图片描述
常用的ListView和Spinner都是继承于AdapterView

Adapter接口及实现类

Adapter本身只是一个接口,它派生了ListAdapter和SpinnerAdapter两个子接口,其中ListAdapter为AbsListView提供列表项,而Spinner为AbsSpinner提供列表项。Adapter接口及其实现类的继承关系如下图所示:
在这里插入图片描述
几乎所有的Adapter都继承了BaseAdapter,而BaseAdapter同时实现了ListAdapter、SpinnerAdapter两个接口,因此BaseAdapter及其子类几乎可以全部的AdapterView的子类提供列表项。

适配器通俗理解:适配器就好比是数据的加工流水线,你丢给它一些数据,(数据即使每个adapterView列表项要展示的东西),然后这些东西要以什么样的布局去展示呢,就需要包装盒。这个包装盒就是要为每个列表项进行包装的布局文件。适配器做的工作就是把这些数据塞到包装盒里,然后排序。出来的就是界面上整理的一个个列表框。这个流水线可以很复杂,也可以做的简单一些。常用的适配器,包括ArrayAdapter、SimpleAdapter、BaseAdapter。

在使用AdapterView和设置AdapterView的以下步骤:

  1. activity布局文件引入AdapterView
  2. 创建适配器需要的布局文件
  3. 创建适配器Adapter实例
  4. 获取AdapterView实例
  5. 为AdapterView设置Adapter

Spinner

先介绍一下Spinner ,继承自AbsSpinner。Spinner是下拉框控件,它用于从一串列表中选择某项,其功能类似于单选按钮的组合。下拉列表的展示方式有两种:

  • 一种是在当前下拉框的正下方弹出列表框,此时要把spinnerMode属性设置为dropdown,下面是XML文件中采取下拉模式的Spinner标签例子:
    <Spinner
        android:id="@+id/sp_dropdown"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:spinnerMode="dropdown">
    </Spinner>
  • 另一种是在页面中部弹出列表对话框,此时要把spinnerMode属性设置为dialog,下面是XML文件中采取对话框模式的Spinner标签例子:
   <Spinner
       android:id="@+id/sp_dropdown"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:spinnerMode="dialog">
   </Spinner>

1.布局文件引入Spinner

见上面

创建每个列表项的布局文件

item_select.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:gravity="center"
    android:textColor="#0000ff"
    android:textSize="17sp"
    tools:text="火星" />

每个列表项就是一个TextView

3. 创建适配器Adapter实例


// 定义下拉列表需要显示的文本数组
private final static String[] starArray = {"水星", "金星", "地球", "火星", "木星", "土星"};

// 声明一个下拉列表的数组适配器
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.item_select, starArray);

选择ArrayAdapter适配器。
创建ArrayAdapter时必须指定如下三个参数。

  • Context:这个参数无需多说,它代表了访问整个Android应用的接口。几乎创建所有组件都需要传入Context对象。
  • textViewResourceID:一个资源ID。资源中的组件作为ArrayAdapter的列表项组件
  • 要适配的数据。通常是数组或者List。

4.获取AdapterView实例

Spinner sp_dropdown = findViewById(R.id.sp_dropdown);

5.为AdapterView设置Adapter

p_dropdown.setAdapter(adapter); // 设置下拉框的数组适配器

完整代码如下:

public class SpinnerMainActivity extends AppCompatActivity {

    // 定义下拉列表需要显示的文本数组
    private final static String[] starArray = {"水星", "金星", "地球", "火星", "木星", "土星"};
    private Spinner sp_dropdown;

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


        sp_dropdown = findViewById(R.id.sp_dropdown);

        // 声明一个下拉列表的数组适配器
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.item_select, starArray);


        // 设置下拉框的标题。对话框模式才显示标题,下拉模式不显示标题
        sp_dropdown.setPrompt("请选择行星");
        sp_dropdown.setAdapter(adapter); // 设置下拉框的数组适配器
        sp_dropdown.setSelection(0); // 设置下拉框默认显示第一项
        // 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法
        sp_dropdown.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(SpinnerMainActivity.this, "您选择的是" + starArray[position], Toast.LENGTH_LONG).show();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });


    }
}

此外,在Java代码中,Spinner还可以调用下列4个方法。

  • setPrompt:设置标题文字。注意对话框模式才显示标题,下拉模式不显示标题。
  • setAdapter:设置列表项的数据适配器。
  • setSelection:设置当前选中哪项。注意该方法要在setAdapter方法后调用。
  • setOnItemSelectedListener:设置下拉列表的选择监听器,该监听器要实现接口OnItemSelectedListener。

运行效果如下:

在这里插入图片描述
在这里插入图片描述

ArrayAdapter数组适配器

在演示Spinne控件的时候,调用了setAdapter方法设置列表适配器。这个适配器好比一组数据的加工流水线,你丢给它一大把糖果(大行星的原始数据),适配器先按顺序排列糖果(对应行星数组starArray),然后拿来制作好的包装盒(对应每个列表项的布局文件item_select.xml),把糖果往里面一塞,出来的便是一个个精美的糖果盒(界面上排布整齐的列表框)。这个流水线可以做得很复杂,也可以做得简单一些,最简单的流水线就是之前演示用到的数组适配器ArrayAdapter。

ArrayAdapter主要用于每行列表只展示文本的情况,实现过程分成下列3个步骤:
步骤一,编写列表项的XML文件,内部布局只有一个TextView标签,示例如下:

item_select.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:gravity="center"
    android:textColor="#0000ff"
    android:textSize="17sp"
    tools:text="火星" />

步骤二,调用ArrayAdapter的构造方法,填入待展现的字符串数组,以及列表项的包装盒,即XML文件
R.layout.item_select。构造方法的调用代码示例如下。

/ 声明一个下拉列表的数组适配器
ArrayAdapter<String> starAdapter = new ArrayAdapter<String>(this,R.layout.item_select, starArray);

步骤三,调用下拉框控件的setAdapter方法,传入第二步得到的适配器实例,代码如下:

sp_dropdown.setAdapter(starAdapter); // 设置下拉框的数组适配器

经过以上3个步骤,先由ArrayAdapter明确原料糖果的分拣过程与包装方式,再由下拉框调用
setAdapter方法发出开工指令,适配器便会把一个个包装好的糖果盒输出到界面。

简单适配器SimpleAdatper

ArrayAdapter只能显示文本列表,显然不够美观,有时还想给列表加上图标,这时简单适配器SimpleAdapter就派上用场了,它允许在列表项中同时展示文本与图片。

SimpleAdapter的实现过程略微复杂,因为它的原料需要更多信息。例如,原料不但有糖果,还有贺卡,这样就得把一大袋糖果和一大袋贺卡送进流水线,适配器每次拿一颗糖果和一张贺卡,把糖果与贺卡按规定塞进包装盒。对于SimpleAdapter的构造方法来说,第2个数Map容器放的是原料糖果与贺卡,第3个参数放的是包装盒,第4个参数放的是糖果袋与贺卡袋的名称,第5个参数放的是包装盒里塞糖果的位置与塞贺卡的位置。

    public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
            @LayoutRes int resource, String[] from, @IdRes int[] to) {

SimpleAdapter最大的难点在于创建SimpleAdaper对象,他需要五个参数,第一个参数无需解释,后面4个参数较为关键。

  • 第2个参数:该参数是一个List<? extends Map<String,? >> 类型的集合对象,该集合的每个Map<String , ?>对象生成一个列表项。
  • 第3个参数:该参数指定一个界面布局的ID。使用该界面布局作为列表项组件。
  • 第4个参数:该参数是一个String[]类型的参数,该参数决定提取Map<String,?>对象中哪些key对应的value来生成列表项。
  • 第5个参数:该参数是一个int[]类型的参数,该参数决定填充哪些组件。

编写简单适配器列表项的XML文件

item_simple.xml文件

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

    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_weight="1"
        tools:src="@drawable/diqiu" />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:gravity="center"
        android:textColor="#ff0000"
        android:textSize="17sp"
        tools:text="地球" />

</LinearLayout>

activity对应的完整代码如下:

public class SpinIconMainActivity extends AppCompatActivity {

    // 定义下拉列表需要显示的行星图标数组
    private static final int[] iconArray = {
            R.drawable.shuixing, R.drawable.jinxing, R.drawable.diqiu,
            R.drawable.huoxing, R.drawable.muxing, R.drawable.tuxing
    };
    // 定义下拉列表需要显示的行星名称数组
    private static final String[] starArray = {"水星", "金星", "地球", "火星", "木星", "土星"};
    private Spinner spinner;

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

        // 1.声明一个映射对象的列表,用于保存行星的图表与名称配对信息
         List<Map<String,Object>> list = new ArrayList<>();

        for (int i = 0; i < starArray.length; i++) {

            Map<String, Object> stringObjectMap = new HashMap<>();
            stringObjectMap.put("name",starArray[i]);
            stringObjectMap.put("icon",iconArray[i]);
            list.add(stringObjectMap);

        }

        // 2.创建适配器 
       	// 第5个参数和第4个参数指定使用ID为R.id.tv_name组件显示name对应的值,ID为R.id.iv_icon组件显示icon对应的值。
        SimpleAdapter simpleAdapter = new SimpleAdapter(this, list, R.layout.item_simple, 
        new String[]{"name", "icon"}, new int[]{R.id.tv_name, R.id.iv_icon});

        // 3.设置适配器
        spinner = findViewById(R.id.sp_dropdown);
        spinner.setAdapter(simpleAdapter);
        spinner.setSelection(0);
        spinner.setPrompt("标题:请选择行星");
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(SpinIconMainActivity.this, "您选择的是" + starArray[position], Toast.LENGTH_LONG).show();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });
    }
}

activity布局文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".widgetandview.SpinnerMainActivity">
    <Spinner
        android:id="@+id/sp_dropdown"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:spinnerMode="dialog">
    </Spinner>
    
</LinearLayout>

显示效果如下:

在这里插入图片描述

基本适配器BaseAdapter

数组适配器适用于纯文本的列表数据,简单适配器适用于带图标的列表数据。然而实际应用常常有更复杂的列表,比如每个列表项存在3个以上的控件,这种情况即便是简单适配器也很吃力,而且不易扩展。为此Android提供了一种适应性更强的基本适配器BaseAdapter,该适配器允许开发者在别的代码文件中编写操作代码,大大提高了代码的可读性和可维护性。

从BaseAdapter派生的数据适配器主要实现下面5种方法。

  • 构造方法:指定适配器需要处理的数据集合。
  • getCount:获取列表项的个数。
  • getItem:获取列表项的数据。
  • getItemId:获取列表项的编号。
  • getView:获取每项的展示视图,并对每项的内部控件进行业务处理,这个方法在每个子项被滚动到屏幕内的时候被调用。

继续以下拉框Spinner为载体,演示如何操作BaseAdapter,具体的编码过程分为3步:

步骤一,编写列表项的布局文件,示例代码如下:

## R.layout.item_list

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <!-- 这是显示行星图片的图像视图 -->
    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="0dp"
        android:layout_height="80dp"
        android:layout_weight="1"
        android:scaleType="fitCenter"
        tools:src="@drawable/diqiu" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_marginLeft="5dp"
        android:layout_weight="3"
        android:orientation="vertical">

        <!-- 这是显示行星名称的文本视图 -->
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="start|center"
            android:textColor="@color/black"
            android:textSize="20sp"
            tools:text="地球" />

        <!-- 这是显示行星描述的文本视图 -->
        <TextView
            android:id="@+id/tv_desc"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="2"
            android:gravity="start|center"
            android:textColor="@color/black"
            android:textSize="13sp"
            tools:text="地球是太阳系八大行星之一,排行第三,也是太阳系中直径、质量和密度最大的类地行星,距离太阳1.5亿公里" />
    </LinearLayout>
</LinearLayout>

步骤二,写个新的适配器继承BaseAdapter,实现对列表项的管理操作,示例代码如下:

## PlanetBaseAdapter.java

public class PlanetBaseAdapter extends BaseAdapter {

    private Context mContext; //声明一个上下文对象
    private List<Planet> mPlanetList; // 声明一个行星信息列表


    // 行星适配器的构造方法,传入上下文行星列表
    public PlanetBaseAdapter(Context context, List<Planet> planetList){
        mContext = context;
        mPlanetList = planetList;
    }

    /**
     * 获取列表项的个数
     * @return
     */
    @Override
    public int getCount() {
        return mPlanetList.size();
    }

    /**
     * 获取列表项的数据
     * @param position
     * @return
     */
    @Override
    public Object getItem(int position) {
        return mPlanetList.get(position);
    }

    /**
     * 获取列表项的编号,第几个列表项
     * @param position
     * @return
     */
    @Override
    public long getItemId(int position) {
        return position;
    }

    /**
     * 获取列表项的数据
     * @param position
     * @param convertView
     * @param parent
     * @return
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if(convertView == null){ // 转换视图为空
            // 根据布局文件item_list.xml生成转换视图对象
            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list,null);
            holder = new ViewHolder(); // 创建一个新的视图持有者
            holder.iv_icon = convertView.findViewById(R.id.iv_icon);
            holder.tv_name = convertView.findViewById(R.id.tv_name);
            holder.tv_desc = convertView.findViewById(R.id.tv_desc);
            convertView.setTag(holder); // 将视图持有者保存到转化视图当中
        }else {// 转换视图非空
            // 从转换视图中获取之前保存的视图持有者
            holder = (ViewHolder) convertView.getTag();
        }


        Planet planet = mPlanetList.get(position);
        holder.iv_icon.setImageResource(planet.image); // 显示行星的图片
        holder.tv_name.setText(planet.name);// 显示行星的名称
        holder.tv_desc.setText(planet.desc);// 显示行星的描述

        return convertView;
    }



    /**
     * 定义一个视图持有者,以便重用列表项的视图资源
     */
    public final class ViewHolder{
        public ImageView iv_icon; // 声明行星图片的图像视图对象
        public TextView tv_name; // 声明行星名称的文本视图对象
        public TextView tv_desc; // 声明行星描述的文本视图对象
    }

}

注意:

  1. 动态加载布局
    根据布局文件item_list.xml生成转换视图对象 convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list,null);
    动态加载布局需要LayoutInflater来实现,通过LayoutInflater的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate() 方法接收两个参数,第一个参数是要加载的布局文件的id,第二个参数是给这个布局文件设置父布局。

  2. getView方法解析。
    getView是在每个子项被滚动到屏幕内的时候会被调用convertView参数用于将之前的加载好的布局进行缓存,以便之后进行重用。 在getView方法中我们对convertView进行了判断,如果convertView为null,则使用LayoutInflater加载布局,如果不为null则直接对convertView进行重用,这样不用在getView方法中,每次都讲布局重新加载了一遍,这样当快速滚动的列表项的话,getView方法重新调用,每次都加载布局,性能就会下降。

  3. 借助内部类ViewHolder对控件的实例进行缓存
    当convertView为null的时候,创建一个ViewHolder对象,并将控件的实例都放在viewHolder里,然后调用view的setTag方法,将viewHolder对象储存在View中,这样当convertView不为null的时候,则调用view的getTag方法,把ViewHolder重新取出。这样所有控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewById()方法来获取控件实例了。

步骤三,在页面代码中创建该适配器实例,并交给下拉框设置,示例代码如下:

# BaseAdapterMainActivity.java


public class BaseAdapterMainActivity extends AppCompatActivity {

    private List<Planet> planetList;

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

        // 从布局文件中获取名叫sp_planet的下拉框
        Spinner sp_planet = findViewById(R.id.sp_dropdown);
        // 获取默认的行星列表,即水星、金星、地球、火星、木星、土星
        planetList = Planet.getDefaultList();
        // 构建一个行星列表的适配器
        PlanetBaseAdapter planetBaseAdapter = new PlanetBaseAdapter(this, planetList);
        sp_planet.setAdapter(planetBaseAdapter); // 设置下拉框的列表适配器

        sp_planet.setPrompt("选择行星"); // 设置下拉框的标题
        sp_planet.setSelection(0); // 设置下拉框默认显示第一项

        // 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法
        sp_planet.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(BaseAdapterMainActivity.this, "您选择的是" + planetList.get(position).name, Toast.LENGTH_LONG).show();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });
    }
}

Planet实体类代码如下:

public class Planet {
    public int image; // 行星图标
    public String name; // 行星名称
    public String desc; // 行星描述

    public Planet(int image, String name, String desc) {
        this.image = image;
        this.name = name;
        this.desc = desc;
    }

    private static int[] iconArray = {R.drawable.shuixing, R.drawable.jinxing, R.drawable.diqiu,
            R.drawable.huoxing, R.drawable.muxing, R.drawable.tuxing};
    private static String[] nameArray = {"水星", "金星", "地球", "火星", "木星", "土星"};
    private static String[] descArray = {
            "水星是太阳系八大行星最内侧也是最小的一颗行星,也是离太阳最近的行星",
            "金星是太阳系八大行星之一,排行第二,距离太阳0.725天文单位",
            "地球是太阳系八大行星之一,排行第三,也是太阳系中直径、质量和密度最大的类地行星,距离太阳1.5亿公里",
            "火星是太阳系八大行星之一,排行第四,属于类地行星,直径约为地球的53%",
            "木星是太阳系八大行星中体积最大、自转最快的行星,排行第五。它的质量为太阳的千分之一,但为太阳系中其它七大行星质量总和的2.5倍",
            "土星为太阳系八大行星之一,排行第六,体积仅次于木星"
    };

    public static List<Planet> getDefaultList() {
        List<Planet> planetList = new ArrayList<Planet>();
        for (int i = 0; i < iconArray.length; i++) {
            planetList.add(new Planet(iconArray[i], nameArray[i], descArray[i]));
        }
        return planetList;
    }
}

activity对应的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".widgetandview.SpinnerMainActivity">


    <Spinner
        android:id="@+id/sp_dropdown"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:spinnerMode="dialog">

    </Spinner>


</LinearLayout>

显示效果:
在这里插入图片描述

ListView

列表视图允许在页面上分行展示相似的数据列表,例如新闻列表、商品列表、图书列表等,方便用户浏览与操作。

ListView同样通过setAdapter方法设置列表项的数据适配器,但操作列表项的时候,它不使用setOnItemSelectedListener方法,而是调用setOnItemClickListener方法设置列表项的点击监听器OnItemClickListener,有时也调用setOnItemLongClickListener方法设置列表项的长按监听器OnItemLongClickListener。在点击列表项或者长按列表项之时,即可触发监听器对应的事件处理方法。

除此之外,列表视图还新增了几个属性与方法,
在这里插入图片描述

在XML文件中添加ListView很简单,只要以下几行就声明了一个列表视图:

    <ListView
        android:id="@+id/lv_planet"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

创建基本的适配器实现列表适配器,同样使用PlanetBaseAdapter。适配器列表布局文件也是使用R.layout.item_list,再调用setAdapter方法设置适配器对象。

完整的Activity代码如下:

import java.util.List;

public class ListViewMainActivity extends AppCompatActivity {

    private List<Planet> mPlanetList;
    private ListView lv_planet;
    private List<Planet> planetList;
    private CheckBox ck_diviver;
    private CheckBox ck_selector;


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


        mPlanetList = Planet.getDefaultList();
        lv_planet = findViewById(R.id.lv_planet);

        ck_diviver = findViewById(R.id.ck_divider);
        ck_diviver.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // 显示分隔线
                if (ck_diviver.isChecked()) {
                    // 从资源文件获得图形对象
                    Drawable drawable = getResources().getDrawable(R.color.black, getTheme());
                    lv_planet.setDivider(drawable);
                    // 设置列表视图的分隔线高度
                    lv_planet.setDividerHeight(50);
                } else {
                    lv_planet.setDivider(null);
                    lv_planet.setDividerHeight(0);
                }
            }
        });
        ck_selector = findViewById(R.id.ck_selector);
        ck_selector.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // 显示按压背景
                if (ck_selector.isChecked()) {
                    // 设置列表项的按压状态图形
                    lv_planet.setSelector(R.drawable.list_selector);
                } else {
                    Drawable drawable = getResources().getDrawable(R.color.transparent, getTheme());
                    lv_planet.setSelector(drawable);
                }
            }
        });

        PlanetBaseAdapter baseAdapter = new PlanetBaseAdapter(this, mPlanetList);
        lv_planet.setAdapter(baseAdapter);// 设置列表视图的适配器

        // 处理列表项的点击事件,由接口OnItemClickListener触发
        lv_planet.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                String desc = String.format("您点击了第%d个行星,它的名字是%s", position + 1, mPlanetList.get(position).name);
                Toast.makeText(ListViewMainActivity.this, desc, Toast.LENGTH_LONG).show();
            }
        });

        // 处理列表项的长按事件,由接口OnItemLongClickListener触发
        lv_planet.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                String desc = String.format("您长按了第%d个行星,它的名字是%s", position + 1, mPlanetList.get(position).name);
                Toast.makeText(ListViewMainActivity.this, desc, Toast.LENGTH_LONG).show();
                return true;
            }
        });

    }
}

按压背景布局文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/shape_edit_focus" android:state_focused="true" />
    <item android:drawable="@drawable/shape_edit_normal" />

</selector>

activity布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".widgetandview.ListViewMainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp">

        <CheckBox
            android:id="@+id/ck_divider"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="start|center"
            android:text="显示分隔线"
            android:textSize="17sp"/>

        <CheckBox
            android:id="@+id/ck_selector"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="start|center"
            android:text="显示按压背景"
            android:textSize="17sp"/>

    </LinearLayout>

    <ListView
        android:id="@+id/lv_planet"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>


</LinearLayout>

显示效果:

在这里插入图片描述

RecyclerView

ListView 不支持水平滚动,而且在点击事件上的处理并不人性化,setOnItemClickListener()方法注册的是子项的点击事件,如果想点击的是子项里具体的某一个按钮,虽然ListView可以做到(设置焦点)但是实现起来是比较麻烦的。而RecycleView支持水平滚动,在点击事件里也摒弃了子项点击事件的监听器,所有的点击事件都有具体的View去注册。

1. 资源布局文件

引入RecyclerView

## R.layout.recycler_view__main_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".widgetandview.RecycleViewActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </androidx.recyclerview.widget.RecyclerView>

</LinearLayout>

2. 创建列表项布局文件

#item_list.xml 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <LinearLayout
        android:id="@+id/item_root"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <!-- 这是显示行星图片的图像视图 -->
        <ImageView
            android:id="@+id/iv_icon"
            android:layout_width="0dp"
            android:layout_height="80dp"
            android:layout_weight="1"
            android:scaleType="fitCenter"
            tools:src="@drawable/diqiu" />

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginLeft="5dp"
            android:layout_weight="3"
            android:orientation="vertical">

            <!-- 这是显示行星名称的文本视图 -->
            <TextView
                android:id="@+id/tv_name"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:gravity="start|center"
                android:textColor="@color/black"
                android:textSize="20sp"
                tools:text="地球" />

            <!-- 这是显示行星描述的文本视图 -->
            <TextView
                android:id="@+id/tv_desc"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="2"
                android:gravity="start|center"
                android:textColor="@color/black"
                android:textSize="13sp"
                tools:text="地球是太阳系八大行星之一,排行第三,也是太阳系中直径、质量和密度最大的类地行星,距离太阳1.5亿公里" />
        </LinearLayout>
    </LinearLayout>

</LinearLayout>

3. 创建Adapter 继承RecyclerView.Adapter

RecyclerView的用法与ListView相似,同样使用Adapter来生成列表项,只不过RecyclerView需要改进的RecyclerView.Adapter,改进后RecyclerView.Adapter需要实现三个方法。

  • onCreateViewHolder(ViewGroup viewGroup , int i):该方法用于创建列表项组件。使用该方法所创建的组件会被自动缓存。
  • onBindViewHolder(ViewHolder viewHolder , int i):该方法负责为列表项组件绑定数据,每次组件重新显示出来的时候都会重新执行该方法。
  • getItemCount(): 该方法的返回值决定包含多少个列表项。

RecyclerView不需要判断之前使用ListView的convertView是否为null。当convertView为null的时候,就会执行onCreateViewHolder方法实现。为convertView内的所有子组件绑定数据的代码交给onBindViewHolder来实现。 现在继承RecyclerView.Adapter 要传入一个ViewHolder对象,说明该Adapter强制使用ViewHolder来管理convertView内的所有子组件,这样的设计可以保证RecyclerView总是具有较好的性能。

完整代码如下:

# PlanetRecyclerAdapter.java

/**
 * 发现在继承RecyclerView.Adapter 要传入一个ViewHolder对象,说明该Adapter强制使用ViewHolder来管理convertView内的所有子组件,
 * 这样的设计可以保证RecyclerView总是具有较好的性能。
 */
public class PlanetRecyclerAdapter extends RecyclerView.Adapter<ViewHolder> {

    private Context mContext;
    private List<Planet> mPlanetList;

     public PlanetRecyclerAdapter(Context context, List<Planet> planetList){
         mContext = context;
         mPlanetList = planetList;
     }


    /**
     * 创建列表项组件的方法,该方法创建组件会被自动缓存
     * @param parent
     * @param viewType
     * @return
     */
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_list, null);

        ViewHolder viewHolder = new ViewHolder(view);
        // 为列表项组件绑定事件监听器,点击列表即可触发
        viewHolder.rootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int adapterPosition = viewHolder.getAdapterPosition();
                Toast.makeText(mContext, "您点击了:"+mPlanetList.get(adapterPosition).name, Toast.LENGTH_SHORT).show();
            }
        });

        // 因为RecyclerView 所有的点击事件都有具体的View去注册,所有我们为列表项的子组件ImageView绑定事件监听器
        viewHolder.iv_icon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               Toast.makeText(mContext, "您点击了:"+mPlanetList.get(viewHolder.getAdapterPosition()).name+"图片", Toast.LENGTH_SHORT).show();
            }
        });


        return viewHolder;
    }

    /**
     * 为列表项组件绑定数据的方法,每次组件重新显示出来时都会重新执行该方法
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

        Planet planet = mPlanetList.get(position);
        holder.iv_icon.setImageResource(planet.image);
        holder.tv_name.setText(planet.name);
        holder.tv_desc.setText(planet.desc);

    }

    /**
     * 该方法的返回值决定包含多少个列表项
     * @return
     */
    @Override
    public int getItemCount() {
        return mPlanetList.size();
    }
}

class ViewHolder extends RecyclerView.ViewHolder{
    View planetView;
    View rootView;
    ImageView iv_icon;
    TextView tv_name;
    TextView tv_desc;

    public ViewHolder(@NonNull View itemView) {
        super(itemView);

        planetView = itemView;
        rootView = planetView.findViewById(R.id.item_root);
        iv_icon = planetView.findViewById(R.id.iv_icon);
        tv_name = planetView.findViewById(R.id.tv_name);
        tv_desc = planetView.findViewById(R.id.tv_desc);

    }

4. 为RecyclerView设置Adapter

# RecyclerViewMainActivity.java


public class RecyclerViewMainActivity extends AppCompatActivity {

    private List<Planet> mPlanetList;
    private RecyclerView mRecyclerView;

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

        mPlanetList = Planet.getDefaultList();
        mRecyclerView = findViewById(R.id.recycler);

		// 设置RecyclerView保持固定大小,这样可优化RecyclerView的性能
		recyclerView.setHasFixedSize(true);
        // 一定要为RecyclerView设置布局管理器,否则RecyclerView不显示。
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        // 设置RecyclerView的滚动方向
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        // 为RecyclerView设置布局管理器
        mRecyclerView.setLayoutManager(layoutManager);

        PlanetRecyclerAdapter planetRecyclerAdapter = new PlanetRecyclerAdapter(this, mPlanetList);
        mRecyclerView.setAdapter(planetRecyclerAdapter);


    }
}

注意,要为RecyclerView设置布局管理器,否则不显示

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ListViewAndroid中最常用的UI组件之一,用于展示具有固定结构的大量数据。它继承自AdapterView,可以通过Adapter来动态地显示数据。 ListView的特点是:每一行数据都是相同的布局,并且每个Item的高度是相同的。因此,我们可以利用ListView来展示一些比较简单的数据列表。 在使用ListView时,我们需要实现一个Adapter来提供数据,并且可以对ListView的Item进行自定义布局。以下是一个简单的使用ListView的例子: ```java public class MainActivity extends AppCompatActivity { private ListView mListView; private List<String> mDataList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = findViewById(R.id.list_view); // 初始化数据 mDataList = new ArrayList<>(); for (int i = 0; i < 50; i++) { mDataList.add("Item " + i); } // 创建适配器 ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDataList); // 设置适配器 mListView.setAdapter(adapter); } } ``` 在这个例子中,我们使用Android自带的ArrayAdapter作为ListView的适配器,并且使用了系统提供的简单列表项布局simple_list_item_1,将数据逐一展示在ListView中。 除了普通的ListViewAndroid还提供了一个ListActivity类,它是一个已经封装好的Activity,专门用来展示ListView。我们只需要继承ListActivity,实现一个适配器即可。以下是一个使用ListActivity的例子: ```java public class MainActivity extends ListActivity { private List<String> mDataList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化数据 mDataList = new ArrayList<>(); for (int i = 0; i < 50; i++) { mDataList.add("Item " + i); } // 创建适配器 ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDataList); // 设置适配器 setListAdapter(adapter); } } ``` 在这个例子中,我们继承了ListActivity,并且在onCreate()方法中直接使用setListAdapter()方法设置适配器,省去了findViewById()和ListView的初始化过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值