《第一行代码 第二版》 第三章

《第一行代码 第二版》

本博客是对第一行代码的精简总结,仅供个人学习使用。如需系统学习请购买正版或者电子书籍。

链接附上 🔗图灵社区:https://www.ituring.com.cn/book/2744/

第 3 章 软件也要拼脸蛋——UI开发的点点滴滴

Android中所有的控件都具有这两个属性,可选值有3种:match_parentfill_parentwrap_content。其中match_parentfill_parent的意义相同,现在官方更加推荐使用match_parentmatch_parent表示让当前控件的大小和父布局的大小一样,也就是由父布局来决定当前控件的大小。wrap_content表示让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小。

3.1控件和布局的继承结构

{%}

3.2 系统控件不够用?创建自定义控件

​ 可以看到,我们所用的所有控件都是直接或间接继承自View的,所用的所有布局都是直接或间接继承自ViewGroup的。View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基础之上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器。

这个时候我们就可以思考一下,当系统自带的控件并不能满足我们的需求时,可不可以利用上面的继承结构来创建自定义控件呢?答案是肯定的,下面我们就来学习一下创建自定义控件的两种简单方法。先将准备工作做好,创建一个UICustomViews项目。

3.2.1 引入布局

创建标题布局栏,只需要加入两个Button和一个TextView,然后在布局中摆放好就可以了。可是这样做却存在着一个问题,一般我们的程序中可能有很多个活动都需要这样的标题栏,如果在每个活动的布局中都编写一遍同样的标题栏代码,明显就会导致代码的大量重复。这个时候我们就可以使用引入布局的方式来解决这个问题,新建一个布局title.xml,代码如下所示:

<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:id="@+id/title_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/back_bg"
        android:text="Back"
        android:textColor="#fff" />

    <TextView
        android:id="@+id/title_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#fff"
        android:textSize="24sp" />

    <Button
        android:id="@+id/title_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/edit_bg"
        android:text="Edit"
        android:textColor="#fff" />

</LinearLayout>

布局完成,剩下的就是如何在程序中使用这个标题栏了,修改activity_main.xml中的代码,如下所示:

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


没错!我们只需要通过一行include语句将标题栏布局引入进来就可以了。

最后别忘了在MainActivity中将系统自带的标题栏隐藏掉,代码如下所示:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActionBar actionbar = getSupportActionBar();
        if (actionbar != null) {
            actionbar.hide();
        }
    }

}

3.2.2 创建自定义控件

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

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

public class TitleLayout extends LinearLayout {

    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
    }

}

首先我们重写了LinearLayout中带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数。然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater来实现了。通过LayoutInflater的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件inflate()方法接收两个参数,第一个参数是要加载的布局文件的id,这里我们传入R.layout.title,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this

现在自定义控件已经创建好了,然后我们需要在布局文件中添加这个自定义控件,修改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>

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

重新运行程序,你会发现此时效果和使用引入布局方式的效果是一样的。

下面我们尝试为标题栏中的按钮注册点击事件,修改TitleLayout中的代码,如下所示:

public class TitleLayout extends LinearLayout {

    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
        Button titleBack = (Button) findViewById(R.id.title_back);
        Button titleEdit = (Button) findViewById(R.id.title_edit);
        titleBack.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ((Activity) getContext()).finish();
            }
        });
        titleEdit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(), "You clicked Edit button",
                    Toast.LENGTH_SHORT).show();
            }
        });
    }

}


3.3 最常用和最难用的控件——ListView

3.3.1 简单用法

创建布局文件

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

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

</LinearLayout>

接下来修改MainActivity中的代码,如下所示:

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> adapter = new ArrayAdapter<String>(
            MainActivity.this, android.R.layout.simple_list_item_1, data);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }

}


数组中的数据是无法直接传递给ListView的,我们还需要借助适配器来完成。Android中提供了很多适配器的实现类,其中我认为最好用的就是ArrayAdapter。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdapter有多个构造函数的重载,你应该根据实际情况选择最合适的一种。这里由于我们提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String,然后在ArrayAdapter的构造函数中依次传入当前上下文、ListView子项布局的id,以及要适配的数据。注意,我们使用了android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。这样适配器对象就构建好了。

最后,还需要调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。

{%}

3.3.2 定制ListView 的界面

首先需要准备好一组图片,分别对应上面提供的每一种水果,待会我们要让这些水果名称的旁边都有一个图样。

接着定义一个实体类,作为ListView适配器的适配类型。新建类Fruit,代码如下所示:

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;
    }

}


定义布局

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

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />

</LinearLayout>


在这个布局中,我们定义了一个ImageView用于显示水果的图片,又定义了一个TextView用于显示水果的名称,并让TextView在垂直方向上居中显示。

接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。新建类FruitAdapter,代码如下所示:

public class FruitAdapter extends ArrayAdapter<Fruit> {

    private int resourceId;

    public FruitAdapter(Context context, int textViewResourceId,
                        List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = getItem(position); // 获取当前项的Fruit实例
        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent,
            false);
        ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
        fruitImage.setImageResource(fruit.getImageId());
        fruitName.setText(fruit.getName());
        return view;
    }

}


MainActivity中的代码:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits(); // 初始化水果数据
        FruitAdapter adapter = new FruitAdapter(MainActivity.this,
            R.layout.fruit_item, fruitList);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0; i < 2; 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 orange = new Fruit("Orange", R.drawable.orange_pic);
            fruitList.add(orange);
            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 grape = new Fruit("Grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }

}

3.3.3 提升ListView的运行效率

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

仔细观察会发现,getView()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改FruitAdapter中的代码,如下所示:

public class FruitAdapter extends ArrayAdapter<Fruit> {

    ...

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = getItem(position);
        View view;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceId, parent,
                false);
        } else {
            view = convertView;
        }
        ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
        fruitImage.setImageResource(fruit.getImageId());
        fruitName.setText(fruit.getName());
        return view;
    }

}


可以看到,现在我们在getView()方法中进行了判断,如果convertViewnull,则使用LayoutInflater去加载布局,如果不为null则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候也可以表现出更好的性能。

不过,目前我们的这份代码还是可以继续优化的,虽然现在已经不会再重复去加载布局,但是每次在getView()方法中还是会调用ViewfindViewById()方法来获取一次控件的实例。我们可以借助一个ViewHolder来对这部分性能进行优化,修改FruitAdapter中的代码,如下所示:

public class FruitAdapter extends ArrayAdapter<Fruit> {

    ...

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = getItem(position);
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceId, parent,
            false);
            viewHolder = new ViewHolder();
            viewHolder.fruitImage = (ImageView) view.findViewById
            (R.id.fruit_image);
            viewHolder.fruitName = (TextView) view.findViewById (R.id.fruit_name);
            view.setTag(viewHolder); // 将ViewHolder存储在View中
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag(); // 重新获取ViewHolder
        }
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
        return view;
    }

    class ViewHolder {

        ImageView fruitImage;

        TextView fruitName;

    }

}


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

通过这两步优化之后,我们ListView的运行效率就已经非常不错了

3.3.4  ListView的点击事件

话说回来,ListView的滚动毕竟只是满足了我们视觉上的效果,可是如果ListView中的子项不能点击的话,这个控件就没有什么实际的用途了。因此,本小节我们就来学习一下ListView如何才能响应用户的点击事件。

修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.
        fruit_item, fruitList);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                                    int position, long id) {
                Fruit fruit = fruitList.get(position);
                Toast.makeText(MainActivity.this, fruit.getName(), 
                    Toast.LENGTH_SHORT).show();
            }
        });
    }

    ...

}


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

3.4 更强大的滚动控件——RecyclerView

ListView缺点:

如果我们不使用一些技巧来提升它的运行效率,那么ListView的性能就会非常差。

ListView的扩展性也不够好,它只能实现数据纵向滚动的效果,如果我们想实现横向滚动的话,ListView是做不到的。

RecyclerView。它可以说是一个增强版的ListView,不仅可以轻松实现和ListView同样的效果,还优化了ListView中存在的各种不足之处

创建 RectcleView的Adapter

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private List<Fruit> mFruitList;

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

        public ViewHolder(View view) {
            super(view);
            fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            fruitName = (TextView) view.findViewById(R.id.fruit_name);
        }
    }

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

    @Override
    public ViewHolder onCreateViewHolder(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(ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

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

}


修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits(); // 初始化水果数据
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0; i < 2; 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 orange = new Fruit("Orange", R.drawable.orange_pic);
            fruitList.add(orange);
            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 grape = new Fruit("Grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }

}


3.4.1 实现横向滚动和瀑布流布局‘

横向滚动

首先要对fruit_item布局进行修改,因为目前这个布局里面的元素是水平排列的,适用于纵向滚动的场景,而如果我们要实现横向滚动的话,应该把fruit_item里的元素改成垂直排列才比较合理。修改fruit_item.xml中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }
    ...
}


可以看到,我们将LinearLayout改成垂直方向排列,并把宽度设为100dp。这里将宽度指定为固定值是因为每种水果的文字长度不一致,如果用wrap_content的话,RecyclerView的子项就会有长有短,非常不美观;而如果用match_parent的话,就会导致宽度过长,一个子项占满整个屏幕。

然后我们将ImageView和TextView都设置成了在布局中水平居中,并且使用layout_marginTop属性让文字和图片之间保持一些距离。

接下来修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }
    ...
}


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

为什么ListView很难或者根本无法实现的效果在RecyclerView上这么轻松就能实现了呢?这主要得益于RecyclerView出色的设计。ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManagerLayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了

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

  • 交错网格布局管理器 实现瀑布流

首先还是来修改一下fruit_item.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height=" wrap_content"
    android:layout_margin="5dp" >

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

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

</LinearLayout>

这里做了几处小的调整,首先将LinearLayout的宽度由100dp改成了match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。另外我们使用了layout_margin属性来让子项之间互留一点间距,这样就不至于所有子项都紧贴在一些。还有就是将TextView的对齐属性改成了居左对齐,因为待会我们会将文字的长度变长,如果还是居中显示就会感觉怪怪的。

接着修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        StaggeredGridLayoutManager layoutManager = new 
        StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit(
                getRandomLengthName("Apple"), R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit(
                getRandomLengthName("Banana"), R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit(
                getRandomLengthName("Orange"), R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit(
                getRandomLengthName("Watermelon"), R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit(
                getRandomLengthName("Pear"), R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit(
                getRandomLengthName("Grape"), R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit(
                getRandomLengthName("Pineapple"), R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit(
                getRandomLengthName("Strawberry"), R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(
                getRandomLengthName("Cherry"), R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit(
                getRandomLengthName("Mango"), R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }

    private String getRandomLengthName(String name) {
        Random random = new Random();
        int length = random.nextInt(20) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(name);
        }
        return builder.toString();
    }

}


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

每个水果的名字都改成调用getRandomLengthName()这个方法来生成,这样就能保证各水果名字的长短差距都比较大,子项的高度也就各不相同了。

3.4.2 RecyclerView的点击事件

和ListView一样,RecyclerView也必须要能响应点击事件才可以,不然的话就没什么实际用途了。不过不同于ListView的是,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件,相比于ListView来说,实现起来要复杂一些。

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private List<Fruit> mFruitList;

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

        public ViewHolder(View view) {
            super(view);
            fruitView = view;
            fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            fruitName = (TextView) view.findViewById(R.id.fruit_name);
        }
    }

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

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.
            fruit_item, parent, false);
        final ViewHolder holder = new ViewHolder(view);
        holder.fruitView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(), "you clicked view " + fruit.getName(),
                    Toast.LENGTH_SHORT).show();
            }
        });
        holder.fruitImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(), "you clicked image " + fruit.getName(),
                    Toast.LENGTH_SHORT).show();
            }
        });
        return holder;
    }

    ...

}


posted @ 2021-04-25 16:52  AronJudge  阅读(163)  评论(0编辑  收藏  举报