Android-UI开发(二)

创建自定义控件

常用控件和布局的继承结构

在这里插入图片描述
View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基础上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器

  • 所有控件都是直接或间接继承自View的,
  • 所有布局都是直接或间接继承自ViewGroup的

当系统自带的控件并不能满足我们的需求时,可以利用上面的继承结构来创建自定义控件。

自定义布局

  • android:background (即可用于为布局,也可用于控件) 指定一个背景,可以使用颜色或图片来进行填充
  • android:layout_margin 可以指定控件在上下左右方向上的间距
  • android:layout_marginLeft / android:layout_marginTop 等来单独指定控件在某个方向上的间距

自定义一个标题的通用布局title.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="wrap_content"
    android:background="@drawable/title_bg">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/titleBack"

        android:layout_gravity="center"
        android:layout_margin="5dp"

        android:background="@drawable/back_bg"
        android:text="Back"
        android:textColor="#fff"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/titleText"

        android:layout_gravity="center"
        android:layout_weight="1"

        android:text="Title"
        android:textColor="#fff"
        android:textSize="24dp"
        android:gravity="center"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/titleEdit"

        android:layout_gravity="center"
        android:layout_margin="5dp"

        android:background="@drawable/edit_bg"
        android:text="Edit"
        android:textColor="#fff"
        />
</LinearLayout>

在LinearLayout中分别加入了两个Button和一个TextView,左边的Button可用于返回,右边的Button可用于编辑,中间的TextView则可以显示一段标题文本。title_bg.png
在这里插入图片描述
在这里插入图片描述
title_bg.png、back_bg.png和edit_bg.png分别用于作为标题栏布局的按钮、返回按钮和编辑按钮的背景。效果如下图所示:
Title样板

引入布局

  1. 在activity_main.xml中通过一行include语句引入标题栏布局
</LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent" 
	android:layout_height="match_parent" >

	<include layout="@layout/title" />
	
</LinearLayout>
  1. 在MainActivity中将系统自带的标题栏隐藏掉
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
	actionBar.hide();
}

左图为未去掉自带标题栏时的效果,右图为去掉了自带标题栏的效果。
在这里插入图片描述

自定义控件

引入布局的技巧确实解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每个Activity中为这些控件单独编写一次事件注册的代码。比如标题栏中的返回按钮,其实不管是在哪一个Activity中,这个按钮的功能都是相同的,即销毁当前Activity。而如果在每一个Activity中都需要重新注册一遍返回按钮的点击事件,无疑会增加很多重复代码,这种情况最好是使用自定义控件的方式来解决。

新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件,代码如下所示:

  1. 新建一个类 TitleLayout 继承 LinearLayout 类,并补全构造方法。
public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
    }
}

重写了TitleLayout中带两个参数的构造函数,在布局中引用TitleLayout会调用这个构造函数。在构造函数中需要对标题栏进行动态加载,这就要借助LayoutInflater来实现。调用LayoutInflater 的静态方法from() 创建一个LayoutInflater对象,然后调用对象的**inflater()**方法就可以动态加载一个布局文件。inflater()接受两个参数,第一个是布局文件的id,另外一个是为加载的布局添加一个父布局,因为我们是想在TitleLayout中引用该布局,所以父布局直接传入this即可。

  1. 布局文件中引入该控件
    activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.uicustomviews.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候,我们需要指明控件的完整类名,包名在这里是不可以省略的

  1. 补全该控件中的各类注册点击事件,修改Layout中的代码
    TitleLayout
public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
    }
}

ListView


ListView简单用法

新建ListViewTest项目
新建main_activity.xml布局文件,在布局中加入ListView控件,为ListView控件指定id,将宽度和高度都设置为,这样ListView就占满了整个布局空间。

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

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

然后再修改MainActivity, 既然ListView是用于展示大量数据的,那我们就应该先将数据提供好。这些数据可以从网上下载,也可以从数据库中读取,应该视具体的应用程序场景而定。这里我们就简单使用一个data集合来进行测试,里面包含了很多水果的名称。

集合中的数据是无法直接传递给ListView的,我们还需要借助适配器来完成。Android中提供了很多适配器的实现类,其中我认为最好用的就是ArrayAdapter。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdapter有多个构造函数的重载,你应该根据实际情况选择最合适的一种

我们这里提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String,然后在ArrayAdapter的构造函数中依次传入Activity的实例、ListView子项布局的id,以及数据源。android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。这样适配器对象就构建好了。最后,还需要调用ListView的 setAdapter() 方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
MainActivity

package com.example.listviewtest;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.sql.Array;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private  String[] data = {"Apple", "Banana", "Orange", "Watermelon",
                                "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
                                    "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
                                        "Pineapple", "Strawberry", "Cherry", "Mango"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(MainActivity.this,
                                                        android.R.layout.simple_list_item_1,
                                                            data);
        ListView listView = findViewById(R.id.listView);
        listView.setAdapter(arrayAdapter);
    }
} 

效果如下图所示,创建了一个滑动的列表,显示数据里的内容。
在这里插入图片描述

自定义ListView

  • 首先需要准备好一组图片资源,分别对应一种水果,
  • 接着定义一个实体类,作为ListView适配器的适配类型。新建Fruit类,类中只有两个字段:name表示水果的名字,imageId表示水果对应图片的资源id:
package com.example.listviewtest;

public class Fruit {
    private String name;
    private int imageId;

    public Fruit(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }
    
    public int getImageId() {
        return imageId;
    }
}
  • 然后需要 为ListView的子项指定一个我们自定义的布局 ,在layout目录下新建fruit_item.xml,在这个布局中,我们定义了一个ImageView用于显示水果的图片,又定义了一个TextView用于显示水果的名称,并让ImageView和TextView都在垂直方向上居中显示。代码如下所示:
<?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="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:id="@+id/fruit_image"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/fruit_name"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        />
</LinearLayout>
  • 接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。新建类FruitAdapter,FruitAdapter定义了一个主构造函数,用于将Activity的实例、ListView子项布局的id和数据源传递进来。另外又重写了**getView()**方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。

    在getView()方法中,首先使用LayoutInflater来为这个子项加载我们传入的布局。LayoutInflater的**inflate()**方法接收3个参数,前两个参数我们已经知道是什么意思了,第一个是布局文件的id,另外一个是为加载的布局添加一个父布局,第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。这是ListView中的标准写法。

    接下来调用View的findViewById()方法分别获取到ImageView和TextView的实例,然后通过getItem()方法得到当前项的Fruit实例,并分别调用它们的setImageResource()和setText()方法设置显示的图片和文字,最后将布局返回,这样我们自定义的适配器就完成了。

代码如下所示:

package com.example.listviewtest;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;

public class FruitAdapter extends ArrayAdapter<Fruit> {
    private int layoutId;

    public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
        super(context, resource, objects);
        layoutId = resource;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Fruit fruit = getItem(position);
        View view = LayoutInflater.from(getContext()).inflate(layoutId, parent,false);
        ImageView imageView = view.findViewById(R.id.fruit_image);
        TextView textView = view.findViewById(R.id.fruit_name);
        imageView.setImageResource(fruit.getImageId());
        textView.setText(fruit.getName());

        return view;
    }
}
  • 最后修改MainActivity中的代码,可以看到,这里添加了一个initFruits()方法,用于初始化所有的水果数据。Fruit类的构造函数中将水果的名字和对应的图片id传入,然后把创建好的对象添加到水果列表中。另外,我们使将所有的水果数据添加了5遍,这是因为如果只添加一遍的话,数据量还不足以充满整个屏幕。

    接着在onCreate()方法中创建了FruitAdapter对象,并将它作为适配器传递给ListView,这样定制ListView界面的任务就完成了。

package com.example.listviewtest;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.sql.Array;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<Fruit>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruitList();
        FruitAdapter fruitAdapter = new FruitAdapter(this, R.layout.fruit_item, fruitList);
        ListView listView = findViewById(R.id.listView);
        listView.setAdapter(fruitAdapter);
    }

    public void initFruitList() {
        for (int i = 0; i < 5; i++) {
            Fruit apple = new Fruit("apple", R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("banana", R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit cherry = new Fruit("cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit grape = new Fruit("grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit mango = new Fruit("mango", R.drawable.mango_pic);
            fruitList.add(mango);
            Fruit pineapple = new Fruit("pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("straberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit watermelon = new Fruit("watermelon", R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("pear", R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit orange = new Fruit("orange", R.drawable.orange_pic);
            fruitList.add(orange);
        }
    }
}

现在重新运行程序,效果如下图:
在这里插入图片描述
目前定制的界面还很简单,但是只要修改fruit_item.xml中的内容,就可以定制出各种复杂的界面了。

提升ListView的运行效率

ListView这个控件有很多细节可以优化,比如运行效率。目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。

getView()方法中还有一个 convertView 参数,这个参数用于将之前加载好的布局进行缓存,以便之后进行重用,我们可以借助这个参数来进行性能优化。在getView()方法中进行了判断:如果convertView为null,则使用LayoutInflater去加载布局;如果不为null,则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候可以表现出更好的性能。修改FruitAdapter中的代码,如下所示:

public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        View view;
        if (convertView != null) {
            view = convertView;
        } else {
            view = LayoutInflater.from(getContext()).inflate(layoutId, parent,false);
        }
        ImageView imageView = view.findViewById(R.id.fruit_image);
        TextView textView = view.findViewById(R.id.fruit_name);
        
        Fruit fruit = getItem(position);
        imageView.setImageResource(fruit.getImageId());
        textView.setText(fruit.getName());

        return view;
}

虽然现在已经不会再重复去加载布局,但是每次在getView()方法中仍然会调用View的findViewById()方法来获取一次控件的实例。我们可以借助一个ViewHolder来对这部分性能进行优化,新增一个内部类ViewHolder对控件进行缓存
修改FruitAdapter中的代码,如下所示:

public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        View view;
        ViewHolder viewHolder;
        if (convertView != null) {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        } else {
            view = LayoutInflater.from(getContext()).inflate(layoutId, parent,false);
            viewHolder = new ViewHolder();
            viewHolder.fruitImage = view.findViewById(R.id.fruit_image);
            viewHolder.fruitName = view.findViewById(R.id.fruit_name);
            view.setTag(viewHolder);
        }

        Fruit fruit = getItem(position);
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());

        return view;
}

    class ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
    }

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

ListView的点击事件

使用setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法,这里我们可以通过参数 i 判断用户点击的是哪一个子项,然后获取到相应的水果,并通过Toast将水果的名字显示出来。

更改MianActivity:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruitList();
        FruitAdapter fruitAdapter = new FruitAdapter(this, R.layout.fruit_item, fruitList);
        ListView listView = findViewById(R.id.listView);
        listView.setAdapter(fruitAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Fruit fruit = fruitList.get(i);
                Toast.makeText(MainActivity.this, "You Clicked "+fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });
    }

RecyclerView


RecyclerView基本用法

新建RecyclerViewTest项目

  • 打开app/build.gradle文件,在dependencies闭包中添加如下内容:
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
    上述代码就表示将RecyclerView库引入我们的项目当中,其中除了版本号部分可能会变化,其他部分是固定不变的,然后点击Sync Now同步
    在这里插入图片描述

  • 修改activity_main.xml中的代码
    先为RecyclerView指定一个id,然后将宽度和高度都设置为match_parent,这样RecyclerView就占满了整个布局的空间。需要注意的是,由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写出来

<?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" >

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

</LinearLayout>
  • 从ListViewTest项目中把图片、Fruit类和fruit_item.xml复制过来
  • 为RecyclerView准备一个适配器,新建FruitAdapter类,让这个适配器继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder。其中,ViewHolder是我们在FruitAdapter中定义的一个内部类,代码如下所示:
package com.example.recyclerviewtest;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private List<Fruit> fruitList;

    static class  ViewHolder extends  RecyclerView.ViewHolder {
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            fruitImage = itemView.findViewById(R.id.fruit_image);
            fruitName = itemView.findViewById((R.id.fruit_name));
        }
    }

    public FruitAdapter(List<Fruit> fruitList) {
        this.fruitList = fruitList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.fruit_item, parent,false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Fruit fruit = fruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    @Override
    public int getItemCount() {
        return fruitList.size();
    }
}

  1. 首先定义了一个内部类ViewHolder,它要继承自RecyclerView.ViewHolder。然后ViewHolder的主构造函数中要传入一个View参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewById()方
    法来获取布局中ImageView和TextView的实例了.
  2. FruitAdapter中也有一个主构造函数,它用于把要展示的数据源传进来,我们后续的操作都将在这个数据源的基础上进行。
  3. FruitAdapter是继承自RecyclerView.Adapter的,那么就须重写onCreateViewHolder()、onBindViewHolder()和getItemCount()这3个方法。
  • onCreateViewHolder() 在该方法中将fruit_item布局加载进来创建了一个View实例,然后把该实例传入ViewHolder的构造函数中创建了一个ViewHolder实例,最后将ViewHolder的实例返回
  • onBindViewHolder() 方法用于对RecyclerView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可
  • getItemCount() 方法用于告诉RecyclerView一共有多少子项,直接返回数据源的长度。
  • 修改Activity.xml的代码如下:

使用了一个同样的initFruits()方法,用于初始化所有的水果数据。接着在onCreate()方法中先创建了一个LinearLayoutManager对象,并利用setLayoutManager() 方法将它设置到RecyclerView当中。LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。接下来我们创建了FruitAdapter的实例,并将水果数据传入FruitAdapter的构造函数中,最后调用RecyclerView的setAdapter() 方法来完成适配器设置,这样RecyclerView和数据之间的关联就建立完成了。

package com.example.recyclerviewtest;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private List<Fruit> fruitList = new ArrayList<Fruit>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruitList();
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(fruitAdapter);
        recyclerView.setLayoutManager(linearLayoutManager);

    }

    public void initFruitList() {
        for (int i = 0; i < 5; i++) {
            Fruit apple = new Fruit("apple", R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("banana", R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit cherry = new Fruit("cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit grape = new Fruit("grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit mango = new Fruit("mango", R.drawable.mango_pic);
            fruitList.add(mango);
            Fruit pineapple = new Fruit("pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("straberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit watermelon = new Fruit("watermelon", R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("pear", R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit orange = new Fruit("orange", R.drawable.orange_pic);
            fruitList.add(orange);
        }
    }
}

最终效果如下,和ListView几乎一模一样的效果:
在这里插入图片描述

实现横向滚动

ListView的扩展性并不好,它只能实现纵向滚动的效果,如果想进行横向滚动的话,ListView就做不到了。RecyclerView能做到

  • 对fruit_item布局进行修改
  • 要实现横向滚动的话,应该把fruit_item里的元素改成垂直排列,并把宽度设为80 dp。这里将宽度指定为固定值是因为每种水果的文字长度不一致,如果用wrap_content的话,RecyclerView的子项就会有长有短,非常不美观,而如果用match_parent的话,就会导致宽度过长,一个子项占满整个屏幕。
  • 将ImageView和TextView都设置成了在布局中水平居中,并且使用layout_marginTop属性让文字和图片之间保持一定距离。

fruit_item.xml

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

    <ImageView
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:id="@+id/fruit_image"
        android:layout_gravity="center_horizontal"
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/fruit_name"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"
        />
</LinearLayout>
  • 修改MainActivity中的代码

MainActivity中只加入了一行代码,调用LinearLayoutManager的setOrientation()方法设置布局的排列方向。默认是纵向排列的,我们传入RecyclerView.HORIZONTAL表示让布局横行排列,这样RecyclerView就可以横向滚动了。

在这里插入图片描述
运行代码,效果如下:
在这里插入图片描述

实现瀑布流布局

除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManager和StaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManager可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。

首先更改fruit_item.xml,

  • 将LinearLayout的宽度改成了match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。
  • 使用了layout_margin属性来让子项之间互留一点间距,这样就不至于所有子项都紧贴在一些
<?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="wrap_content"
    android:orientation="vertical"
    android:layout_margin="5dp">

    <ImageView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:id="@+id/fruit_image"
        android:layout_gravity="center_horizontal"
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/fruit_name"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"
        />
</LinearLayout>

修改MainActivity中的代码,

在onCreate()方法中,我们创建了一个StaggeredGridLayoutManager的实例。StaggeredGridLayoutManager的构造函数接收两个参数:第一个参数用于指定布局的列数,传入3表示会把布局分为3列;第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列。最后把创建好的实例
设置到RecyclerView当中就可以了.

运行代码效果如图:
在这里插入图片描述

RecyclerView的点击事件

不同于ListView的是,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件。

修改ViewHolder,在ViewHolder中添加了fruitView变量来保存外层子项布局itemView,然后在onCreateViewHolder()方法中注册点击事件。这里分别为最外层布局以及IamgeView注册了点击事件。在两个点击事件中,分别通过position来获取相应的Fruit实例,再使用Toast分别弹出两种不同的内容以示区别。
在这里插入图片描述

在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值