第1章 简单控件

        本章介绍App开发常见的几类简单控件的用法,主要包括:显示文字的文本视图、容纳视图的常用布局、响应点击的按钮控件、显示图片的图像视图等。然后结合本章所学的知识,演示一个实战项目“简单计算器”的设计与实现。

1.1  文本显示

        本节介绍如何在文本视图TextView上显示规定的文本,包括:怎样在XML文件和java代码中设置文本内容,尺寸的大小有哪些单位、又该怎么设置文本的大小,颜色的色值是如何表达的、又该怎么设置文本的颜色。

1.1.1  设置文本的内容

        设置文本内容的两种方式,一种是在XML文件中通过属性android:text设置文本,比如下面这样:

<TextView
        android:id="@+id/tv_hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="你好,世界!" />

        另一种实在Java代码中调用文本视图对象的setText方法设置文本,比如下面这样:

        // 获取名叫tv_hello的文本视图
        TextView tv_hello = findViewById(R.id.tv_hello);
        tv_hello.setText("你好,世界"); // 设置tv_hello的文字内容

        在XML文件中设置文本的话,把鼠标移到“你好,世界”上方时,Android Studio会弹出如图所示的提示框。

        提示内容为“Hardcoded string "你好,世界",should use @string resouce ”,意思是说这几个字是硬编码的字符串,建议使用来自@string的资源。Android Studio不推荐在XML布局文件里直接写字符串,因为可能有好几个页面都显示“你好,世界”,若想把这句话换成“你吃饭了吗?”,就得一个一个XML文件改过去,无疑费时费力。故而Android Studio推荐把字符串放到专门的地方进行管理,这个名为@string的地方位于res/values目录下的strings.xml,打开该文件发现它的初始内容如下:

<resources>
    <string name="app_name">My Application</string>
</resources>

        strings.xml定义了一个名为“app_name”的字符串常量,其值为“My Application”。在此添加新的字符串定义,字符串名为“hello”,字符串值为“你好,世界”,添加之后的strings.xml内容如下:

<resources>
    <string name="app_name">My Application</string>
    <string name="hello">你好,世界</string>
</resources>

        添加完新的字符串定义,回到XML布局文件,将android:text属性值改为“@string/字符串名”这般,也就是“@string/hello”,修改之后的TextView标签示例如下:

    <TextView
        android:id="@+id/tv_hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

        然后把鼠标移到“你好,世界”上方,此时Android Studio不再弹出任何提示了。

        若要在Java代码中引用字符串资源,则需要在调用setText方法时填写形如“R.string.字符串名”的参数,就本例而言填入“R.string.hello”,修改之后的Java代码示例如下:

        //获取名为tv_hello的文本视图
        TextView tv_hello = findViewById(R.id.tv_hello);
        tv_hello.setText(R.string.hello);  //设置tv_hello的文字资源

        至此不管是XML文件还是Java代码都从strings.xml引用字符串资源,以后想把“你好,世界”改为其他文字的话,只需改动string.xml一个地方即可。

1.1.2  设置文本的大小

        TextView允许设置文本内容,也允许设置文本大小,在Java代码中调用setTextSize方法,即可指定文本大小,就像以下代码这样:

//从布局文件中获取名为tv_sp的文本视图
TextView tv_sp = findViewById(R.id.tv_sp);
tv_sp.setTextSize(30);  //设置tv_sp的文本大小

        这里的大小数值越大,则看到的文本也越大;大小数值越小,则看到的文本也越小。在XML文件中则通过属性android:textSize指定文本大小,可是如果给TextView标签添加"android:textSize="30"",数字马上变成红色,鼠标移过去还会提示错误“Cannot resolve symbol '30'”,意思是无法解析“30”这个符号,如图所示。

        原来文本大小存在不同的字号单位,XML文件要求在字号数字后面写明单位类型,常见的字号单位主要有px、dp、sp 3种,分别介绍如下。

        1.  px

        px是手机屏幕的最小显示单位,它与设备的显示屏有关。一般来说,同样尺寸的屏幕(比如6英寸手机),如果看起来越清晰,则表示像素密度越高,以px计量的分辨率也越大。

        2.  dp

        dp有时也写作dip,指的是与设备无关的显示单位,它只与屏幕的尺寸有关。一般来说,同样尺寸的屏幕以dp计量的分辨率是相同的,比如同样是6英寸手机,无论它由哪个厂家生产,其分辨率换算成dp单位都是一个大小。

        3.  sp

        sp的原理跟dp差不多,但它专门用来设置字体大小,也是Android推荐的字号单位。手机在系统设置里可以调整字体的大小(小、标准、大、超大)。设置普通字体时,同数值dp和sp的文字看起来一样大;如果设置为大字体,用dp设置的文字没有变化,用sp设置的文字就变大了。

        字体大小采用不同单位的话,显示的文字大小各不相同。例如,30px、30dp、30sp这3个字号,在不同手机上的显示大小有所差异。有的手机像素密度较低,1个dp相当于2个px,此时30px等同于15dp;有的手机像素密度较高,1个dp相当于3个px,此时30px等同于10dp。假设某个APP的内部文本使用字号30px,则该App安装到前一部手机的字体大小为15dp,安装到后一部手机的字体大小为10dp,显然后一部手机显示的文本会更小。

        至于dp与sp之间的区别,可通过以下实验加以观察。首先创建测试活动页面,该页面的XML文件分别声明30px、30dp、30sp这3个字号的TextView控件,布局内容如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="你好,世界!(px大小)"
        android:textSize="30px" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="你好,世界!(dp大小)"
        android:textSize="30dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="你好,世界!(sp大小)"
        android:textSize="30sp" />

</LinearLayout>

 

1.1.3    设置文本的颜色 

         除了文字大小外,文字的颜色也经常需要修改,毕竟Android默认的灰色文字不够醒目。在Java代码中调用setTextColor方法即可设置文本颜色,具体在Color类中定义了12种颜色,详细的取值说明见表。

                                                 表    颜色类型的取值说明

Color类中的颜色类型说明Color类中的颜色类型说明
BLACK黑色GREEN绿色
DKGRAY深灰BLUE蓝色
GRAY灰色YELLOW黄色
LTGRAY浅灰CYAN青色
WHITE白色MAGENTA玫红
RED红色TRANSPARENT透明

        比如以下代码便将文本试图的文字颜色改成了绿色:

//从布局文件中获取名为tv_code_system的文本试图
TextView tv_code_system = findViewById(R.id.tv_code_system);
//将tv_code_system的文字颜色设置为系统自带的颜色
tv_code_system.setTextColor(Color.GREEN);

         由于XML文件无法引用Color类的颜色常量,为此Android制定了一套规范的编码标准,将色值交由透明度alpha和RGB三原色(红色red、绿色green、蓝色blue)联合定义。该标准又有8位十六进制数与6位十六进制数两种表达方式,例如8位编码FFEEDDCC中,FF表示透明度,EE表示红色的浓度,DD表示绿色的浓度,CC表示蓝色的浓度。透明度为FF表示完全不透明,为00表示完全透明。RGB三色的数值越大,表示颜色越浓,也就越暗;数值越小,表示颜色越淡,也就越亮。RGB亮到极致就是白色,暗到极致就是黑色。

        至于6位十六进制编码,则有两种情况:它在XML文件中默认不透明(等价于透明度为FF),在代码中默认透明(等价于透明度为00)。以下代码给两个文本视图分别设置6位色值与8位色值,注意添加0x前缀表示十六进制数:

//从布局文件中获取名为tv_code_six的文本视图
TextView tv_code_six = findViewById(R.id.tv_code_six);
//将tv_code_six的文字颜色设置为透明的绿色,透明就是看不到
tv_code_six.setTextColor(0x00ff00);
//从布局文件中获取名为tv_code_eight的文本视图
TextView tv_code_eight = findViewById(R.id.tv_code_eight);
//将tv_code_eight的文字颜色设置为不透明的绿色,即正常的绿色
tv_code_eight.setTextColor(0xff00ff00);

        运行测试App,发现tv_code_six控件的文本不见了(其实是变透明了),而tv_code_eight控件的文本显示正常的绿色。

        在XML文件中可通过属性android:textColor设置文本颜色,但要给色值添加井号前缀(#),设定文本颜色的TextView标签示例如下:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="布局文件设置6位文字颜色"
        android:textColor="#00ff00" />

         就像字符串资源那样,Android把颜色也当做一种资源,打开res/values目录下的colors.xml,发现里面已经定义了2种颜色:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
</resources>

         那么先在resources节点内部补充如下的绿色常量定义:

<color name="green">#00ff00</color>

        然后回到XML布局文件,把android:textColor的属性值改为“@color/颜色名称”,也就是android:textColor="@color/green",修改之后的TextView标签如下:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="布局文件设置6位文字颜色"
        android:textColor="@color/green" />

         不仅文本颜色,还有背景颜色也会用到上述的色值定义,在XML文件中通过属性android:background设置控件的背景颜色。Java代码则有两种方式设置背景颜色:倘若色值来自Color类或十六进制数,则调用setBackgroundColor方法设置背景颜色;倘若色值来自colors.xml中的颜色资源,则调用setBackgroundResource方法,以“R.color.颜色名称”的格式设置背景颜色。下面是两种方式的背景颜色设定代码的例子:

//从布局文件中获取名为tv_code_background的文本视图
TextView tv_code_background = findViewById(R.id.tv_code_background);
//将tv_code_background的背景颜色设置为绿色
tv_code_background.setBackgroundColor(Color.GREEN);    //在代码中定义的色值
tv_code_background.setBackgroundResource(R.color.green);    //颜色来自资源文件

        注意属性android:background和setBackgroundResource方法,它俩用来设置控件的背景,不单单是背景颜色,还包括背景图片。在设置背景图片之前,先将图片文件放到res/drawable***目录(以drawable开头的目录,不仅仅是drawable目录),然后把android:background的属性值改为“@drawable/不含扩展名的图片名称”,或者调用setBackgroundResource方法填入“R.drawable.不含扩展名的图片名称”。

1.2    视图基础

        本节介绍视图的几个基本概念及其用法,包括:如何设置视图的宽度和高度,如何设置视图的外部间距和内部间距,如何设置视图的外部对齐方式和内部对齐方式,等等。

1.2.1    设置视图的宽和高

        手机屏幕是块长方形区域,较短的那条边叫做宽,较长的那条边叫做高。App控件通常也是长方形形状,控件宽度通过属性android:layout_width表达,控件高度通过属性android:layout_height表达,宽和高的取值主要有下列3种:

        (1)match_parent:表示与上级视图保持一致。上级视图的尺寸有多大,当前视图的尺寸就有多大。

        (2)wrap_content:表示与内容自适应。对于文本视图来说,内部文字需要多大的显示空间,当前视图就要占据多大的尺寸。但最宽不能超过上级视图的宽度,一旦超过就要换行;最高不能超过上级视图的高度,一旦超过就会被隐藏。

        (3)以dp为单位的具体尺寸,比如300dp,表示宽度或者高度就是这么大。

        在XML文件中采用以上任一方式均可设置视图的宽和高,但在Java代码中设置宽和高就有点复杂了,首先确保XML中的宽和高属性值为wrap_content,这样才允许在代码中修改宽和高。接着打开该页面对应的Java代码,依序执行以下3个步骤:

01    调用控件对象的setLayoutParams方法获取布局参数,参数类型为ViewGroup.LayoutParams.

02    布局参数的width属性表示宽度,height属性表示高度,修改这两个属性值,即可调整控件的    宽和高。

03    调用控件对象的setLayoutParams方法,填入修改后的布局参数使之生效。

         不过布局参数的width和height两个数值默认是px单位,需要将dp单位的数值转换为px单位的数值,然后才能赋值给width属性和height属性。下面是把dp大小转为px大小的方法代码:

package util;

import android.content.Context;

public class Utils {
    //根据手机的分辨率从dp的单位转成为px(像素)
    public static int dip2px(Context context, float dpValue){
        //获取当前手机的像素密度(1个dp对应几个px)
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue*scale+0.5f);    //四舍五入取整
    }
}

        有了上面定义的公共方法dip2px,就能将某个dp数值转换成px数值,比如准备把文本视图的宽度改为300dp,那么调整宽度的Java代码示例如下:

package com.example.myapplication;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import util.Utils;

@SuppressLint("MissingInflatedId")
public class MainActivity extends AppCompatActivity {
    public TextView tv_code;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        //获取名为tv_code的文本视图
        tv_code = findViewById(R.id.tv_code);
        //获取tv_code的布局参数(含宽度和高度)
        ViewGroup.LayoutParams params = tv_code.getLayoutParams();
        //修改布局参数中的宽度数值,注意默认是px单位,需要把dp数值转成px数值
        params.width = Utils.dip2px(this, 300);
        tv_code.setLayoutParams(params);  //设置tv_code的布局参数
    }
}

        接下来通过演示页面并观察几种尺寸设置方式的界面效果,主要通过背景色区分当前视图的宽高范围,详细的XML文件内容如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity_view_border"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:background="#00ffff"
        android:text="视图宽度采用wrap_content定义"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="5dp"
        android:background="#00ffff"
        android:text="视图宽度采用match_parent定义"/>
    <TextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:background="#00ffff"
        android:text="视图宽度采用固定大小"/>
    <TextView
        android:id="@+id/tv_code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:background="#00ffff"
        android:text="通过代码指定视图宽度"/>
</LinearLayout>

        运行测试App,打开的演示界面如图所示,依据背景色判断文本视图的边界,可见wrap_content方式刚好包住了文本内容,match_parent方式扩展了与屏幕等宽,而300dp的宽度介于前两者之间(安卓手机的屏幕宽度基本为360dp)。

1.2.2     设置视图的间距

        在上一小节末尾的XML文件中,每个TextView标签都携带新的属性android:layout_marginTop="5dp",该属性的作用是让当前视图与上方间隔一段距离。同理,android:layout_marginLeft让当前视图与左边间隔一段距离,android:layout_marginRight让当前视图与右边间隔一段距离,android:layout_marginBottom让当前视图与下方间隔一段距离。如果上下左右都间隔同样的距离,还能使用android:layout_margin一次性设置四周的间距。

        layout_margin不单单用于文本视图,还可用于所有视图,包括各类布局和各类控件。因为不管布局还是控件,它们统统由视图基类View派生而来,而layout_margin正是View的一个通用属性,所以View的子子孙孙都能使用layout_margin。在View的大家族中,视图组ViewGroup尤为特殊,它既是View的子类,又是各类布局的基类。布局下面能容纳其他视图,而控件却不行,这正源自ViewGroup的组装特性。View、ViewGroup、控件、布局四者的继承关系如图所示。

         除了layout_margin之外,padding也是View的一个通用属性,它用来设置视图的内部间距,并且padding也提供了paddingTop、paddingBottom、paddingLeft、paddingRight四个方向的距离属性。同样是设置间距,layout_margin指的是当前视图与外部视图(包括上级视图和平级视图)之间的距离,而padding指的是当前视图与内部视图(包括下级视图和内部文本)之间的距离。为了观察外部间距和内部间距的差异,接下来做个实验,看看layout_margin与padding究竟有什么区别。

        首先创建新的活动页面,并给该页面的XML文件填入以下的布局内容:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    tools:context=".activity_view_margin"
    android:orientation="vertical"
    android:background="#00aaff">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="20dp"
        android:background="#ffff99"
        android:padding="60dp">
        <View
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#ff0000"/>
    </LinearLayout>
</LinearLayout>

        上面的XML文件有两层视图嵌套,第一层是蓝色背景布局里面放黄色背景布局,第二层是黄色背景布局里面放红色背景视图。中间层的黄色背景布局,同时设置了20dp的layout_margin,以及60dp的padding,其中padding是layout_margin的三倍宽(60/20=3)。接着运行测试App,看到的演示界面如图所示。

        从效果图可见,外面一圈间隔较窄,里面一圈间隔较宽,表示20dp的layout_margin位于外圈,而60dp的padding位于内圈。这种情况印证了:layout_margin指的是当前图层与外部图层的距离,而padding指的是当前图层与内部图层的距离。

1.2.3    设置视图的对齐方式

        App界面上的视图排列,默认靠左朝上对齐,这也符合日常的书写格式。然而页面的排版不是一成不变的,有时出于美观或者其他原因,要将视图排列改为朝下或靠右对齐,为此需要另外指定视图的对齐方式。在XML文件中通过属性android:layout_gravity可以指定当前视图的对齐方向,当属性值为top时表示视图朝上对齐,为bottom时表示视图朝下对齐,为left时表示视图靠左对齐,为right时表示视图靠右对齐。如果希望视图既朝上又靠左,则用竖线连接top与left,此时属性标记为android:layout_gravity="top|left";如果希望视图既朝下又靠右,则用竖线连接bottom与right,此时属性标记为android:layout_gravity="bottom|right"。

        注意layout_gravity规定的对齐方式,指的是当前视图往上级视图的那个方向对齐,并非当前视图的内部对齐。若想设置内部视图的对齐方向,则需由当前视图的属性android:gravity指定,该属性一样拥有top、bottom、left、right  4种取值及其组合。它与layout_gravity的不同之处在于:layout_gravity设定了当前视图相对于上级视图的对齐方式,而gravity设定了下级视图相对于当前视图的对齐方式;前者决定了当前视图的位置,而后者决定了下级视图的位置。

        为了进一步分辨layout_gravity与gravity的区别,接下来做个实验,对某个布局视图同时设置android:layout_gravity和android:gravity属性,再观察内外视图的对齐情况。下面便是实验用的XML文件例子:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="400dp"
    tools:context=".activity_view_gravity"
    android:background="#ffff99"
    android:padding="5dp">
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="200dp"
        android:layout_weight="1"
        android:layout_gravity="bottom"
        android:gravity="left"
        android:background="#ff0000"
        android:layout_margin="10dp"
        android:padding="10dp">
        <View
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#00ffff"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="200dp"
        android:layout_weight="1"
        android:layout_gravity="top"
        android:gravity="right"
        android:background="#ff0000"
        android:layout_margin="10dp"
        android:padding="10dp">
        <View
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#00ffff"/>
    </LinearLayout>
</LinearLayout>

        运行测试App,打开演示界面如图所示。

        由效果图可见,第一个子布局朝下,并且它的内部视图靠左;而第二个子布局朝上,并且它的内部视图靠右。对比XML文件中的layout_gravity和gravity取值,证明了二者的对齐情况正如之前所言:layout_grativy决定当前视图位于上级视图的哪个方位,而gravity决定了下级视图位于当前视图的哪个方位。

1.3    常用布局 

        本节介绍常见的几种布局用法,包括:在某个方向上顺序排列的线性布局,参照其他视图的位置相对排列的相对布局,像表格那样分行分列显示的网格布局,以及支持通过滑动操作拉出更多内容的滚动视图。

1.3.1    线性布局  LinearLayout

        前几个小节的例程中,XML文件用到了LinearLayout布局,它的学名为线性布局。顾名思义,线性布局像是用一根线把它的内部视图串起来,故而内部视图之间的排列顺序是固定的,要么从左到右排列,要么从上到下排列。在XML文件中,LinearLayout通过属性android:orientation区分两种方向,其中从左到右排列叫做水平方向,属性值为horizontal;从上到下排列叫做垂直方向,属性值为vertical。如果LinearLayout标签不指定具体方向,则系统默认该布局为水平方向排列,也就是默认android:orientation="horizontal"。

        下面做个实验,让XML文件的根节点挂着两个线性布局,第一个线性布局采取水平方向。第二个线性布局采取垂直方向。然后每个线性布局内部各有两个文本视图,通过观察这些文本视图的排列情况,从而检验线性布局的显示效果。详细的XML文件内容如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity_linear_layout"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="横排第一个"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="横排第二个"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="竖排第一个"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="竖排第二个"/>
    </LinearLayout>
</LinearLayout>

        运行测试App,进入如图所示的演示页面,可见horizontal为横向排列,vertical为纵向排列,说明android:orientation的方向属性确实奏效了。

        除了方向之外,线性布局还有一个权重概念。所谓权重,指的是线性布局的下级视图各自拥有多大比例的宽和高。比如一块蛋糕分给两个人吃,可能两个人平均分,也可能甲分三分之一,乙分三分之二。两人平均分的话,先把蛋糕平均切成两半,然后甲分到一半,乙分到另一半,此时甲、乙的权重比为1:1.甲分三分之一、乙分三分之二的话,先把蛋糕平均切成三块,然后甲分到一块,乙分到两块,此时甲、乙的权重比为1:2.就线性布局而言,它自身的尺寸相当于一整块蛋糕,它的下级视图们一起来分这一整块蛋糕,有的视图分得多,有的视图分得少。分多分少全凭每个视图分到了多大的权重而定,这个权重在XML文件中通过属性android:layout_weight来表达。

        把线性布局看作蛋糕的话,分蛋糕的甲、乙两人就相当于线性布局的下级视图。假设线性布局平均分为左、右两块,则甲视图和乙视图的权重比为1:1,意味着两个下级视图的layout_weight属性都是1。不过视图有宽、高两个方向,系统怎知layout_weight表示哪个方向的权重呢?所以这里有个规定,一旦设置了layout_weight属性值,便要求layout_width填0dp或者layout_height填0dp。如果layout_width填0dp,则layout_weight表示水平方向的权重,下级视图会从左往右分割线性布局;如果layout_height填0dp,则layout_weight表示垂直方向的权重,下级视图会从上往下分割线性布局。

        按照左右均分的话,线性布局设置水平方向horizontal,且甲、乙两视图的layout_width都填0dp,layout_weight都填1,此时横排的XML片段示例如下:

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ff0000"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="横排第一个"/>
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="横排第二个"/>
    </LinearLayout>

        按照上下 均分的话,线性布局设置垂直方向vertical,且甲乙两视图对的layout_height都填0dp,layout_weight都填1,此时竖排的XML片段示例如下:

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#00ffff"
        android:orientation="vertical">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="竖排第一个"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="竖排第二个"/>
    </LinearLayout>

        把上面两个片段放到新页面的XML文件中,其中第一个是横排区域,采用红色背景(色值为ff0000),第二个是竖排区域,采用青色背景(色值为00ffff)。重新运行测试App,打开的演示界面如图所示,可见横排区域平均分为左、右两块,竖排区域平均分为上、下两块。

1.3.2    相对布局  RelativeLayout

        线性布局的下级视图是顺序排列着的,另一种相对布局的下级视图位置则由其他视图决定。相对布局名为RelativeLayout,因为下级视图的位置是相对位置,所以得有具体的参照物才能确定最终位置。如果不设定下级视图的参照物,那么下级视图默认显示在RelativeLayout内部的左上角。

        用于确定下级视图位置的参照物分两种:一种是与该视图自身平级的视图;另一种是该视图的上级视图(也就是它归属的RelativeLayout)。综合两种参照物,相对位置在XML文件中的属性取值说明见表。

                   相对位置的属性取值说明

相对位置的属性取值相对位置说明
layout_toLeftOf当前视图在指定视图的左边
layout_toRightOf当前视图在指定视图的右边
layout_above当前视图在指定视图的上方
layout_below当前视图在指定视图的下方
layout_alignLeft当前视图与指定视图的左侧对齐
layout_alignRight当前视图与指定视图的右侧对齐
layout_alignTop当前视图与指定视图的顶部对齐
layout_alignBottom当前视图与指定视图的底部对齐
layout_centerInParent当前视图在上级视图中间
layout_centerHorizontal当前视图在上级视图的水平方向居中
layout_centerVertical当前视图在上级视图的垂直方向居中
layout_alignParentLeft当前视图与上级视图的左侧对齐
layout_alignParentRight当前视图与上级视图的右侧对齐
layout_alignParentTop当前视图与上级视图的顶部对齐
layout_alignParentBottom当前视图与上级视图的底部对齐

        为了更好的理解上述相对属性的含义,接下来使用RelativeLayout及其下级视图进行布局来看看实际效果。下面是演示相对布局的XML文件例子:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    tools:context=".activity_relative_layout">
    <TextView
        android:id="@+id/tv_center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="#eeeeee"
        android:text="我在中间"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:background="#eeeeee"
        android:text="我在水平中间"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:background="#eeeeee"
        android:text="我在垂直中间"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:background="#eeeeee"
        android:text="我跟上级左边对齐"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:background="#eeeeee"
        android:text="我跟上级右边对齐"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:background="#eeeeee"
        android:text="我跟上级顶部对齐"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="#eeeeee"
        android:text="我跟上级底部对齐"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@+id/tv_center"
        android:layout_alignTop="@+id/tv_center"
        android:background="#eeeeee"
        android:text="我在中间左边"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/tv_center"
        android:layout_alignBottom="@+id/tv_center"
        android:background="#eeeeee"
        android:text="我在中间右边"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/tv_center"
        android:layout_alignLeft="@+id/tv_center"
        android:background="#eeeeee"
        android:text="我在中间上面"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_center"
        android:layout_alignRight="@+id/tv_center"
        android:background="#eeeeee"
        android:text="我在中间下面"/>
</RelativeLayout>

        上述XML文件的布局效果如图所示,RelativeLayout下级视图都是文本视图,控件上的文字说明了所处的相对位置,具体的控件显示方位正如XML属性中描述的那样。

1.3.3    网格布局  GridLayout 

        虽然线性布局既能在水平方向排列,也能在垂直方向排列,但它不支持多行多列的布局方式,只支持单行(水平排列)或单列(垂直排列)的布局方式。若要实现类似表格那样的多行多列形式,可采用网格布局GridLayout。

        网格布局默认从左到右、从上到下排列,它先从第一行从左往右放置下级视图,塞满之后另起一行放置其余的下级视图,如此循环往复直至所有下级视图都放置完毕。为了判断能够容纳几行几列,网格布局新增了android:columnCount与android:rowCount两个属性,其中columnCount指定了网格的列数,即每行能放多少个视图;rowCount指定了网格的行数,即每列能放多少个视图。

        下面是运用网格布局的XML布局样例,它规定了一个两行两列的网格布局,且内部容纳四个文本视图。XML文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<GridLayout 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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity_grid_layout"
    android:columnCount="2"
    android:rowCount="2">
    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:background="#ffcccc"
        android:text="浅红色" />
    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:background="#ffaa00"
        android:text="橙色" />
    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:background="#00ff00"
        android:text="绿色" />
    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:gravity="center"
        android:background="#660066"
        android:text="深紫色" />
</GridLayout>

        在一个新建的活动页面加载上述布局,运行App观察到的界面如图所示。

        由图可见,App界面的第一行分布着浅红色背景与橙色背景的文本视图,第二行分布着绿色背景与深紫色背景的文本视图,说明利用网格布局实现了多行多列的效果。

1.3.4    滚动视图  ScrollView

        手机屏幕的显示空间有限,常常需要上下滑动或左右滑动才能拉出其余页面内容,可惜一般的布局节点都不支持自行滚动,这时就要借助滚动视图了。与线性布局类似,滚动视图也分为垂直方向和水平方向两类,其中垂直滚动视图名为ScrollView,水平滚动视图名为HorizontalScrollView。这两个滚动视图的使用并不复杂,主要注意以下3点:

        (1)在垂直方向滚动时,layout_width属性值设置为match_parent,layout_height属性值设置为wrap_content。

        (2)在水平方向滚动时,layout_width属性值设置为wrap_content,layout_height属性值设置为match_parent。

        (3)滚动视图节点下面必须且只能挂着一个子布局节点,否则会在运行时报错Caused by:java.lang.IllegalStateException:ScrollView can host only one direct child。

       下面是ScrollView和HorizontalScrollView的XML例子:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity_scroll_view"
    android:orientation="vertical">
    <HorizontalScrollView
        android:layout_width="wrap_content"
        android:layout_height="200dp">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <View
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:background="#aaffff" />
            <View
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:background="#ffff00" />
        </LinearLayout>
    </HorizontalScrollView>
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <View
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="#00ff00" />
            <View
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="#ffffaa" />
        </LinearLayout>
    </ScrollView>
</LinearLayout>

        运行测试App,可知ScrollView在纵向滚动,而HorizontalScrollView在横向滚动。

 

        有时ScrollView的实际内容不够,又想让它充满屏幕,怎么办呢?如果把layout_height属性赋值为match_parent,结果还是不会充满,正确的做法是再增加一行属性android:fillViewport(该属性值为true表示允许填满视图窗口),属性片段举例如下:

android:layout_height="match_parent"
android:fillViewport="true"

1.4    按钮触控 

        本节介绍按钮控件的常见用法,包括:如何设置大小写属性与点击属性,如何响应按钮的点击事件和长按事件,如何禁用按钮又该如何启用按钮,等等。

1.4.1    按钮控件  Button

        除了文本视图之外,按钮也是一种基础控件。因为Button是由TextView派生而来,所以文本视图拥有的属性和方法,包括文本内容、文本大小、文本颜色等,按钮控件均能使用。不同的是,Button拥有默认的按钮背景,而TextView默认无背景;Button的内部文本默认居中对齐,而TextView的内部文本默认靠左对齐。此外,按钮还要额外注意textAllCaps与onClick两个属性,这两个属性分别介绍如下:

        1.  textAllCaps属性

        对于TextView来说,text属性设置了什么文本,文本视图就显示什么文本。但对于Button来说,不管text属性设置的是大写字母还是小写字母,按钮控件都默认转成大写字母显示。比如在XML文件中加入下面的Button标签:

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Hello World" />

         此功能已取消,不用写了

        2.  onClick属性

        按钮之所以成为按钮,是因为它会响应按下动作,就手机而言,按下动作等同于点击操作,即手指轻触屏幕然后马上松开。每当点击按钮之时,就表示用户确认了某个事项,接下来轮到App进行处理了。onClick属性便用来接管用户的点击动作,该属性的值是个方法名,也就是当前页面的Java代码存在这么一个方法:当用户点击按钮时,就会自动调用该方法。

        例如下面的Button标签指定了onClick属性值为doClick,表示点击该按钮会触发Java代码中的doClick方法:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity_button_style"
    android:orientation="vertical">
    <Button
        android:id="@+id/btn_click_xml"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="直接指定点击方法"
        android:onClick="doClick"
        android:textColor="#000000"
        android:textSize="17sp" />
    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="这里查看按钮的点击结果"
        android:textColor="#000000"
        android:textSize="17sp" />
</LinearLayout>

        与之相对应,页面所在的Java代码需要增加doClick方法,方法代码如下:

package com.example.myapplication;

import android.annotation.SuppressLint;

import java.text.SimpleDateFormat;
import java.util.Date;
@SuppressLint("SimpleDateFormat")
public class DateUtil {
    // 获取当前的日期时间
    public static String getNowDateTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.format(new Date());
    }

    // 获取当前的时间
    public static String getNowTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(new Date());
    }

}
package com.example.myapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class activity_button_style extends AppCompatActivity {
    private TextView tv_result;  //声明一个文本视图实例
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_button_style);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        tv_result = findViewById(R.id.tv_result);  //获取名叫tv_result的文本视图
    }

    public void doClick(View view) {
        String desc = String.format("%s 您点击了按钮:%s",DateUtil.getNowTime(),((Button) view).getText());
        tv_result.setText(desc);
    }
}

         然后编译运行,并在App界面上点击新加的按钮,点击前、后的界面分别如以下两图所示。

        比较两图的文字差异,可见点击按钮之后确实调用了doClick方法。

1.4.2    点击事件和长按事件

        虽然按钮控件能够在XML文件中通过onClick属性指定点击方法,但是方法的名称可以随便叫,既能叫doClick也能叫doTouch,甚至叫它doA或doB都没问题,这样很不利于规范化代码,倘若以后换了别人接手,就不知道doA或doB是干什么用的。因此在实际开发中,不推荐使用Button标签的onClick属性,而是在代码中给按钮对象注册点击监听器。

        所谓监听器,意思是专门监听控件的动作行为,它平时无事可做,只有控件发生了指定的动作,监听器才会触发开关去执行对应的代码逻辑。点击监听器需要实现接口View.OnClickListener,并重写onClick方法补充点击事件的处理代码,再由按钮调用setOnClickListener方法设置监听器对象。比如下面的代码给按钮控件btn_click_single设置了一个点击监听器:

        // 从布局文件中获取名叫btn_click_single的按钮控件
        Button btn_click_single = findViewById(R.id.btn_click_single);
        // 设置点击监听器,一旦用户点击按钮,就触发监听器的onClick方法
        btn_click_single.setOnClickListener(new MyOnClickListener());

        上面的点击监听器名为MyOnClickListener,它的定义代码示例如下:

    // 定义一个点击监听器,它实现了接口View.OnClickListener
    class MyOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) { // 点击事件的处理方法
            String desc = String.format("%s 您点击了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc); // 设置文本视图的文本内容
        }
    }

        接着运行App,点击按钮之后的界面如图所示,可见点击动作的确触发了监听器的onClick方法。

如果一个页面只有一个按钮,单独定义新的监听器倒也无妨,可是如果存在许多按钮,给每个按钮都定义自己的监听器,那就劳民伤财了。对于同时监听多个按钮的情况,更好的办法是注册统一的监听器,也就是让当前页面实现接口View.OnClickListener,如此一来,onClick方法便写在了页面代码之内。因为是统一的监听器,所以onClick内部需要判断是哪个按钮被点击了,也就是利用视图对象的getId方法检查控件编号,完整的onClick代码举例如下:

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_click_public) { // 来自按钮btn_click_public
            String desc = String.format("%s 您点击了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc); // 设置文本视图的文本内容
        }

    }

        当然在该页面的onCreate内部别忘了调用按钮对象的setOnClickListener方法,把按钮的点击监听器设置成当前页面,设置代码如下:

        // 从布局文件中获取名叫btn_click_public的按钮控件
        Button btn_click_public = findViewById(R.id.btn_click_public);
        // 设置点击监听器,一旦用户点击按钮,就触发监听器的onClick方法
        btn_click_public.setOnClickListener(this);

         重新运行App,点击第二个按钮之后的界面如图所示,可见当前页面的onClick方法也正确执行了。

完整代码如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity_button_click"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_click_single"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="指定单独的点击监听器"
            android:textColor="#000000"
            android:textSize="15sp" />
        <Button
            android:id="@+id/btn_click_public"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="指定公共的点击监听器"
            android:textColor="#000000"
            android:textSize="15sp" />
    </LinearLayout>
    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="5dp"
        android:text="这里查看按钮的点击结果"
        android:textColor="#000000"
        android:textSize="15sp" />
</LinearLayout>
@SuppressLint("SimpleDateFormat")
public class DateUtil {
    // 获取当前的日期时间
    public static String getNowDateTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.format(new Date());
    }

    // 获取当前的时间
    public static String getNowTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(new Date());
    }

}
package com.example.myapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class activity_button_click extends AppCompatActivity implements View.OnClickListener {
    private TextView tv_result; //声明一个文本视图实例
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_button_click);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        tv_result = findViewById(R.id.tv_result); // 获取名叫tv_result的文本视图
        // 从布局文件中获取名叫btn_click_single的按钮控件
        Button btn_click_single = findViewById(R.id.btn_click_single);
        // 设置点击监听器,一旦用户点击按钮,就触发监听器的onClick方法
        btn_click_single.setOnClickListener(new MyOnClickListener());
        // 从布局文件中获取名叫btn_click_public的按钮控件
        Button btn_click_public = findViewById(R.id.btn_click_public);
        // 设置点击监听器,一旦用户点击按钮,就触发监听器的onClick方法
        btn_click_public.setOnClickListener(this);

    }

    // 定义一个点击监听器,它实现了接口View.OnClickListener
    class MyOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) { // 点击事件的处理方法
            String desc = String.format("%s 您点击了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc); // 设置文本视图的文本内容
        }
    }


    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_click_public) { // 来自按钮btn_click_public
            String desc = String.format("%s 您点击了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc); // 设置文本视图的文本内容
        }

    }
}

        除了点击事件外,Android还设计了另外一种长按事件,每当控件被按住超过500毫秒之后,就会触发该控件的长按事件。若要捕捉按钮的长按事件,可调用按钮对象的setOnLongClickListener方法设置长按监听器。具体的设置代码示例如下: 

        // 从布局文件中获取名叫btn_click_public的按钮控件
        Button btn_longclick_public = findViewById(R.id.btn_longclick_public);
        // 设置长按监听器,一旦用户长按按钮,就触发监听器的onLongClick方法
        btn_longclick_public.setOnLongClickListener(this);

        以上代码把长按监听器设置到当前页面,意味着该页面需要实现对应的长按接口View.OnLongClickListener,并重写长按方法onLongClick,下面便是重写后的onLongClick代码例子:

    @Override
    public boolean onLongClick(View v) {
        if (v.getId() == R.id.btn_longclick_public) { // 来自按钮btn_longclick_public
            String desc = String.format("%s 您长按了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc); // 设置文本视图的文本内容
        }
        return true;
    }

        再次运行App,长按按钮之后的界面如图所示,说明长按事件果然触发了onLongClick方法。

   

        值得注意的是,点击监听器和长按监听器不局限于按钮控件,其实它们都来自视图基类View,凡是从View派生而来的各类控件,均可注册点击监听器和长按监听器。譬如文本视图TextView,其对象也能调用setOnClickListener方法与setOnLongClickListener方法,此时TextView控件就会响应点击动作和长按动作。因为按钮存在按下和松开两种背景,便于提示用户该控件允许点击,但文本视图默认没有按压背景,不方便判断是否被点击,所以一般不会让文本视图处理点击事件和长按事件。

完整代码如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity_button_longclick"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_longclick_single"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="指定单独的长按监听器"
            android:textColor="#000000"
            android:textSize="15sp" />
        <Button
            android:id="@+id/btn_longclick_public"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="指定公共的长按监听器"
            android:textColor="#000000"
            android:textSize="15sp" />
    </LinearLayout>
    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="5dp"
        android:text="这里查看按钮的长按结果"
        android:textColor="#000000"
        android:textSize="15sp" />
</LinearLayout>
package com.example.myapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class activity_button_longclick extends AppCompatActivity implements View.OnLongClickListener {
    private TextView tv_result; // 声明一个文本视图实例
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_button_longclick);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        tv_result = findViewById(R.id.tv_result); // 获取名叫tv_result的文本视图
        // 从布局文件中获取名叫btn_click_single的按钮控件
        Button btn_longclick_single = findViewById(R.id.btn_longclick_single);
        // 设置长按监听器,一旦用户长按按钮,就触发监听器的onLongClick方法
        btn_longclick_single.setOnLongClickListener(new MyOnLongClickListener());
        // 从布局文件中获取名叫btn_click_public的按钮控件
        Button btn_longclick_public = findViewById(R.id.btn_longclick_public);
        // 设置长按监听器,一旦用户长按按钮,就触发监听器的onLongClick方法
        btn_longclick_public.setOnLongClickListener(this);

    }

    // 定义一个长按监听器,它实现了接口View.OnLongClickListener
    class MyOnLongClickListener implements View.OnLongClickListener {
        @Override
        public boolean onLongClick(View v) { // 长按事件的处理方法
            String desc = String.format("%s 您长按了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc); // 设置文本视图的文本内容
            return true;
        }
    }

    @Override
    public boolean onLongClick(View v) {
        if (v.getId() == R.id.btn_longclick_public) { // 来自按钮btn_longclick_public
            String desc = String.format("%s 您长按了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc); // 设置文本视图的文本内容
        }
        return true;
    }
}
1.4.3    禁用与恢复按钮

         尽管按钮控件生来就是给人点击的,可是某些情况仍然希望暂时禁止点击操作,譬如用户在注册的时候,有的网站要求用户必须同意指定条款,而且至少浏览10秒之后才能点击注册按钮。那么在10秒之前,注册按钮应当置灰且不能点击,等过了10秒之后,注册按钮才恢复正常。在这样的业务场景中,按钮先后拥有两种状态,即不可用状态与可用状态,它们在外观和功能上的区别如下:

        (1)不可用按钮:按钮不允许点击,即使点击也没反应,同时按钮文字为灰色。

        (2)可用按钮:按钮允许点击,点击按钮会触发点击事件,同时按钮文字为正常的黑色。

        从上述的区别说明可知,不可用与可用状态主要有两点差异:其一,是否允许点击;其二,按钮文字的颜色。就文字颜色而言,可在布局文件中使用textColor属性设置颜色,也可在Java代码中调用setTextColor方法设置颜色。至于是否允许点击,则需引入新属性android:enabled,该属性值为true时表示启用按钮,即允许点击按钮;该属性值为false时表示禁用按钮,即不允许点击按钮。在Java代码中,则可通过setEnabled方法设置按钮的可用状态(true表示启用,false表示禁用)。

        接下来通过一个例子演示按钮的启用和禁用操作。为了改变测试按钮的可用状态,需要额外添加两个控制按钮,分别是“启用测试按钮”和“禁用测试按钮”,加起来一共3个按钮控件,注意“测试按钮”默认是灰色文本。“测试按钮”尚未启用时的界面效果如图所示。

        与图对应的布局文件内容如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity_button_enable">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_enable"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="启用测试按钮" />
        <Button
            android:id="@+id/btn_disable"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="禁用测试按钮" />
    </LinearLayout>
    <Button
        android:id="@+id/btn_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:enabled="false"
        android:text="测试按钮" />
    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="5dp"
        android:text="这里查看测试按钮的点击结果" />
</LinearLayout>

         然后再Java代码中给3个按钮分别注册点击监听器,注册代码如下:

        // 因为按钮控件的setOnClickListener方法来自View基类,所以也可对findViewById得到的视图直接设置点击监听器
        findViewById(R.id.btn_enable).setOnClickListener(this);
        findViewById(R.id.btn_disable).setOnClickListener(this);
        btn_test = findViewById(R.id.btn_test); // 获取名叫btn_test的按钮控件
        btn_test.setOnClickListener(this); // 设置btn_test的点击监听器

        同时重写页面的onClick方法,分别处理3个按钮的点击事件,修改之后的onClick代码示例如下:

    @Override
    public void onClick(View v) {
        // 由于多个控件都把点击监听器设置到了当前页面,因此公共的onClick方法内部需要区分来自哪个按钮
        if (v.getId() == R.id.btn_enable){// 点击了按钮“启用测试按钮”
            btn_test.setTextColor(Color.BLACK);// 设置按钮的文字颜色
            btn_test.setEnabled(true);// 启用当前控件
        } else if (v.getId() == R.id.btn_disable) {// 点击了按钮“禁用测试按钮”
            btn_test.setTextColor(Color.GRAY); // 设置按钮的文字颜色
            btn_test.setEnabled(false);// 禁用当前控件
        } else if (v.getId() == R.id.btn_test) {
            String desc = String.format("%s 您点击了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc);// 设置文本视图的文本内容
        }
    }

完整代码如下:

@SuppressLint("SimpleDateFormat")
public class DateUtil {
    // 获取当前的日期时间
    public static String getNowDateTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.format(new Date());
    }

    // 获取当前的时间
    public static String getNowTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(new Date());
    }

}
package com.example.myapplication;

import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class activity_button_enable extends AppCompatActivity implements View.OnClickListener {
    private TextView tv_result; //声明一个文本视图实例
    private Button btn_test; //声明一个按钮控件实例
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_button_enable);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        tv_result = findViewById(R.id.tv_result); //获取名叫tv_result的文本视图
        // 因为按钮控件的setOnClickListener方法来自View基类,所以也可对findViewById得到的视图直接设置点击监听器
        findViewById(R.id.btn_enable).setOnClickListener(this);
        findViewById(R.id.btn_disable).setOnClickListener(this);
        btn_test = findViewById(R.id.btn_test); // 获取名叫btn_test的按钮控件
        btn_test.setOnClickListener(this); // 设置btn_test的点击监听器
    }

    @Override
    public void onClick(View v) {
        // 由于多个控件都把点击监听器设置到了当前页面,因此公共的onClick方法内部需要区分来自哪个按钮
        if (v.getId() == R.id.btn_enable){// 点击了按钮“启用测试按钮”
            btn_test.setTextColor(Color.BLACK);// 设置按钮的文字颜色
            btn_test.setEnabled(true);// 启用当前控件
        } else if (v.getId() == R.id.btn_disable) {// 点击了按钮“禁用测试按钮”
            btn_test.setTextColor(Color.GRAY); // 设置按钮的文字颜色
            btn_test.setEnabled(false);// 禁用当前控件
        } else if (v.getId() == R.id.btn_test) {
            String desc = String.format("%s 您点击了按钮:%s",
                    DateUtil.getNowTime(), ((Button) v).getText());
            tv_result.setText(desc);// 设置文本视图的文本内容
        }
    }
}

        最后编译运行App,点击了“启用测试按钮”之后,原本置灰的测试按钮btn_test恢复正常的黑色文本,点击该按钮发现界面有了反应,具体效果如图所示。

       对比两图,观察按钮启用前后的外观及其是否响应点击动作,即可知晓禁用按钮和启用按钮两种模式的差别。

1.5    图像显示

        本节介绍与图像显示有关的几种控件及其用法,包括:专门用于显示图片的图像视图以及若干缩放类型的效果,支持显示图片的按钮控件--图像按钮,如何在按钮控件上同时显示文本和图标等。

1.5.1    图像视图  ImageView

        显示文本用到了文本视图TextView,显示图像则用到了图像视图ImageView。由于图像通常保存为单独的图片文件,因此需要先把图片放到res/drawable目录中,然后再去引用该图片的资源名称。比如先在有张名为apple.png的苹果图片,那么XML文件通过属性android:src设置图片资源,属性值格式形如“@drawabl/不含扩展名的图片名称”。添加了src属性的ImageView标签示例如下:

    <ImageView
        android:id="@+id/iv_scale"
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:layout_marginTop="5dp"
        android:src="@drawable/apple" />

         若想在Java代码中设置图像视图的图片资源,可调用ImageView控件的setImageResource方法,方法参数格式形如“R.drawable.不含扩展名的图片名称”。仍以上述的苹果图片为例,给图像视图设置图片资源的代码例子如下:

//  从布局文件中获取名为iv_scale的图像视图
ImageView iv_scale = findViewById(R.id.iv_scale);
iv_scale.setImageResource(R.drawable.apple);  //  设置图像视图的图片资源

        运行测试App,展示图片的界面效果如图所示。

 

        观察效果图发现苹果图片居中显示,而非像文本视图里的文字那样默认靠左显示,这时怎么回事?原来ImageView本身默认图片居中显示,不管图片有多大抑或有多小,图像视图都会自动缩放图片,使之刚好够着ImageView的边界,并且缩放后的图片保持原始的宽高比例,看起来图片很完美地占据了视图中央。这种缩放类型在XML文件中通过属性android:scaleType定义,即使图像视图未明确指定该属性,系统也会默认其值为fitCenter,表示让图像缩放后居中显示。添加了缩放属性的ImageView标签如下:

    <ImageView
        android:id="@+id/iv_scale"
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:layout_marginTop="5dp"
        android:src="@drawable/apple"
        android:scaleType="fitCenter" />

        在Java代码中可调用setScaleType方法设置图像视图的缩放类型,其中fitCenter对应的类型为ScaleType.FIT_CENTER,设置代码示例如下:

            // 将缩放类型设置为“保持宽高比例,缩放图片使其位于视图中间”
            iv_scale.setScaleType(ImageView.ScaleType.FIT_CENTER);

        除了居中显示,图像视图还提供了其他缩放类型,详细的缩放类型取值说明见表。

                                                            缩放类型的取值说明

XML中的缩放类型ScaleType类型中的缩放类型说明
fitCenterFIT_CENTER保持宽高比例,缩放图片使其位于视图中间
centerCropCENTER_CROP缩放图片使其充满视图(超出部分会被裁剪),并位于视图中间
centerInsideCENTER_INSIDE保持宽高比例,缩小图片使之位于视图中间(只缩小不放大)
centerCENTER保持图片原始尺寸,并使其位于视图中间
fitXYFIT_XY缩放图片使其正好填满视图(图片可能被拉伸变形)
fitStartFIT_START保持宽高比例,缩放图片使其位于视图上方或左侧
fitEndFIT_END保持宽高比例,缩放图片使其位于视图下方或右侧

        注意,fitCenter是默认的缩放类型 ,它的图像效果如之前的图所示。其余缩放类型的图像显示效果分别不同,具体完整代码如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity_image_scale">
    <ImageView
        android:id="@+id/iv_scale"
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:layout_marginTop="5dp"
        android:src="@drawable/apple" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_fitCenter"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="fitCenter"
            android:textColor="#000000"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_centerCrop"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="centerCrop"
            android:textColor="#000000"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_centerInside"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="centerInside"
            android:textColor="#000000"
            android:textSize="14sp" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_center"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="center"
            android:textColor="#000000"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_fitXY"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="fitXY"
            android:textColor="#000000"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_fitStart"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="fitStart"
            android:textColor="#000000"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_fitEnd"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="fitEnd"
            android:textColor="#000000"
            android:textSize="14sp" />

    </LinearLayout>

</LinearLayout>
package com.example.myapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class activity_image_scale extends AppCompatActivity implements View.OnClickListener {
    private ImageView iv_scale; // 声明一个图像视图的对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_image_scale);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        // 从布局文件中获取名叫iv_scale的图像视图
        iv_scale = findViewById(R.id.iv_scale);
        // 下面通过七个按钮,分别演示不同缩放类型的图片缩放效果
        findViewById(R.id.btn_center).setOnClickListener(this);
        findViewById(R.id.btn_fitCenter).setOnClickListener(this);
        findViewById(R.id.btn_centerCrop).setOnClickListener(this);
        findViewById(R.id.btn_centerInside).setOnClickListener(this);
        findViewById(R.id.btn_fitXY).setOnClickListener(this);
        findViewById(R.id.btn_fitStart).setOnClickListener(this);
        findViewById(R.id.btn_fitEnd).setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_center) {
            // 将缩放类型设置为“按照原尺寸居中显示”
            iv_scale.setScaleType(ImageView.ScaleType.CENTER);
        } else if (v.getId() == R.id.btn_fitCenter) {
            // 将缩放类型设置为“保持宽高比例,缩放图片使其位于视图中间”
            iv_scale.setScaleType(ImageView.ScaleType.FIT_CENTER);
        } else if (v.getId() == R.id.btn_centerCrop) {
            // 将缩放类型设置为“缩放图片使其充满视图,并位于视图中间”
            iv_scale.setScaleType(ImageView.ScaleType.CENTER_CROP);
        } else if (v.getId() == R.id.btn_centerInside) {
            // 将缩放类型设置为“保持宽高比例,缩小图片使之位于视图中间(只缩小不放大)”
            iv_scale.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        } else if (v.getId() == R.id.btn_fitXY) {
            // 将缩放类型设置为“缩放图片使其正好填满视图(图片可能被缩放变形)”
            iv_scale.setScaleType(ImageView.ScaleType.FIT_XY);
        } else if (v.getId() == R.id.btn_fitStart) {
            // 将缩放类型设置为“保持宽高比例,缩放图片使其位于视图上方或左侧”
            iv_scale.setScaleType(ImageView.ScaleType.FIT_START);
        } else if (v.getId() == R.id.btn_fitEnd) {
            // 将缩放类型设置为“保持宽高比例,缩放图片使其位于视图下方或右侧”
            iv_scale.setScaleType(ImageView.ScaleType.FIT_END);
        }
    }
}
1.5.2    图像按钮  ImageButton

        常见的按钮控件Button其实是文本按钮,因为按钮上面只能显示文字,不能显示图片,ImageButton才是显示图片的图像按钮。虽然ImageButton号称图像按钮,但它并非继承自Button,而是继承了ImageView。所以凡是ImageView拥有的属性和方法,ImageButton统统拿了过来,区别在于ImageButton有个按钮背景。

        尽管ImageButton源自ImageView,但它毕竟是个按钮,按钮家族常用的点击事件和长按事件,ImageButton全都没落下。不过ImageButton和Button之间除了名称不同外,还有下列差异:

        ●  Button既可显示文本也可显示图片(通过setBackgroundResource方法设置背景图片),而ImageButton只能显示图片不能显示文本。

        ●  ImageButton上的图像可按比例缩放,而Button通过背景设置的图像会拉伸变形,因为背景图采取fitXY方式,无法按比例缩放。

        ●  Button只能靠背景显示一张图片,而ImageButton可分别在前景和背景显示图片,从而实现两张图片叠加的效果。

        从上面可以看出,Button与ImageButton各有千秋,通常情况使用Button就够用了。但在某些场合,比如输入法打不出来的字符,以及特殊字体显示的字符串,就适合先切图再放到ImageButton。举个例子,数学常见的开方运算,由输入法打出来的运算符为“√”,但该符号缺少右上角的一横,正确的开方符号是带横线的√▔,此时便需要通过ImageButton显示这个开方图片。

        不过使用ImageButton得注意,图像按钮默认的缩放类型为center(保持原始尺寸不缩放图片),而非图像视图默认的fitCenter,倘若图片尺寸较大,那么图像按钮将无法显示整个图片。为避免显示不完整的情况,XML文件中的ImageButton标签必须指定fitCenter的缩放类型,详细的标签内容示例如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity_image_button">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:src="@drawable/sqrt"
        android:scaleType="fitCenter" />
</LinearLayout>

         运行测试App,打开的演示界面如图所示,可见图像按钮正确展示了开方符号。

1.5.3    同时展示文本与图像 

        现在有了Button可在按钮上显示文字,又有ImageButton可在按钮上显示图像,照理说绝大多数场合都够用了。然而现实项目中的需求往往捉摸不定,例如客户要求在按钮文字的左边加一个图标,这样按钮内部既有文字又有图片,乍看之下Button和ImageButton都没法直接使用。若用LinearLayout对ImageView和TextView组合布局,虽然可行,但XML文件却变得冗长许多。

        其实有个既简单又灵活的办法,要想在文字周围放置图片,使用Button就能实现。Button提供了几个与图标有关的属性,通过这些属性即可指定文字旁边的图标。相关的图标属性说明如下:

        ●  drawableTop:指定文字上方的图片。

        ●  drawableBottom:指定文字下方的图片。

        ●  drawableLeft:指定文字左边的图片。

        ●  drawableRight:指定文字右边的图片。

        ●  drawablePadding:指定图片与文字的间距。

        譬如下面是个既有文字又有图标的Button标签例子:

     <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_gravity="center"
        android:drawableTop="@drawable/ic_about"
        android:drawablePadding="5dp"
        android:text="图标在上"
        android:textColor="#000000"
        android:textSize="17sp" />

         以上的Button标签通过属性android:drawableTop设置了文字上边的图标,若想变更图标所处的位置,只要把drawableTop换成对应方向的属性即可。各方向的图文混排按钮效果分别如图所示。

 

1.6    实战项目:计算器 

        本章虽然只学了一些Android的简单控件,但是只要活学善用这些布局和控件,也能够做出实用的App。接下来让我们尝试设计并实现一个简单计算器。

1.6.1    需求描述

        计算器是人们日常生活中最常用的工具之一,无论是在计算机上还是在手机上,都少不了计算器的身影。以Windows系统自带的计算器为例,它的界面简洁且十分实用,如图所示。

        计算器的界面分为两大部分:第一部分是上方的计算表达式,既包括用户的按键输入,也包括计算结果数字;第二部分是下方的各个按键,包括从0到9的数字按键、加减乘除与等号、正负号按键、小数点按键、求倒数按键、平方按键、开方按键,以及退格、清空、取消等控制按键。通过这些按键操作,能够实现整数和小数的四则运算,以及求倒数、求平方、求开方等简单运算。

1.6.2    界面设计

        上一小节介绍的Windows计算器,它主要由上半部分的计算结果与下半部分的计算按键两块区域组成,据此可创建一个界面相似的计算器App,同样由计算结果和计算按键两部分组成,如图所示。

        按照计算器App的效果图,大致分布着下列Android控件:

        ●  线性布局LinearLayout:因为计算器界面整体从上往下布局,所以需要垂直方向的LinearLayout。

        ●  网格布局GridLayout:计算器下半部分的几排按键,正好成五行四列表格分布,适合采用GridLayout。

        ●  滚动视图ScrollView:虽然计算器界面不宽也不高,但是以防万一,最好还是加个垂直方向的ScrollView。

        ●  文本视图TextView:很明显顶部标题 “简单计算器” 就是TextView,且文字居中显示;标题下面的计算结果也需要使用TextView,且文字靠右靠下显示。

        ●  按钮Button:几乎所有的数字与运算符按键都采用了Button控件。

        ●  图像按钮ImageButton:开方的运算符 “√” 虽然能够打出来,但是右上角少了数学课上的一横,所以该按钮要显示一张标准的开方符号图片,需要用到ImageButton。

1.6.3    关键代码

        App在同用户交互的过程中,时常要向用户反馈一些信息,例如:点错了按钮、输入了非法字符,诸如此类。对于这些一句话的提示,Android设计了Toast控件,用于展示短暂的提示文字。Toast的用法很简单,只需以下一行代码即可弹出提示窗口:

Toast.makeText(MainActivity.this,"提示文字",Toast.LENGTH_SHORT).show();

        上面代码用到了两个方法,分别是makeText和show,其中show方法用来展示提示窗口,而makeText方法用来构建提示文字的模版。makeText的第一个参数为当前页面的实例,倘若当前页面名为MainActivity的话,这里就填MainActivity.this,当然如果不引发歧义的话,直接填this也可以;第二个参数为准备显示的提示文本;第三个参数规定了提示窗的驻留时长,为Toast.LENGTH_SHORT表示停留2秒后消失,为Toast.LENGTH_LONG表示停留3.5秒后消失。

        对于计算器来说,有好几种情况需要提示用户,比如“除数不能为零” “开方的数值不能小于零” “不能对零求倒数” 等,这时就能通过Toast控件弹窗提醒用户。Toast弹窗的展示效果如图所示,此时App发现了除数为零的情况。

        对于 简单计算来说,每次运算至少需要两个操作数,比如加减乘除四则运算就要求有两个操作数,求倒数、求平方、求开方只要求一个操作数;并且每次运算过程有且仅有一个运算符(等号不计在内),故而计算器App得事先声明下列几个字符串变量:

private String operator = "";    //运算符
private String firstNum = "";    //第一个操作数
private String secondNum = "";    //第二个操作数
private String result = "";    //当前的计算结果

        用户在计算器界面每输入 一个按键,App都要进行下列两项操作:

        1.  输入按键的合法性校验

        在开展计算之前,务必检查用户输入的按键是否合法,因为非法按键将导致不能正常运算。合法的按键输入包括但不限于下列情况:

        (1)除数不能为零。

        (2)开方的数值不能小于零。

        (3)不能对零求倒数。

        (4)一个数字不能有两个小数点。

        (5)如果没输入运算符,就不能点击等号按钮。

        (6)如果没输入操作符,也不能点击等号按钮。

        比如点击等号按钮之时,App的逻辑校验代码示例如下:

        if (v.getId() == R.id.btn_equal) { // 点击了等号按钮
            if (operator.equals("")) { // 无运算符
                Toast.makeText(this, "请输入运算符", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (firstNum.equals("") || secondNum.equals("")) { // 无操作数
                Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (operator.equals("÷") && Double.parseDouble(secondNum) == 0) { // 除数为零
                Toast.makeText(this, "除数不能为零", Toast.LENGTH_SHORT).show();
                return false;
            }
        }
        2.  执行运算并显示计算结果 

        合法性校验通过,方能继续接下来的业务逻辑,倘若用户本次未输入与计算有关的按钮(例如等号、求倒数、求平方、求开方),则计算器只需拼接操作数或者运算符;倘若用户本次输入了与计算有关的按钮(例如等号、求倒数、求平方、求开方),则计算器立即执行运算操作并显示计算结果。以加减乘除四则运算为例,它们的计算代码例子如下:

    // 加减乘除四则运算,返回计算结果
    private double calculateFour() {
        double calculate_result = 0;
        if (operator.equals("+")) { // 当前是相加运算
            calculate_result = Double.parseDouble(firstNum) + Double.parseDouble(secondNum);
        } else if (operator.equals("-")) { // 当前是相减运算
            calculate_result = Double.parseDouble(firstNum) - Double.parseDouble(secondNum);
        } else if (operator.equals("×")) { // 当前是相乘运算
            calculate_result = Double.parseDouble(firstNum) * Double.parseDouble(secondNum);
        } else if (operator.equals("÷")) { // 当前是相除运算
            calculate_result = Double.parseDouble(firstNum) / Double.parseDouble(secondNum);
        }
        Log.d(TAG, "calculate_result=" + calculate_result); // 把运算结果打印到日志中
        return calculate_result;
    }

        完成合法性校验与运算处理之后,计算器App的编码基本结束了。运算计算器App,执行各种运算的界面效果如两图所示。其中图一为执行乘法运算8*9=?的计算器界面,图二为先对8做开方再给开方结果加上60的计算器界面。

 

完整代码如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#eeeeee"
    android:padding="5dp"
    tools:context=".activity_calculator">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:layout_width="360dp"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="简单计算器"
                android:textColor="#000000"
                android:textSize="20sp" />
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <TextView
                    android:id="@+id/tv_result"
                    android:layout_width="360dp"
                    android:layout_height="wrap_content"
                    android:background="#ffffff"
                    android:gravity="right|bottom"
                    android:lines="3"
                    android:maxLines="3"
                    android:scrollbars="vertical"
                    android:text="0"
                    android:textColor="#000000"
                    android:textSize="25sp" />
            </LinearLayout>
            <GridLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:columnCount="4">
                <Button
                    android:id="@+id/btn_cancel"
                    android:width="90dp"
                    android:height="75dp"
                    android:gravity="center"
                    android:text="CE"
                    android:textColor="@color/black"
                    android:textSize="30sp" />
                <Button
                    android:id="@+id/btn_divide"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="÷"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_multiply"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="×"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_clear"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="C"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_seven"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="7"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_eight"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="8"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_nine"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="9"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_plus"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="+"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_four"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="4"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_five"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="5"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_six"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="6"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_minus"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="-"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_one"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="1"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_two"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="2"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_three"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="3"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <ImageButton
                    android:id="@+id/ib_sqrt"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:scaleType="centerInside"
                    android:src="@drawable/sqrt" />

                <Button
                    android:id="@+id/btn_reciprocal"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="1/x"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_zero"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="0"
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_dot"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="."
                    android:textColor="@color/black"
                    android:textSize="30sp" />

                <Button
                    android:id="@+id/btn_equal"
                    android:layout_width="90dp"
                    android:layout_height="75dp"
                    android:gravity="center"
                    android:text="="
                    android:textColor="@color/black"
                    android:textSize="30sp" />
            </GridLayout>
        </LinearLayout>
    </ScrollView>
</LinearLayout>
package com.example.myapplication;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class activity_calculator extends AppCompatActivity implements View.OnClickListener {
    private final static String TAG = "CalculatorActivity";
    private TextView tv_result; // 声明一个文本视图对象
    private String operator = ""; // 运算符
    private String firstNum = ""; // 第一个操作数
    private String secondNum = ""; // 第二个操作数
    private String result = ""; // 当前的计算结果
    private String showText = ""; // 显示的文本内容

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_calculator);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        // 从布局文件中获取名叫tv_result的文本视图
        tv_result = findViewById(R.id.tv_result);
        // 下面给每个按钮控件都注册了点击监听器
        findViewById(R.id.btn_cancel).setOnClickListener(this); // “取消”按钮
        findViewById(R.id.btn_divide).setOnClickListener(this); // “除法”按钮
        findViewById(R.id.btn_multiply).setOnClickListener(this); // “乘法”按钮
        findViewById(R.id.btn_clear).setOnClickListener(this); // “清除”按钮
        findViewById(R.id.btn_seven).setOnClickListener(this); // 数字7
        findViewById(R.id.btn_eight).setOnClickListener(this); // 数字8
        findViewById(R.id.btn_nine).setOnClickListener(this); // 数字9
        findViewById(R.id.btn_plus).setOnClickListener(this); // “加法”按钮
        findViewById(R.id.btn_four).setOnClickListener(this); // 数字4
        findViewById(R.id.btn_five).setOnClickListener(this); // 数字5
        findViewById(R.id.btn_six).setOnClickListener(this); // 数字6
        findViewById(R.id.btn_minus).setOnClickListener(this); // “减法”按钮
        findViewById(R.id.btn_one).setOnClickListener(this); // 数字1
        findViewById(R.id.btn_two).setOnClickListener(this); // 数字2
        findViewById(R.id.btn_three).setOnClickListener(this); // 数字3
        findViewById(R.id.btn_reciprocal).setOnClickListener(this); // 求倒数按钮
        findViewById(R.id.btn_zero).setOnClickListener(this); // 数字0
        findViewById(R.id.btn_dot).setOnClickListener(this); // “小数点”按钮
        findViewById(R.id.btn_equal).setOnClickListener(this); // “等号”按钮
        findViewById(R.id.ib_sqrt).setOnClickListener(this); // “开平方”按钮
    }

    private boolean verify(View v) {
        if (v.getId() == R.id.btn_cancel) { // 点击了取消按钮
            if (operator.equals("") && (firstNum.equals("") || firstNum.equals("0"))) { // 无运算符,则表示逐位取消第一个操作数
                Toast.makeText(this, "没有可取消的数字了", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (!operator.equals("") && secondNum.equals("")) { // 有运算符,则表示逐位取消第二个操作数
                Toast.makeText(this, "没有可取消的数字了", Toast.LENGTH_SHORT).show();
                return false;
            }
        } else if (v.getId() == R.id.btn_equal) { // 点击了等号按钮
            if (operator.equals("")) { // 无运算符
                Toast.makeText(this, "请输入运算符", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (firstNum.equals("") || secondNum.equals("")) { // 无操作数
                Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (operator.equals("÷") && Double.parseDouble(secondNum) == 0) { // 除数为零
                Toast.makeText(this, "除数不能为零", Toast.LENGTH_SHORT).show();
                return false;
            }
        } else if (v.getId() == R.id.btn_plus || v.getId() == R.id.btn_minus // 点击了加、减、乘、除按钮
                || v.getId() == R.id.btn_multiply || v.getId() == R.id.btn_divide) {
            if (firstNum.equals("")) { // 缺少第一个操作数
                Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (!operator.equals("")) { // 已有运算符
                Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                return false;
            }
        } else if (v.getId() == R.id.ib_sqrt) { // 点击了开根号按钮
            if (firstNum.equals("")) { // 缺少底数
                Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (Double.parseDouble(firstNum) < 0) { // 不能对负数开平方
                Toast.makeText(this, "开根号的数值不能小于零", Toast.LENGTH_SHORT).show();
                return false;
            }
        } else if (v.getId() == R.id.btn_reciprocal) { // 点击了求倒数按钮
            if (firstNum.equals("")) { // 缺少底数
                Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (Double.parseDouble(firstNum) == 0) { // 不能对零求倒数
                Toast.makeText(this, "不能对零求倒数", Toast.LENGTH_SHORT).show();
                return false;
            }
        } else if (v.getId() == R.id.btn_dot) { // 点击了小数点
            if (operator.equals("") && firstNum.contains(".")) { // 无运算符,则检查第一个操作数是否已有小数点
                Toast.makeText(this, "一个数字不能有两个小数点", Toast.LENGTH_SHORT).show();
                return false;
            }
            if (!operator.equals("") && secondNum.contains(".")) { // 有运算符,则检查第二个操作数是否已有小数点
                Toast.makeText(this, "一个数字不能有两个小数点", Toast.LENGTH_SHORT).show();
                return false;
            }
        }
        return true;
    }


    @Override
    public void onClick(View v) {
        if (!verify(v)) { // 未通过合法性校验,直接返回
            return;
        }
        String inputText;
        if (v.getId() == R.id.ib_sqrt) { // 如果是开根号按钮
            inputText = "√";
        } else { // 除了开根号之外的其他按钮
            inputText = ((TextView) v).getText().toString();
        }
        Log.d(TAG, "inputText=" + inputText);
        if (v.getId() == R.id.btn_clear) { // 点击了清除按钮
            clear();
        } else if (v.getId() == R.id.btn_cancel) { // 点击了取消按钮
            if (operator.equals("")) { // 无运算符,则表示逐位取消第一个操作数
                if (firstNum.length() == 1) {
                    firstNum = "0";
                } else if (firstNum.length() > 1) {
                    firstNum = firstNum.substring(0, firstNum.length() - 1);
                }
                refreshText(firstNum);
            } else { // 有运算符,则表示逐位取消第二个操作数
                if (secondNum.length() == 1) {
                    secondNum = "";
                } else if (secondNum.length() > 1) {
                    secondNum = secondNum.substring(0, secondNum.length() - 1);
                }
                refreshText(showText.substring(0, showText.length() - 1));
            }
        } else if (v.getId() == R.id.btn_plus || v.getId() == R.id.btn_minus // 点击了加、减、乘、除按钮
                || v.getId() == R.id.btn_multiply || v.getId() == R.id.btn_divide) {
            operator = inputText; // 运算符
            refreshText(showText + operator);
        } else if (v.getId() == R.id.btn_equal) { // 点击了等号按钮
            double calculate_result = calculateFour(); // 加减乘除四则运算
            refreshOperate(String.valueOf(calculate_result));
            refreshText(showText + "=" + result);
        } else if (v.getId() == R.id.ib_sqrt) { // 点击了开根号按钮
            double calculate_result = Math.sqrt(Double.parseDouble(firstNum)); // 开平方运算
            refreshOperate(String.valueOf(calculate_result));
            refreshText(showText + "√=" + result);
        } else if (v.getId() == R.id.btn_reciprocal) { // 点击了求倒数按钮
            double calculate_result = 1.0 / Double.parseDouble(firstNum); // 求倒数运算
            refreshOperate(String.valueOf(calculate_result));
            refreshText(showText + "/=" + result);
        } else { // 点击了其他按钮,包括数字和小数点
            if (result.length() > 0 && operator.equals("")) { // 上次的运算结果已经出来了
                clear();
            }
            if (operator.equals("")) { // 无运算符,则继续拼接第一个操作数
                firstNum = firstNum+inputText;
            } else { // 有运算符,则继续拼接第二个操作数
                secondNum = secondNum + inputText;
            }
            if (showText.equals("0") && !inputText.equals(".")) { // 整数不需要前面的0
                refreshText(inputText);
            } else {
                refreshText(showText + inputText);
            }
        }
    }

    // 刷新运算结果
    private void refreshOperate(String new_result) {
        result = new_result;
        firstNum = result;
        secondNum = "";
        operator = "";
    }

    // 刷新文本显示
    private void refreshText(String text) {
        showText = text;
        tv_result.setText(showText);
    }

    // 清空并初始化
    private void clear() {
        refreshOperate("");
        refreshText("");
    }

    // 加减乘除四则运算,返回计算结果
    private double calculateFour() {
        double calculate_result = 0;
        if (operator.equals("+")) { // 当前是相加运算
            calculate_result = Double.parseDouble(firstNum) + Double.parseDouble(secondNum);
        } else if (operator.equals("-")) { // 当前是相减运算
            calculate_result = Double.parseDouble(firstNum) - Double.parseDouble(secondNum);
        } else if (operator.equals("×")) { // 当前是相乘运算
            calculate_result = Double.parseDouble(firstNum) * Double.parseDouble(secondNum);
        } else if (operator.equals("÷")) { // 当前是相除运算
            calculate_result = Double.parseDouble(firstNum) / Double.parseDouble(secondNum);
        }
        Log.d(TAG, "calculate_result=" + calculate_result); // 把运算结果打印到日志中
        return calculate_result;
    }


}

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值