Android——UI开发的点点滴滴2

1.2详解四种布局

1.2.1线性布局LinearLayout

LinearLayout又称线性布局,这个布局会将它所包含的控件在线性方向上依次排列。android:orientation 属性指定排列方向,vertical是垂直方向horizontal是水平方向
注意:如果LinearLayout的排列方向是horizontal。内部控件就绝对不能将宽度指定为math_parent,这样的话单独一个控件就会将水平方向占满,同样,若是vertical,就不能将高度指定为math_parent.

补充: android:gravity用于指定文字在控件中的对齐方式
       android:layout_gravity用于指定控件在布局中的对齐方式。

1.2.2相对布局RelativeLayout

可以通过相对定位的方式让控件出现在布局的任何位置。
1.相对父局:
android:layout_alignParentTop
android:layout_alignParentRight
android:layout_alignParentLeft
android:layout_alignParentBottom
android:layout_centerInParent

2:相对控件:
android:layout_above:让一个控件位于另一个控件上方,需要为这个属性指定相对控件id的引用,其他类似。
android:layout_below
android:layout_toLeftOf
android:layout_toRightOf

3.另外一组相对于控件进行定位的属性:
android:layout_alignLeft:表示让一个控件的左边缘和另一个控件的左边缘对齐。
android:layout_alignRight
android:layout_alignTop
android:layout_alignBottom

1.2.3帧布局FrameLayout

这种布局没有方便的定位方式,所有的控件都默认放在布局的左上角

1.2.4百分比布局

这种布局中我们可以不再使用wrap_content,math_parent,等方式来指定控件的大小,而是允许直接指定控件在布局中占的百分比。由于LinearLayout本身已经支持按比例指定控件的大小,因此百分比布局只为FrameLayout和RelativeLayout进行了功能拓展,提供了PercentFrameLayout和PercentRelativeLayout这两个全新布局。

在dependencies闭包中添加如下内容

implementation 'androidx.percentlayout:percentlayout:1.0.0'

然后修改activity_main.xml中的代码

<androidx.percentlayout.widget.PercentFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button1"
        android:text="Button 1"
        android:layout_gravity="left|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>
    <Button
        android:id="@+id/button2"
        android:text="Button 2"
        android:layout_gravity="right|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>
    <Button
        android:id="@+id/button3"
        android:text="Button 3"
        android:layout_gravity="left|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>
    <Button
        android:id="@+id/button4"
        android:text="Button 4"
        android:layout_gravity="right|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

androidx.percentlayout.widget.PercentFrameLayout>

最外层使用了PercentFrameLayout,由于百分比布局并不是内置在系统SDK当中的,所以需要把完整的包名写出来。然后还必须定义一个app的命名空间,这样才能使用百分比布局的自定义属性。
不过PercentFrameLayout还是会继承FrameLayout的特性,即所有的控件默认都是摆放在布局的左上角。
Android——UI开发的点点滴滴2_第1张图片

1.3 创建自定义布局

常用控件和布局的继承结构
Android——UI开发的点点滴滴2_第2张图片
所有的控件都是直接或间接继承自View的所有的布局都是直接或间接继承自ViewGroupView是Android中最基本的一种UI组件,可以在屏幕上绘制一块矩形区域,并能相应这块区域的各种事件,因此我们使用的各种控件其实就是在View的基础上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器
如果系统的控件并不能满足我们时,我们可以利用上面的继承结构来创建自定义控件。
创建一个新项目,新建一个布局title.xml

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

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/title_back"
        android:text="Back"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:textAllCaps="false"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/title_text"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#fafafa"/>
    <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:textAllCaps="false"
        android:text="Edit"/>
LinearLayout>

修改activity_main.xml中的代码

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <include layout="@layout/title"/>

androidx.constraintlayout.widget.ConstraintLayout>

只需要通过一行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();
        }
    }
}

调用了getSupportActionBar方法获得ActionBar的实例,然后再调用ActionBar的hide()方法将标题栏隐藏起来。
Android——UI开发的点点滴滴2_第3张图片
引入布局的技巧确实解决了重复编写布局代码的问题,如果布局中要求一些控件能够响应事件,我们还需在每个活动中为这些控件单独编写一次事件注册的代码,无疑会增加很多代码,这些情况最好使用自定义控件来解决。

1.4 创建自定义控件

新建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中的代码如下:

<androidx.constraintlayout.widget.ConstraintLayout 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"/>

androidx.constraintlayout.widget.ConstraintLayout>

需要注意,在添加自定义控件时我们需要指明控件的完整类名,包名在这里不可以省略
然后为标题栏中的按钮注册点击事件,修改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 view) {
                ((Activity) getContext()).finish();
            }
        });
        titleEdit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getContext(),"you click Edit button",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

先通过findViewById()方法得到按钮的实例,然后分别调用setOnClickListener()方法给两个按钮注册了点击事件,当点击返回时销毁当前活动,当点击编辑时弹出一段文本。运行效果如下:
Android——UI开发的点点滴滴2_第4张图片

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

1.5.1 ListView的简单用法

可以称为是Android中最常用的控件之一,允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时,屏幕上原有的数据会滚动出屏幕。
新建项目,修改activity_main.xml中的代码

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/list_view"/>

androidx.constraintlayout.widget.ConstraintLayout>

接下来修改MainActivity中的代码

public class MainActivity extends AppCompatActivity {
    private String[] fruit = {"apple","banana","orange","apple","banana","orange","apple","banana",
                                "orange","apple","banana","orange","apple","banana","orange"};
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView listView = (ListView) findViewById(R.id.list_view);
        ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, fruit);
        listView.setAdapter(adapter);
    }
}

简单定义一个fruit数组来测试。不过数组中的数据是无法直接传递给ListView的,我们需要借助适配器来完成。Android中提供了很多适配器的实现类,其中ArrayAdapter,可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。在ArrayAdapter的构造函数中依次传入当前上下文ListView子项布局的id,以及要适配的数据。这里使用了android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android的内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本,这样适配器对象就构建好了。最后还需要调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
Android——UI开发的点点滴滴2_第5张图片

1.5.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 int getImageId() {
        return imageId;
    }

    public String getName() {
        return name;
    }
}

然后需要为ListView的子项指定一个我们自定义的布局,在layout目录下新建fruit.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/fruit_image"/>
    <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>

在这个布局里,定义ImageView用于显示水果图片,定义一个TextView用于显示水果名称,并让TextView在垂直方向上居中显示。
接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类,新建类FruitAdapter

public class FruitAdapter extends ArrayAdapter<Fruit> {
    private int resourceId;
    public FruitAdapter(Context context, int textViewResourceId, List<Fruit> object){
        super(context,textViewResourceId,object);
        resourceId = textViewResourceId;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent){
        Fruit fruit = getItem(position);
        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);
        fruitName.setText(fruit.getName());
        fruitImage.setImageResource(fruit.getImageId());
        return view;
    }
}

FruitAdapter重写了父类的一组构造函数,用于将上下文ListView的子项布局的id数据都传递进来,另外又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView()方法中,首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局
这里LayoutInflater的inflate()方法接收三个参数,第三个false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父局,因为一旦View有了父局之后,就不能再添加到ListView中了
接下来调用View的findViewById()方法分别获取到ImageView和TextView的实例,并分别调用他们的setImageResource()和setText()方法来设置显示的图片和文字,最后将布局返回,这样自定义的适配器就完成了。
下面修改MainActivity中的代码

public class MainActivity extends AppCompatActivity {
    private List<Fruit> fruitList = new ArrayList<>();
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        ListView listView = (ListView) findViewById(R.id.list_view);
        FruitAdapter adapter = new FruitAdapter(MainActivity.this,R.layout.fruit,fruitList);
        listView.setAdapter(adapter);

    }
    private void initFruits(){
        for(int i=0;i<10;i++){
            Fruit apple = new Fruit("apple",R.drawable.ic_launcher_background);
            fruitList.add(apple);
        }
    }
}

Android——UI开发的点点滴滴2_第6张图片
可以找一组图片去传,这里比较简陋

1.5.3 提升ListView的运行效率

目前的运行效率还是很低的,在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动时,就会成为性能的瓶颈。
getView()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用,修改FruitAdapter中的代码

@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()方法中进行了判断,如果convertView为null,则使用LayoutInflater去加载布局,如果不为null,则直接对convertView进行重用。这样就大大提升了ListView的效率。
虽然现在已经不会重复加载布局,但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例,我们可以借助一个ViewHolder来对这部分性能进行优化,修改FruitAdapter中的代码

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,用于对控件的实例进行缓存。当convertView控件为null的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法将ViewHolder对象存储在View中,当convertView不为null时,则调用View的getTag()方法,把ViewHolder重新取出,这样所有的控件的实例都缓存在了ViewHolder里,每次没有必要通过findViewById()方法来获取控件实例了。

1.5.4 ListView的点击事件

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

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

1.6 更强大的滚动控件RecyclerView

RecyclerView是一个增强版的ListView,和百分比布局类似,也属于新增的控件,首先需要在项目的build.gradle中添加相应的依赖库。

implementation 'com.android.support:recyclerview-v7:24.2.1'

然后修改activity_main.xml中的代码

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recycle_view"/>

androidx.constraintlayout.widget.ConstraintLayout>

由于RecyclerView并不是内置在SDK当中,所以需要把完整的路径写出来。
将Fruit类和fruit.xml复制过来
接下来为RecyclerView准备一个适配器,新建FruitAdapter类,并让这个适配器继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder.其中ViewHolder是我们在FruitAdapter中定义的一个内部类。

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,parent,false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }
    @Override
    public void onBindViewHolder(ViewHolder holder,int position){
        Fruit fruit = mFruitList.get(position);
        holder.fruitName.setText(fruit.getName());
        holder.fruitImage.setImageResource(fruit.getImageId());
    }
    @Override
    public int getItemCount(){
        return mFruitList.size();
    }
}

首先定义了一个内部类ViewHolder,继承自RecyclerView.ViewHolder,然后ViewHolder的构造函数中传入一个View参数,这个参数通常就是RecycleView子项的最外层布局,然后可以通过findViewById()方法获取到布局中的ImageView和TextView的实例了。
接着往下FruitAdapter中也有一个构造函数,这个方法用于把要展示的数据源传递过来赋值给一个全局变量mFruitList,之后的操作都会在这个数据源的基础上进行。
由于FruitAdapter继承RecyclerView.Adapter,所以必须重写onCreateViewHolder(),onBindViewHolder,getItemCount()这三个方法。onCreateViewHolder()用于创建ViewHolder实例,在这个方法中将fruit布局加载出来,然后创建一个ViewHolder的实例,并把加载出来的布局传入到构造函数当中,最后将ViewHolder的实例返回。onBindViewHolder()方法是用于对RecycleView子项的数据进行赋值,会在每个子项滚动进屏幕执行,通过position获得当前Fruit的实例,然后在将数据设置到ViewHolder的ImageView和TextView。getItemCount()用于告诉RecycleView一共有多少个子项,直接返回数据源的长度。
适配器准备好之后,修改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.recycle_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<10;i++){
            Fruit apple = new Fruit("apple",R.drawable.ic_launcher_background);
            fruitList.add(apple);
            Fruit banana = new Fruit("banana",R.drawable.ic_launcher_foreground);
            fruitList.add(banana);
        }
    }
}

先获取RecyclerView的实例,然后创建了一个LinearLayoutManager对象,并把它设置在RecyclerView里。LayoutManager用于指定布局方式,这里使用LinearLayoutManager是线性布局的意思,可以实现和ListView一样的效果。然后创建FruitAdapter实例,将水果数据传入到构造函数当中,最后调用RecycleView的setAdapter()方法来完成适配器设置。这样RecyclerView和数据之间的关联就建立完成了。
运行效果和之前一样。

1.6.2 实现横向滚动和瀑布流布局

要实现横向滚动,应该把fruit里的元素改成垂直排列

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="100dp"
    android:layout_height="wrap_content">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/fruit_image"/>
    <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>

因为每种水果的文字长度不一样,把宽度设成100dp,为了美观,然后将ImageView和TextView都设置成了水平居中,并使用layout_marginTop属性让文字和图片之间保持距离。
接下来修改MainActivity中的代码

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

只需加入一行代码,调用LinearLayoutManager的setOrientation()方法来设置布局的排列方向,默认是纵向排列,我们传入LinearLayoutManager.HORIZONTAL,表示让布局横行排列。
Android——UI开发的点点滴滴2_第7张图片
ListView的布局排列是由自身去管理,RecycleView将这个工作交给了LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能制定出不同排列方式的布局。
还有GridLayoutManager可用于实现网格布局,StaggeredGridLayoutManager可用于实现瀑布流布局。
实现瀑布流布局,修改fruit中的代码,

<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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/fruit_image"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/fruit_name"
        android:layout_gravity="left"
        android:layout_marginTop="10dp"/>

LinearLayout>

接着修改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.recycle_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<10;i++){
            Fruit apple = new Fruit(getRandomLengthName("apple"),R.drawable.ic_launcher_background);
            fruitList.add(apple);
            Fruit banana = new Fruit(getRandomLengthName("banana"),R.drawable.ic_launcher_foreground);
            fruitList.add(banana);
        }
    }
    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();
    }
}

首先创建StaggeredGridLayoutManager的实例,StaggeredGridLayoutManager的构造函数接收两个参数,第一个用于指定布局的列数第二个用于指定布局的排列方向,最后把创建好的实例设置到RecyclerView中。 getRandomLengthName(),这个方法使用Random对象来创造一个1到20的随机数,然后将参数中传入的字符串重复几遍。效果如下
Android——UI开发的点点滴滴2_第8张图片

1.6.3 RecyclerView的点击事件

RecyclerView没有类似与setOnItemClickListener()这样的注册监听器方法,需要我们自己给子项具体的View去注册点击事件
修改FruitAdapter中的代码

......
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,parent,false);
    final ViewHolder holder = new ViewHolder(view);
    holder.fruitView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            int position = holder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(view.getContext(),"you clicked view"+fruit.getName(),Toast.LENGTH_SHORT).show();
        }
    });
    holder.fruitImage.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            int position = holder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(view.getContext(),"you clicked image"+fruit.getName(),Toast.LENGTH_SHORT).show();
        }
    });
    return holder;
}
......

在ViewHolder中添加fruitView变量来保存子项最外层布局的实例然后在onCreateViewHolder()方法中注册点击事件,这里分别为最外层布局和ImageView都注册了点击事件在点击事件中先获取了用户点击的position,然后通过position拿到相应的Fruit实例,再使用Toast弹出两种不同的内容以示区别。
点击不同的地方弹出的内容不同,效果如下
Android——UI开发的点点滴滴2_第9张图片
Android——UI开发的点点滴滴2_第10张图片

你可能感兴趣的:(android)