第一行代码学习笔记第三章——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中显示的文本内容。

这里简单的解释了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"/>

属性说明:

其它属性跟上面说的TextView一样,这里说一个特殊的。

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

Button注册监听器

  • 方式一(匿名类):

      public class MainActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              Button button = (Button) findViewById(R.id.button);
              button.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      // 在此处添加逻辑
                  }
              });	
          }
      }
    
  • 方式二(实现接口):

      public class MainActivity extends AppCompatActivity implements View.OnClickListener {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              Button button = (Button) findViewById(R.id.button);
              button.setOnClickListener(this);
          }
      
          @Override
          public void onClick(View v) {
              switch (v.getId()) {
                  case R.id.button:
                      // 在此处添加逻辑
                      break;
                  default:
                      break;
              }
          }
      }
    
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"/>

属性说明:

  • android:visibility 控制控件是否可见。 visible表示控件可见,默认值。invisible表示控件不可见,但仍占据原来的位置,相当于控件透明。gone表示控件不可见,且不占据原来的位置。

  • style 指定控件的形状。

  • android:max 给进度条设置一个最大值。

代码中控制控件是否可见

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

主要作用:

用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。

使用方法:

    AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
    dialog.setTitle("This is Dialog");
    dialog.setMessage("Something improtant");
    dialog.setCancelable(false);
    dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            
        }
    });
    dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            
        }
    });
	dialog.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 详解4中基本布局

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

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时,只有水平方向上的对齐方式才会生效。

3.3.2 相对布局

RelativeLayout称作相对布局。通过相对定位的方式让控件出现在布局的任何位置。

基本使用:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    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_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Button 1"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="Button 2"/>

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

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentBottom="true"
        android:text="Button 4"/>

    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:text="Button 5"/>

    <Button
        android:id="@+id/button6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/button3"
        android:layout_toLeftOf="@id/button3"
        android:text="Button 6"/>

    <Button
        android:id="@+id/button7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="Button 7"/>

    <Button
        android:id="@+id/button8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/button3"
        android:layout_toLeftOf="@id/button3"
        android:text="Button 8"/>

    <Button
        android:id="@+id/button9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="Button 9"/>

</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的情况。

3.3.3 帧布局

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

基本使用:

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

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:text="This is TextView"/>

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

</FrameLayout>

属性说明:

  • android:layout_gravity 指定控件在布局中的对齐方式。
3.3.4 百分比布局

在前面介绍的LinearLayout、RelativeLayout和FrameLayout三种布局,只有LinearLayout支持使用layout_weight属性来实现按比例指定控件大小的功能。为此,Android引入了百分比布局。我们不再需要使用wrap_content、match_parent等方式来指定控件的大小,而是直接指定控件在布局中所占的百分比,这样就可以实现任意比例分割布局的效果。

由于LinearLayout本身支持按比例指定控件的大小,因此百分比布局只为RelativeLayout和FrameLayout进行了功能扩展,提供了PercentFrameLayout和PercentRelativeLayout这两个布局。

Android开发人员将百分比布局定义在了support库中,因此我们需要在项目的build.gradle中添加百分比布局库的依赖,就能保证百分比布局在Android所以系统版本上的兼容性。

打开app/build.gradle文件,在dependencies闭包中添加如下内容:

dependencies {
    implementation 'com.android.support:percent:25.3.1'
}

基本使用:

<android.support.percent.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%"/>

</android.support.percent.PercentFrameLayout>

效果图:

属性说明:

  • android.support.percent.PercentFrameLayout 由于百分比布局并不是内置在系统SDK当中,所以需要把完整的包路径写出来

  • xmlns:app 定义一个app的命名空间,这样才能使用百分比布局的自定义属性

  • app:layout_widthPercent 指定控件的宽度在布局中所占的百分比

  • app:layout_heightPercent 指定控件的高度在布局中所占的百分比

  • PercentFrameLayout继承了FrameLayout的特性,所以我们需要通过android:layout_gravity属性来指定放置布局的位置。

PercentRelativeLayout的用法同PercentFrameLayout很类似。它继承了RelativeLayout的所有特性。可按照PercentFrameLayout自行尝试。

3.4 创建自定义控件

前面学习了Android中的常用控件和布局,顺便梳理下控件和布局之间的继承关系:

从上面的图中可得出如下结论:

  • 所有控件都是直接或间接继承自View

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

  • View是Android中最基本的一种UI控件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件

  • ViewGroup是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器

3.4.1 引入布局

在平时的项目开发中,很多活动可能都需要相同样式的标题栏,如果在每个活动中都编写一遍同样的标题栏代码,就会导致代码的大量重复,这时候我们就可以用引入布局的方式来解决这个问题。

新建布局:

新建一个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>

属性说明:

  • 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()方法将标题栏隐藏。

效果图:

3.4.2 创建自定义控件

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

自定义控件

public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, @Nullable 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 click Edit button", Toast.LENGTH_SHORT).show();
            }
        });
    }
}
  • 重写了LinearLayout带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数

  • 使用LayoutInflater对标题栏布局进行动态加载,LayoutInflater的from()方法构建出一个LayoutInflater对象,然后调用inflate()方法动态加载一个布局。

  • inflate()方法第一个参数是要加载布局文件的id,第二个参数是给加载好的布局添加一个父布局。

  • 通过findViewById()找到布局文件中的控件,分别为各个按钮注册点击事件。

在布局中使用自定义控件

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

备注:
在使用自定义控件的时候,我们需要指定控件的完整类名。

效果图:

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

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

    public String getName() {
        return name;
    }

    public int getImageId() {
        return imageId;
    }
}

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

自定义ListView子项布局

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

自定义适配器

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

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

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull 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);
        fruitImage.setImageResource(fruit.getImageId());
        fruitName.setText(fruit.getName());
        return view;
    }
}
  • 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 List<Fruit> mFruitList = new ArrayList<>();

    @Override
    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);
    }

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

效果图

3.5.3 提升ListView的运行效率

上面实例中的ListView运行效率很低,主要原因有如下两点:

  • FruitAdapter的getView方法中,每次都将布局重新加载一遍

  • FruitAdapter的getView方法中,每次都会调用View的findViewById()方法去获取控件的实例

针对上面的两种情况,我们可以使用convertView和ViewHolder去优化:

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

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

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull 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.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
        return view;
    }

    class ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
    }
}
  • 在getView()方法中,判断convertView是否为null,如果为null,则使用LayoutInflater去加载布局;如果不为null,则直接对convertView重用

  • 新建一个内部类ViewHolder,然后分别使用setTag()和getTag()方法去存储和取出

3.5.4 ListView的点击事件

使用setOnItemClickListener()方法为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的基本用法

1. 添加依赖

RecyclerVeiw属于新增控件,Android团队将其定义在了support库中,因此我们需要在项目的build.gradle中添加相应的依赖库。

dependencies {
    implementation 'com.android.support:recyclerview-v7:24.2.1'
}

2. 布局中使用

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

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

</LinearLayout>

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

3. 新建适配器

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);
        ViewHolder holder = new ViewHolder(view);
        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;
        public ViewHolder(View itemView) {
            super(itemView);
            fruitImage = ((ImageView) itemView.findViewById(R.id.fruit_image));
            fruitName = ((TextView) itemView.findViewById(R.id.fruit_name));
        }
    }
}

4. 真正使用RecyclerView

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);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        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("Apple", R.drawable.apple_pic);
            mFruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
            mFruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
            mFruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            mFruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            mFruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            mFruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            mFruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            mFruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            mFruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            mFruitList.add(mango);
        }
    }
}

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

5. RecyclerView效果图

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

横向滚动

1. 修改fruit_item.xml文件

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

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

    <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="center_horizontal"
        android:layout_marginTop="10dp"/>

</LinearLayout>

2. 设置布局横向滚动

    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的方向
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(mFruitList);
        recyclerView.setAdapter(adapter);
    }

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

3. 效果图

瀑布流布局

除了LinearLayoutManager之外,RecyclerView还提供了GridLayoutManager和StaggerdGridLayoutManager这两种内置的布局排列方式。其中,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. 效果图

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中注册相关控件的点击事件

效果图

3.7 编写界面的最佳实践

3.7.1 制作Nine-Patch图片

在Android sdk目录下有一个tools文件夹,在这个文件夹中找到draw9patch.bat文件,我们使用他来制作Nine-Patch

要打开这个文件,必须先将JDK的bin目录配置到环境变量中。例如使用的是AndroidStudio内置的sdk,要配置的路径就是AndroidStudio安装目录/jre/bin

双击打开draw9patch.bat文件,在导航栏点击File——>Open 9-patch将需要制作的图片加载进来,如下图所示:

可以在图片的四个边框绘制一个个的小黑点。上边框和左边框绘制的部分表示当图片需要拉伸时就拉伸黑点标记的区域;下边框和右边框绘制的部分表示内容会被放置的区域。

3.7.2 编写精美的聊天界面

略略略。。。

非常感谢您的耐心阅读,希望我的文章对您有帮助。欢迎点评、转发或分享给您的朋友或技术群。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章