第一行代码第三章——UI开发的点点滴滴

3.1如何编写程序界面

Android开发中编写界面的方法主要有如下两种:
可视化编辑器
优点:允许拖放控件来编写布局,同时可以在视图上修改控件的属性
缺点:不利于了解界面背后的原理,屏幕适配性不好
XML代码
这是用的最多的方式。不仅能够实现高度复杂的控件,还能分析和修改当前现有界面。

3.2 常用控件的使用方法

Android中提供了大量的UI控件,下面我们学习几种比较常用的控件。

3.2.1 TextView

主要作用:
在界面上显示一段文本信息。

<TextView
    android:id="@+id/text_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="24sp"
    android:textColor="#00ff00"
    android:text="This is TextView" />

属性说明:
● android:id 给当前控件定义一个唯一标识符。
● android:layout_width和android:layout_height指定控件的宽度和高度,Android中所有的控件都有这两个属性,值有match_parent、fill_parent和wrap_content。
其中:match_parent和fill_parent意义相同,表示让当前控件的大小和父布局的大小一样;wrap_content表示让当前控件的大小能够刚好包含住里面的内容。
● android:gravity 指定文字的对齐方式,可选值有top、bottom、left、right、center等。可以用“|”来同时指定多个值。
● android:textSize 指定文字的大小。Android中字体大小使用sp作为单位。
● android:textColor 指定文字颜色。
● android:text 指定TextView中显示的文本内容。

3.2.2 Button

主要作用:
显示一个按钮,与用户进行交互。
Button可配置的属性和TextView差不多。
使用方法:

<Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Button"
    android:textAllCaps="false"/>

属性说明:
● android:textAllCaps :
如果不加这个属性,那么上面设置的文字Button最终显示的却是“BUTTON”,这是因为系统会对Button中的所有英文字母自动进行大写转换,如果不想要这个效果,可以将android:textAllCaps设置为false。

Android中点击事件的四种写法
1.使用匿名内部类实现点击事件
2.让MainActivity实现View.OnClickListener接口
3. 使用内部类实现点击事件
4.通过布局文件中控件的属性

1.内部类实现

 private Button button; //匿名对象实现
  @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Toast.makeText(getApplicationContext(),"匿名类实现",Toast.LENGTH_LONG).show();
            }
        });
}

2.让MainActivity实现View.OnClickListener接口

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    
    private Button button2;//this 实现
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button2 = findViewById(R.id.button3);
        button2.setOnClickListener(this);
        }
    @Override
    public void onClick(View v) {
    
    
        Toast.makeText(getApplicationContext(),"this实现",Toast.LENGTH_LONG).show();
    }   
}

3. 使用内部类实现点击事件

  private Button button1;//内部类实现
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button1 = findViewById(R.id.button2);
        button1.setOnClickListener(new Click());
  }
class Click implements View.OnClickListener{
    
    
   @Override
   public void onClick(View v) {
    
    
   Toast.makeText(getApplicationContext(),"内部类实现",Toast.LENGTH_LONG).show();
        }
    }

4.通过布局文件中控件的属性

xml文件

    <Button
        android:id="@+id/button4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:onClick="Call"//代码中重写Call(View view) 公有接口
        android:text="OnClick3"> 
    </Button>

直接进行重写**public void Call(View view)**接口,不用去findViewById和注册监听器
注意:view对象就是Button 本身

public void Call(View view) {
    
    
        Toast.makeText(getApplicationContext(),"XML", Toast.LENGTH_LONG).show();
    }

3.2.3 EditText

主要作用:
用于和用户进行交互,用户可以在控件里面输入和编辑内容,并可以在程序中对这些内容进行处理。
使用方法:

<EditText
    android:id="@+id/edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Type something here"
    android:maxLines="2"/>

属性说明:
● android:hint 指定一段提示性文本,用户输入后,文本自动消失。
● android:maxLines 指定EditText的最大行数,当输入的内容超过行数后,文本就会向上滚动。
获取输入框文本:

EditText editText = (EditText) findViewById(R.id.edit_text);
String inputText = editText.getText().toString();

3.2.4 ImageView

主要作用:
用于在界面上展示图片。图片根据分辨率的不同放在不同的drawable或mipmap目录下。
使用方法:

<ImageView
    android:id="@+id/image_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@mipmap/ic_launcher"/>

属性说明:
● android:src 给ImageView指定一张图片。
代码中指定图片:

ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageResource(R.mipmap.ic_launcher_round);

3.2.5 ProgressBar

主要作用:
在界面上显示一个进度条
使用方法:

<ProgressBar
    android:id="@+id/progress_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
	android:visibility="visible"
    style="?android:attr/progressBarStyleHorizontal"  //设置成水平方向
    android:max="100"/>   //设置成100

属性说明:
ProgressBar默认为原型,需要设置style才为水平

● android:visibility 控制控件是否可见。 visible表示控件可见,默认值。invisible表示控件不可见,但仍占据原来的位置,相当于控件透明。gone表示控件不可见,且不占据原来的位置。
● style 指定控件的形状。
● android:max 给进度条设置一个最大值。
代码中控制控件是否可见
控件可见三种方式:
View.VISIBLE;
View.INVISIBLE;
View.GONE

注意: View.VISIBLE; View.INVISIBLE; 还是占有原来控件的位置,只有设置成了View.GONE,才在布局文件中不占有控件的位置,并且不可见

ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
if (progressBar.getVisibility() == View.GONE) {
    
    
    progressBar.setVisibility(View.VISIBLE);
} else {
    
    
    progressBar.setVisibility(View.GONE);
}

3.2.6 AlertDialog

主要作用:
用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。对话空可以屏蔽用户与其他控件的交互能力。
简单用法:
https://www.cnblogs.com/shen-hua/p/5709663.html

例如:点击button弹出对话框,进行选择

AlertDialog.Builder diaglog = new AlertDialog.Builder(MainActivity.this);
        diaglog.setTitle("请做出选择")
                .setMessage("1+1= ?")
                .setPositiveButton("2", new DialogInterface.OnClickListener() {
    
    
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
    
    
                        Toast.makeText(getApplicationContext(),"恭喜你答对了",Toast.LENGTH_LONG).show();
                    }
                })
                .setNegativeButton("不知道", new DialogInterface.OnClickListener() {
    
    
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
    
    
                        Toast.makeText(getApplicationContext(),"???",Toast.LENGTH_LONG).show();
                    }
                })
                .setNeutralButton("3", new DialogInterface.OnClickListener() {
    
    
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
    
    
                        Toast.makeText(getApplicationContext(),"错误",Toast.LENGTH_LONG).show();
                    }
                });
        diaglog.show();

使用说明:
● AlertDialog.Builder 创建一个AlertDialog实例
● setTitle 设置标题
● setMessage 设置内容
● setCancelable 是否用Back键关闭对话框
● setPositiveButton() 设置确认按钮的点击事件
● setNegativeButton() 设置取消按钮的点击事件
● show() 将dialog显示出来

3.2.7 ProgressDialog

主要作用:
跟AlertDialog类似,都能够屏蔽掉其他控件的交互能力。只是ProgressDialog会在对话框中显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心地等待。
使用方法:

   ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
    progressDialog.setTitle("This is ProgressDialog");
    progressDialog.setMessage("Loding...");
    progressDialog.setCancelable(true);
    progressDialog.show();

使用说明:
● new ProgressDialog 创建一个ProgressDialog对象
● setTitle 设置标题
● setMessage 设置内容
● setCancelable 是否用Back键关闭对话框
● show() 将dialog显示出来
备注:
如果在setCancelable()中传入了false,表示ProgressDialog不能通过Back键取消掉,这时需要在代码中做好控制,当数据加载完成后必须调用ProgressDialog的dismiss()方法来关闭对话框,否则ProgressDialog将会一直存在。

3.3详解3中简单的布局

布局是一种可以放置多个控件或者布局的容器,可以按照一定的规律调整内部控件或布局的位置,从而来写出精美的界面。

3.3.1 线性布局

LinearLayout称作线性布局,该布局会将它所包含的控件在线性方向(水平或垂直方向)上依次排列。
基本使用:

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

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:text="Button 1"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="Button 2"/>

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:text="Button 3"/>

	<EditText
        android:id="@+id/input_message"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:hint="Type something"/>

    <Button
        android:id="@+id/send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="send"/>

</LinearLayout>

属性说明:
● android:orientation 指定排列方向,可以是vertical或horizontal。默认的排列方向是horizontal
● android:layout_weight 使用比例的方式来指定控件的大小

备注:
● LinearLayout的排列方向是horizontal,内部的控件宽度就不能指定为match_parent,因为这样的话,单独的一个控件就会将整个水平方向占满,其他的控件就没有可放的位置。
● LinearLayout的排列方向是vertical,内部控件的高度就不能指定为match_parent。

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

● LinearLayout的排列方式是horizontal时,只有在垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因此无法确定该方向上的对齐方式。同理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方式才会生效。

权重说明:
1.如果按照水平方向布局,控件比例为1:1 则控件的宽为0dp

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1">
    </Button>
    <Button
        android:id="@+id/button1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1">
    </Button>
</LinearLayout>

2.如果按照垂直平方向布局,控件比例为1:1 则控件的高为0dp
3.水平布局,左边按照控件实际大小进行排列,右边控件按照权重1方式排列

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </Button>
    <Button
        android:id="@+id/button1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1">
    </Button>
</LinearLayout>

3.3.2 相对布局

RelativeLayout称作相对布局。通过相对定位的方式让控件出现在布局的任何位置。
属性说明:
主要用在控件相对于父布局进行定位的属性
● android:layout_alignParentLeft 与父布局的左边对齐
● android:layout_alignParentRight 与父布局的右边对齐
● android:layout_alignParentTop 与父布局的上边对齐
● android:layout_alignParentBottom 与父布局的下边对齐
● android:layout_centerInParent 位于父布局中心

控件也可以相对于其他控件进行定位
● android:layout_above 位于相对控件的上方
● android:layout_below 位于相对控件的下方
● android:layout_toLeftOf 位于相对控件的左侧
● android:layout_toRightOf 位于相对控件的右侧

注意: 当一个控件去引用另一个控件id的时候,该控件一定要定义在引用控件的后面,不然会出现找不到id的情况。

基本使用:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".MainActivity">
    <Button
        android:id="@+id/top_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true">
    </Button>
    <Button
        android:id="@+id/top_right"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true">
    </Button>
    <Button
        android:id="@+id/center"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_centerInParent="true">
    </Button>
    <Button
        android:id="@+id/bottom_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true">
    </Button>
    <Button
        android:id="@+id/bottom_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true">
    </Button>
    <Button
        android:id="@+id/center_top_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/center"
        android:layout_toLeftOf="@+id/center">
    </Button>
    <Button
        android:id="@+id/center_top_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/center"
        android:layout_toRightOf="@id/center">
    </Button>
    <Button
        android:id="@+id/center_bottom_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/center"
        android:layout_toLeftOf="@+id/center">
    </Button>
    <Button
        android:id="@+id/center_bottom_right"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_below="@+id/center"
        android:layout_toRightOf="@+id/center">
    </Button>
</RelativeLayout>

在这里插入图片描述

3.3.2 帧布局

FrameLayout称作帧布局。它里面所有的控件都会默认摆放在布局的左上角。
基本使用:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".MainActivity">
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </Button>
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="100dp"></Button>
</FrameLayout>

属性说明:
● android:layout_gravity 指定控件在布局中的对齐方式。

3.4 创建自定义控件

下面是控件和布局的继承结构:
在这里插入图片描述
从上面的图中可得出如下结论:
● 所有控件都是直接或间接继承自View
● 所有的布局都是直接或间接继承自ViewGroup
● View是Android中最基本的一种UI控件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件
● ViewGroup是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个

3.4.1 引入布局

例如:创建一个标题栏布局,引入到主布局中
创建标题栏布局:

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

属性说明:
● android:background 用于给控件或布局指定一个背景
● android:layout_margin 指定控件在上下左右方向上偏移的距离,单位为dp
使用标题栏:

<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语句将标题栏布局引入进来。
隐藏系统自带的标题栏
在显示我们通过引入布局写的标题栏前,我们需要隐藏掉系统自带的标题栏:

ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
    
    
    actionBar.hide();
}

通过调用getSupportActionBar()获得ActionBar的实例,然后调用ActionBar的hide()方法将标题栏隐藏。
注意:getSupportActionBar() 要在setContentView前面调用。而且getSupportActionBar() 只适用于AppCompatActivity

3.4.2 创建自定义控件

引入布局很好的解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,那么我们就需要在每个活动中单独编写一次事件注册的代码。比如标题栏中的返回按钮,其实不管在哪个活动中,这个按钮的功能都是销毁当前活动。如果每一个活动中都需要重新注册一遍返回按钮的点击事件,这样就会增加很多重复代码,此时我们就应使用自定义控件的方式来解决。

自定义控件

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

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

    <EditText
        android:id="@+id/text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="你好请输入"
        android:layout_weight="1">
    </EditText>

    <Button
        android:id="@+id/button1"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content">
    </Button>
</LinearLayout>

控件代码:


public class Title extends LinearLayout implements View.OnClickListener{
    
    

    private Button button;
    private Button button1;
    public Title(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
        View view = LayoutInflater.from(context).inflate(R.layout.layout,this);
        button = view.findViewById(R.id.button);
        button1 = view.findViewById(R.id.button1);
        button.setOnClickListener(this);
        button1.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()) {
    
    
            case R.id.button:
                Toast.makeText(getContext(),"button1",Toast.LENGTH_LONG).show();
                break;
            case R.id.button1:
                Toast.makeText(getContext(),"button2",Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

Layout inflation是在android系统中使用的术语,当XML布局资源被解析并转换成View对象时会用到。
● 重写了LinearLayout带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数
● 使用LayoutInflater对标题栏布局进行动态加载,LayoutInflater的from()方法构建出一个LayoutInflater对象,然后调用inflate()方法动态加载一个布局。
inflate()方法第一个参数是要加载布局文件的id,第二个参数是给加载好的布局添加一个父布局。
● 通过findViewById()找到布局文件中的控件,分别为各个按钮注册点击事件。
引用 LinearLayout 用法: https://www.jianshu.com/p/14610c347f09

MainActivity_layout引入控件布局:

<?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=".MainActivity">
    <com.example.relative.Title
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </com.example.relative.Title>
</LinearLayout>

MainActivity代码:

public class MainActivity extends AppCompatActivity {
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        setContentView(R.layout.activity_main);
    }
}

在这里插入图片描述

3.5 ListView

ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。

3.5.1 ListView的简单用法

在activity_main.xml中的使用:

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

● data 是ListView中要显示的数据,可以自定义,也可以从后台获取
● ArrayAdapter 数据无法直接传给ListView,需要借助适配器来完成。而ArrayAdapter是Android中提供的适配器实现类,可以通过泛型来指定要适配的数据类型。
● ArrayAdapter 构造函数参数一:当前上下文;参数二:ListView子项布局的id;参数三:要适配的数据。
● android.R.layout.simple_list_item_1 是一个Android内置的布局,里面只有一个TextView,可用于简单地显示一段文本。
● listView.setAdapter() 通过该方法将构建好的适配器对象传递进去,这样ListView和数据之间就建立了关联。

3.5.2 定制ListView的界面

ListView的子项布局正常情况下不止一段文本,下面我们就在上面每个子项布局的水果旁边加上一个图样。
定义实体类

public class Fruit {
    
    
    private String Name;
    private int Id;

    public Fruit(String _Name,int _Id) {
    
    
        Name = _Name;
        Id = _Id;
    }
    public String getName() {
    
    return this.Name;}
    public int getId(){
    
    return this.Id;}
}

其中:name表示水果的名字,imageId表示水果对应图片的资源id。

自定义ListView子项布局

<?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:orientation="horizontal">
    <ImageView
        android:id="@+id/fruitimage"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content">
    </ImageView>

    <TextView
        android:id="@+id/fruittext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </TextView>
</LinearLayout>

自定义适配器

public class FruitAdpter extends ArrayAdapter<Fruit> {
    
    
    private  int resource;
    public FruitAdpter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
    
    
        super(context, resource, objects);
        resource =resource;
    }
    public
    View getView(int position,  View convertView,ViewGroup parent) {
    
    
        Fruit fruit = getItem(position);
        View view=null;
        ViewHodle viewHodle = null;
        if (convertView == null) {
    
    
            view = LayoutInflater.from(getContext()).inflate(R.layout.layout,parent,false);
            viewHodle = new ViewHodle();
            viewHodle.imageView = view.findViewById(R.id.fruitimage);
            viewHodle.textView = view.findViewById(R.id.fruittext);
            view.setTag(viewHodle);
        }else {
    
    
            view = convertView;
            viewHodle = (ViewHodle) view.getTag();
        }
        viewHodle.imageView.setImageResource(fruit.getId());
        viewHodle.textView.setText(fruit.getName());
        return view;
    }
    private class ViewHodle {
    
    
        public ImageView imageView;
        public TextView textView;
    }
}

● FruitAdapter 重写父类个构造函数,将上下文、ListView子项布局id和数据传递进来
● 重写getView()方法。每个子项滚动到屏幕内时都会调用这个方法。
● getItem()方法得到当前项的Fruit实例
● LayoutInflater 为子项加载我们传入的布局
● inflate()方法的参数三要传入false,表示只让我们在父布局中声明的layout生效,但不会为这个View添加父布局,因为View一旦有了父布局,就不能将它添加到ListView中
● 调用View的findViewById()方法分别获取到ImageView和TextView的实例,然后分别调用setImageResource()和setText()来设置显示图片和文字
在MainActivity中的代码:

public class MainActivity extends AppCompatActivity {
    
    

    private ListView listView = null;
    private List<Fruit> fruitList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        FruitAdpter fruitAdpter = new FruitAdpter(getApplicationContext(),R.layout.layout,fruitList);
        listView = findViewById(R.id.listview);
        listView.setAdapter(fruitAdpter);
    }
    public void initData() {
    
    
        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);
        }
    }
}
<?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=".MainActivity">

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

在这里插入图片描述

3.5.3 提升ListView的运行效率

FruitAdapter的getView方法中,每次都将布局重新加载一遍 FruitAdapter的getView方法中,每次都会调用View的findViewById()方法去获取控件的实例

public class FruitAdpter extends ArrayAdapter<Fruit> {
    
    
    private  int resource;
    public FruitAdpter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
    
    
        super(context, resource, objects);
        resource =resource;
    }
    public
    View getView(int position,  View convertView,ViewGroup parent) {
    
    
        Fruit fruit = getItem(position);
        View view=null;
        ViewHodle viewHodle = null;
        if (convertView == null) {
    
    
            view = LayoutInflater.from(getContext()).inflate(R.layout.layout,parent,false);
            viewHodle = new ViewHodle();
            viewHodle.imageView = view.findViewById(R.id.fruitimage);
            viewHodle.textView = view.findViewById(R.id.fruittext);
            view.setTag(viewHodle);
        }else {
    
    
            view = convertView;
            viewHodle = (ViewHodle) view.getTag();
        }
        viewHodle.imageView.setImageResource(fruit.getId());
        viewHodle.textView.setText(fruit.getName());
        return view;
    }
    private class ViewHodle {
    
    
        public ImageView imageView;
        public TextView textView;
    }
}

● 在getView()方法中,判断convertView是否为null,如果为null,则使用LayoutInflater去加载布局;如果不为null,则直接对convertView重用
●通过setTag getTag 重复利用ViewHodle
● 新建一个内部类ViewHolder,然后分别使用setTag()和getTag()方法去存储和取出

3.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_item, mFruitList);
    listView.setAdapter(adapter);
	//注册监听器
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    
    
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    
    
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
        }
    });
}

当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法,这个方法中我们可以通过position参数来判断出用户点击的是哪一个子项。

3.6 RecyclerView

ListView只能实现数据纵向滚动效果,无法实现横向滚动。因此Android提供了一个增强版的ListView——RecyclerView。

3.6.1 RecyclerVeiw的基本用法

RecyclerView属于新增的控件,为了让RecyclerView在所有的版本上都能适用,Android团队采用同样的方式,将RecyclerView定义在了support库中.
使用步骤:
1.build.gradle添加依赖:

implementation 'androidx.recyclerview:recyclerview:1.0.0'

2.XML引入
由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写出来。
main_activity.xml

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

layout.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">
    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </ImageView>
    <TextView
        android:id="@+id/text"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content">
    </TextView>
</LinearLayout>

3.创建适配器
步骤:
1.创建适配器类继承自RecyclerView.Adapter,泛型传入RecyclerView.ViewHolder类。
2.创建内部类即RecyclerView.ViewHolder类的子类,并初始化item的控件。

3.重写RecyclerView.Adapter类的相关方法。

必须重写onCreateViewHolder(), onBindViewHolder( )getItemCount()三个方法
 ●  onCreateViewHolder()用于创建ViewHolder实例,并把加载的布局传入到构造函数去,再把ViewHolder实例返回。 
 ●  onBindViewHolder()则是用于对子项的数据进行赋值,会在每个子项被滚动到屏幕内时执行。position得到当前项的Fruit实例。 
 ●  getItemCount()返回RecyclerView的子项数目。
public class Fruit {
    
    
    private String Name;
    private int Id;

    public Fruit(String _Name,int _Id) {
    
    
        Name = _Name;
        Id = _Id;
    }
    public String getName(){
    
    return this.Name;}
    public int getId(){
    
    return this.Id;}
}
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHodeler> {
    
    
    private static final String TAG = "fruit";
    private List<Fruit> fruitList;
    static class ViewHodeler extends  RecyclerView.ViewHolder {
    
    
        ImageView imageView;
        TextView textView;
        public ViewHodeler(@NonNull View itemView) {
    
    
            super(itemView);
            imageView = itemView.findViewById(R.id.image);
            textView = itemView.findViewById(R.id.text);
        }
    }
    public FruitAdapter(List<Fruit> fruitList1) {
    
    
        fruitList = fruitList1;
    }
    @NonNull
    @Override
    public ViewHodeler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        Log.d(TAG,"onCreateViewHolder");
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout,parent,false);
        return new ViewHodeler(view);
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHodeler holder, int position) {
    
    
        Log.d(TAG,"onBindViewHolder: "+position);
        Fruit fruit = fruitList.get(position);
        holder.imageView.setImageResource(fruit.getId());
        holder.textView.setText(fruit.getName());
    }
    @Override
    public int getItemCount() {
    
    
        Log.d(TAG,"getItemCount: "+ fruitList.size());
        return fruitList.size();
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
    
    

    private List<Fruit> fruitList = new ArrayList<>();
    private FruitAdapter fruitAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        setContentView(R.layout.activity_main);
        initData();
        fruitAdapter = new FruitAdapter(fruitList);
        RecyclerView recyclerView = findViewById(R.id.recyclerview);
        //默认为垂直方向
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(fruitAdapter);
    }
    public void initData() {
    
    
        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);
        }
    }
}

LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。
效果:
在这里插入图片描述

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

实现横向滚动

因为要横向滚动,所以需要对上面的layout.xml文件进行修改:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/image"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_gravity="center_horizontal">
    </ImageView>
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp">
    </TextView>
</LinearLayout>

MainActivity.java

    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        setContentView(R.layout.activity_main);
        initData();
        FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
        RecyclerView recyclerView = findViewById(R.id.recycler_h);
        //设置方向水平  LinearLayoutManager.HORIZONTAL
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        
        recyclerView.setAdapter(fruitAdapter);
        recyclerView.setLayoutManager(layoutManager);
    }

RecyclerView的布局排列是由LayoutManager去控制,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局。

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

瀑布流布局

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

1. 修改fruit_item.xml文件
因为要实现瀑布流效果,所以需要对上面的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>

2. 使用StaggerdGridLayoutManager

public class MainActivity extends AppCompatActivity {
    
    

    private List<Fruit> mFruitList = 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(mFruitList);
        recyclerView.setAdapter(adapter);
    }

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

StaggeredGridLayoutManager的构造函数接受两个参数,参数一:用于指定布局的列数,传入3表示会把布局分为3列;参数二用于指定布局的排列方向

3.6.3 RecyclerView的点击事件

RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件。
其实ListView的setOnItemClickListener()方法注册的是子项的点击事件,如果想点击子项里面具体的某一个按钮,ListView实现起来就有点复杂。为此,RecyclerView干脆直接摒弃了子项点击事件的监听器,所有的点击事件都由具体的View去注册

注册监听
RecyclerView的点击事件是在Adapter中实现的,修改FruitAdapter文件:

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

    private List<Fruit> mFruitList;

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

    //创建ViewHolder实例
    @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();
            }
        });
        //给子项中的ImageView注册点击事件
        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;
    }

    //对RecyclerView子项的数据进行赋值
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
    
    
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    //RecyclerView有多少子项
    @Override
    public int getItemCount() {
    
    
        return mFruitList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
    
    
        ImageView fruitImage;
        TextView fruitName;
        View fruitView; //子项最外层布局实例
        public ViewHolder(View itemView) {
    
    
            super(itemView);
            fruitView = itemView;
            fruitImage = ((ImageView) itemView.findViewById(R.id.fruit_image));
            fruitName = ((TextView) itemView.findViewById(R.id.fruit_name));
        }
    }
}

实现步骤:
● 在ViewHolder中添加fruitView变量保存子项最外层布局的实例
● 在onCreateViewHolder中注册相关控件的点击事件

猜你喜欢

转载自blog.csdn.net/weixin_41477306/article/details/105396626