我想要说

今天学习 ListView 组件,和其它的组件不同的是。ListView 组件并不是简简单单的创建和使用就行,ListView 组件中,需要放置其某一个项,也就是 item。在说实例之前,先来看看 ListView 是长什么样子的吧。

由于这个组件非常的重要,会讲的详细一些。

ListView 学好了之后,后面的 GridView 也是大同小异

打码技术堪忧,哈哈哈哈哈哈😅😅😅

这是一张微信中的截图,这个界面可以认为是包含了一个 ListView,这些每一个小项,就叫做 item,比如“雨课堂”就是一个 item。而且,这些 item 中的布局也是需要自己来实现布局的,所以还需要用到一个布局的 xml 文件。布局难度稍微增加了一些,但是不要怕。

通过这张图,对 ListView 有了一个更好的认识。

重点来咯

自己创建 Activity

以下内容涉及到自定义一个布局和与其对应的 Java 代码。

创建布局文件

1、创建一个布局文件

2、设置文件名,名字为 activity_list_view,点击 OK

至此 activity_list_view.xml 文件创建完成。

创建 java 文件

1、创建一个 java 文件,文件名为 ListViewActivity。

2、重写 onCreate 方法,并将 activity_list_view 布局加载进去。

写法如下:

setContentView(R.layout.activity_list_view);

完整代码:

package top.bestguo.androidlayout.listview;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;

import top.bestguo.androidlayout.R;

/**
 * Created by BestGuo on 2021/1/28.
 */

public class ListViewActivity extends Activity {

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

3、在 AndroidManifest.xml 文件中,添加一个 activity。

至此,Activity 的创建已经完成了

新建一个 ListView 组件

创建 ListView 非常简单,直接创建即可。给它赋予一个 id,后面需要使用到它。

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

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

</LinearLayout>

创建完成之后,右边的预览界面会有一个效果图出来。

这是它展示的一个样例,直接运行我没有去尝试。不知道直接运行能不能看到这样的效果。

自定义 item

自定义 item 是非常重要的,也就是微信截图中“雨课堂”的那部分。本实例中,我们要设置的 item 效果如下

所以,我们还需要另外的一个 xml 文件用来设计 item。

新建布局文件

新建一个布局文件,名字为 layout_list_item.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:padding="15dp">

    <!-- 创建了一个图片组件 -->
    <ImageView
        android:id="@+id/iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="centerCrop"
        android:background="@color/colorBlack"/>

    <!--
      创建了另外一个布局,用于放置图片右边的文字描述。
      宽度和父类相同,高度由里面的内容来决定。
      设置了 marginTop="4dp"
    -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginTop="4dp">

        <!-- 
          第一个 TextView 组件,用来展示标题。设置了文字大小和 paddingLeft="15dp"
        -->
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="Hello"
            android:paddingLeft="15dp"
            android:textSize="20sp"
            android:textColor="@color/colorBlack"/>

        <!-- 第二个 TextView 组件,用来展示时间 -->
        <TextView
            android:id="@+id/tv_time"
            android:paddingLeft="15dp"
            android:layout_marginTop="5dp"
            android:textSize="15sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/colorGray"
            android:text="2020-1-28"/>

        <!-- 第三个 TextView 组件,用来展示简要介绍 -->
        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:paddingLeft="15dp"
            android:text="这是内容"
            android:textColor="@color/colorAccent"
            android:textSize="15dp" />

    </LinearLayout>

</LinearLayout>

得到的 item 的效果就是如图所示的效果

注意:之前在设置字体颜色和背景颜色,是用16进制来设置的。但是在这里是用 @color/colorAccent 设置的。
其实这个不难设置,只需在 res 文件夹下的 values 文件夹下的 colors.xml 来进行设置的。通过每一个英文所对应的十六进制颜色代码来设置就可以了。后期使用颜色,直接使用 @color/xxxxxx 这种写法即可。

新建 Adapter

非常重点的部分来咯!什么是 Adapter,Adapter 是用来在展示这些 item 的。以下的微信截图用红色圈出的部分就是由 Adapter 将这些 item 给创建出来的。

什么?适配器,不要和我讲适配器,我可不懂是什么鬼东西。

咦,我说明的文字这么这么小,太不给力了。😱😱😱

Adapter 有很多种,在学习时说是使用 BaseAdapter 最多,那我们就用 BaseAdapter 来举例。

首先新建一个 MyListAdapter 类,需要继承自 BaseAdapter 类,重写其相关的方法才是真正的运用到。

继承之后,需要重写的方法共有 4 个,分别如下:

/**
 * Created by BestGuo on 2021/1/28.
 */
public class MyListAdapter extends BaseAdapter {

    /**
     * ListView 组件中需要放置的多少项,这个是要从服务端来进行获取的值。
     * 后面的示例,由于没有涉及到服务端,展示一下效果,会直接在此设置数值
     * @return 整型
     */
    @Override
    public int getCount() {
        return 0;
    }

    // 通过索引获取内容,但是我们目前没有内容,因此先放一下
    @Override
    public Object getItem(int i) {
        return null;
    }

    // 获取id,具体是什么样的id还需要看什么场景,因此先放一下
    @Override
    public long getItemId(int i) {
        return 0;
    }

    /**
     * 列表中的样式每一项的设置
     *
     * @param i ListView 中的索引
     * @param view 自定义的 item 布局,也就是对应我们之前创建的 layout_list_item.xml 文件
     * @param viewGroup 不知道,到时候用到在介绍
     * @return
     */
    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        return null;
    }
}

中间的两个方法,可以先不管它。暂时用不到

重写方法

我们主要重写的方法有两个,一个 getCount(),另一个是 getView()

getCount 方法

这个方法的用途很简单,就是要让 ListView 知道要放多少个 item。一般是服务器动态获取的,也可以是人为设置的。

假设我们只要 15 项,且永远不变。那我们的 getCount 方法中的返回值,那就是 15。所以是这样写。

public int getCount() {
    return 15;
}

假设我们从服务器获取到的数据,用的是 ArrayList 保存的,如果要知道获取多少条数据。那么就调用它的 size 方法即可。

private ArrayList arr = new ArrayList();

...

public int getCount() {
    return arr.size();
}
getView 方法

这个方法的用途是将我们自定义的 item 布局进行处理。对每一个 item 中的组件的值进行一些设置,比如“雨课堂”的图标、雨课堂文字、发布日期。这些就是 item 中的组件,需要对其进行设置。

下图是雨课堂部分(红色圈出的部分就是微信中 item 用到的组件)

以下红色圈圈的部分就是我们需要设置的部分

其中这里面有一个非常重要的方法,就是 inflate,inflate 方法是用于将我们自定义的 layout_list_item.xml 文件自动填充到 View 中,只有填充进去了,才能正确的调用到这个 layout 文件中设置的 id 值。

LayoutInflater 可以看成是一个布局扩充的一个类,将 layout_list_item 自定义的组件扩到 activity_list_view 这个组件中。它有两个方法,一个 from 方法,用于指定 layout_list_item 是 activity_list_view 组件管理的;一个 inflate 方法,将 layout_list_item 填充到 activity_list_view 中。
但是,执行完 inflate 方法,并没有将这些组件真正的填充进去,只是给这些组件“创建了一个座位”。要将这些组件保存起来,需要用到 view 中的 setTag 方法来将找到的组件真正的保存起来。setTag 传入的参数类型为 Object,因此,需要创建一个类,用于保存这些 item 中的组件。

以下伪代码如下

// 定义一个布局扩充类
private LayoutInflater layoutInflater = LayoutInflater.from(ListViewActivity.this);
...
// 自定义的内部类,用它来将 layout_list_item 中的组件将其保存起来
private static class ViewHolder {
    ImageView imageView;
    TextView tvClock, tvContent, tvTitle;
}

public View getView(int i, View view, ViewGroup viewGroup) {
    ViewHolder viewHolder;
    // 第一次调用 getView 方法时。首先这个自定义的组件肯定是没有进行填充进去。
    if(view == null){
        // 开始扩充“座位”,并将 layout_list_item 保存到变量中。
        view = layoutInflater.inflate(R.layout.layout_list_item, null);

        // 创建一个自定义的对象,将找到的组件放到对象中
        viewHolder = new ViewHolder();

        viewHolder.imageView = view.findViewById(R.id.iv); // 找到 ImageView 组件
        viewHolder.tvClock = view.findViewById(R.id.tv_time); // 找到 TextView 组件
        viewHolder.tvTitle = view.findViewById(R.id.tv_title); // 找到 TextView 组件
        viewHolder.tvContent = view.findViewById(R.id.tv_content); // 找到 TextView 组件

        // 全部找齐,保存这些 item 中的组件。
        view.setTag(viewHolder);
    } else {
        // 第二次进行调用,此时这个自定义的组件已经是填充进去的,将其获取到。方便设置值
        viewHolder = (ViewHolder) view.getTag();
    }
    // 给控件赋值
    viewHolder.tvClock.setText("2021-1-28");
    viewHolder.tvTitle.setText("Hello!");
    viewHolder.tvContent.setText("我是可爱的祢豆子 n(*≧▽≦*)n");
    // 添加网络图片
    if(i%2 != 0)
        Glide.with(context).load("http://www.topacg.com/wp-content/uploads" +
            "/2020/03/frc-11c619718c036bf579c246cdd07e6d77.jpeg").into(viewHolder.imageView);
    else
        Glide.with(context).load("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl." +
                "duitang.com%2Fuploads%2Fitem%2F201912%2F10%2F20191210915" +
                "02_8PjP3.thumb.700_0.jpeg&refer=http%3A%2F%2Fc-ssl.duitang." +
                "com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg").into(viewHolder.imageView);

    Log.d("id=", i+"");

    return view;
}

完整代码如下

以下代码用到了构造方法,用于在创建该 Adapter 对象时,将当前的 ListViewActivity 给传入进去。

package top.bestguo.androidlayout.listview;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;

import top.bestguo.androidlayout.R;

/**
 * Created by BestGuo on 2021/1/28.
 */

public class MyListAdapter extends BaseAdapter {

    // 传入的 Activity,也就是 ListItemActivity
    private Context context;
    // 获取自己设置的布局
    private LayoutInflater layoutInflater;

    public MyListAdapter(Context context) {
        this.context = context;
        this.layoutInflater = LayoutInflater.from(context);
    }

    /**
     * 列表的长度是多少
     * @return 整型
     */
    @Override
    public int getCount() {
        return 15;
    }

    // 通过索引获取内容,但是我们目前没有内容,因此先放一下
    @Override
    public Object getItem(int i) {
        return null;
    }

    // 获取id,具体是什么样的id还需要看什么场景
    @Override
    public long getItemId(int i) {
        return 0;
    }

    // 自定义的内部类,用它来将 layout_list_item 中的组件将其保存起来
    private static class ViewHolder {
        ImageView imageView;
        TextView tvClock, tvContent, tvTitle;
    }

    /**
     * 列表中的样式每一项的设置
     *
     * @param i 每一个 item 中的第几项
     * @param view 对应的 item 布局
     * @param viewGroup 不知道,到时候用到在介绍
     * @return
     */
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        // 第一次调用 getView 方法时。首先这个自定义的组件肯定是没有进行填充进去。
        if(view == null){
            // 开始扩充“座位”,并将 layout_list_item 保存到变量中。
            view = layoutInflater.inflate(R.layout.layout_list_item, null);

            // 创建一个自定义的对象,将找到的组件放到对象中
            viewHolder = new ViewHolder();

            viewHolder.imageView = view.findViewById(R.id.iv); // 找到 ImageView 组件
            viewHolder.tvClock = view.findViewById(R.id.tv_time); // 找到 TextView 组件
            viewHolder.tvTitle = view.findViewById(R.id.tv_title); // 找到 TextView 组件
            viewHolder.tvContent = view.findViewById(R.id.tv_content); // 找到 TextView 组件

            // 全部找齐,保存这些 item 中的组件。
            view.setTag(viewHolder);
        } else {
            // 第二次进行调用,此时这个自定义的组件已经是填充进去的,将其获取到。方便设置值
            viewHolder = (ViewHolder) view.getTag();
        }
        // 给控件赋值
        viewHolder.tvClock.setText("2021-1-28");
        viewHolder.tvTitle.setText("Hello!");
        viewHolder.tvContent.setText("我是可爱的祢豆子 n(*≧▽≦*)n");
        // 添加网络图片
        if(i%2 != 0)
            Glide.with(context).load("http://www.topacg.com/wp-content/uploads" +
                "/2020/03/frc-11c619718c036bf579c246cdd07e6d77.jpeg").into(viewHolder.imageView);
        else
            Glide.with(context).load("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl." +
                    "duitang.com%2Fuploads%2Fitem%2F201912%2F10%2F20191210915" +
                    "02_8PjP3.thumb.700_0.jpeg&refer=http%3A%2F%2Fc-ssl.duitang." +
                    "com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg").into(viewHolder.imageView);

        Log.d("id=", i+"");

        return view;
    }
}

ListViewActivity.java 文件

package top.bestguo.androidlayout.listview;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import top.bestguo.androidlayout.R;

/**
 * Created by BestGuo on 2021/1/28.
 */

public class ListViewActivity extends Activity {

    private ListView listView;

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

        listView = findViewById(R.id.list_view);
        // 设置一个适配器
        listView.setAdapter(new MyListAdapter(this));
        
    }
}

运行效果

运行效果如下

别写了,再写到我脑阔都晕咯🙄🙄🙄

事件

事件这一部分还是较为简单,ListView 共有两种常用的事件,分别是 setOnItemClickListenersetOnItemLongClickListener 两个方法。一个是手指短按的事件,另外一个是长按的事件。

案例如下

package top.bestguo.androidlayout.listview;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import top.bestguo.androidlayout.R;

/**
 * Created by BestGuo on 2021/1/28.
 */

public class ListViewActivity extends Activity {

    private ListView listView;

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

        listView = findViewById(R.id.list_view);
        // 设置一个适配器
        listView.setAdapter(new MyListAdapter(this));
        // 设置点击某一项的监听事件
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Toast.makeText(ListViewActivity.this, "点击的是第" + i + "项!", Toast.LENGTH_SHORT).show();
            }
        });
        // 设置点击某一项长按的监听事件
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
                Toast.makeText(ListViewActivity.this, "长按的是第" + i + "项!", Toast.LENGTH_SHORT).show();
                return true;
            }
        });
    }
}

运行效果

总结

总算是吧这根骨头给啃了,可以好好的休息一下了,现在已经凌晨1点多了。

之后的网格布局,我感觉其实都差不多,后面的就好一些吧。