安装安卓 谷歌开发者的中文网站可以直接下载Android Studio,详细的下载页面是https://developer.android.google.cn/studio/index.html。
安卓应用的目录结构
App工程分为两个层次,第一个层次是项目,另一个层次是模块
模块依附于项目,每个项目至少有一个模块,也能拥有多个模块
一般所言的”编译运行App”,指的是运行某个模块,而非运行某个项目,因为模块才对应实际的App
App项目的目录说明
App项目下面有两个分类:app(代表app模块)、Gradle Scripts。
app下面有3个子目录,Gradle Scripts下面主要是工程的编译配置文件
项目下面有两个分类:一个是app(代表app模块);另一个是Gradle Scripts。其中,app下面又有3个子目录,其功能说明如下:
(1)manifests子目录,下面只有一个XML文件,即AndroidManifest.xml,它是App的运行配置文
件。
(2)java子目录,下面有3个com.example.myapp包,其中第一个包存放当前模块的Java源代码,后
面两个包存放测试用的Java代码。
(3)res子目录,存放当前模块的资源文件。res下面又有4个子目录:
drawable目录存放图形描述文件与图片文件。
layout目录存放App页面的布局文件。
mipmap目录存放App的启动图标(即手机软件的启动图标) 。
values目录存放一些常量定义文件,例如字符串常量strings.xml、像素常量dimens.xml、颜色常
量colors.xml、样式风格定义styles.xml等。
Gradle Scripts下面主要是工程的编译配置文件,主要有:
(1)build.gradle,该文件分为项目级与模块级两种,用于描述App工程的编译规则。
(2)proguard-rules.pro,该文件用于描述Java代码的混淆规则。
(3)gradle.properties,该文件用于配置编译工程的命令行参数,一般无须改动。
(4)settings.gradle,该文件配置了需要编译哪些模块。初始内容为include ‘:app’,表示只编译app模
块。
(5)local.properties,项目的本地配置文件,它在工程编译时自动生成,用于描述开发者电脑的环境
配置,包括SDK的本地路径、NDK的本地路径等。
编译配置文件build.gradle 新创建的App项目默认有两个build.gradle,一个是Project项目级别的build.gradle;另一个是Module
模块级别的build.gradle。
项目级别的build.gradle指定了当前项目的总体编译规则,打开该文件在buildscript下面找到
repositories和dependencies两个节点,其中repositories节点用于设置Android Studio插件的网络仓
库地址,而dependencies节点用于设置gradle插件的版本号。由于官方的谷歌仓库位于国外,下载速度
相对较慢,因此可在repositories节点添加阿里云的仓库地址,方便国内开发者下载相关插件。修改之后
的buildscript节点内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 buildscript { repositories { // 以下四行添加阿里云的仓库地址,方便国内开发者下载相关插件 maven { url 'https://maven.aliyun.com/repository/jcenter' } maven { url 'https://maven.aliyun.com/repository/google'} maven { url 'https://maven.aliyun.com/repository/gradle-plugin'} maven { url 'https://maven.aliyun.com/repository/public'} google() jcenter() } dependencies { // 配置gradle插件版本,下面的版本号就是Android Studio的版本号 classpath 'com.android.tools.build:gradle:4.1.0' } }
模块级别的build.gradle对应于具体模块,每个模块都有自己的build.gradle,它指定了当前模块的详细
编译规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { applicationId "com.example.chapter02" minSdkVersion 19 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt' ), 'proguard-rules.pro' } } } dependencies { implementation fileTree (dir: 'libs' , include : ['*.jar' ]) implementation 'androidx.appcompat:appcompat:1.2.0' testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }
清单文件 每个应用的根目录中都必须包含一个AndroidManifest.xml,并且文件名必须一模一样。
这个文件中包含了App的配置信息,系统需要根据里面的内容运行app的代码,显示界面。
AndroidManifest.xml指定了App的运行配置信息,它是一个XML描述文件,初始内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.chapter02"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".Main2Activity" /> <!-- activity节点指定了该App拥有的活动页面信息,其中拥有 android.intent.action.MAIN的activity说明它是入口页面 --> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
可见AndroidManifest.xml的根节点为manifest,它的package属性指定了该App的包名。manifest下
面有个application节点,它的各属性说明如下:
android:allowBackup,是否允许应用备份。允许用户备份系统应用和第三方应用的apk安装包和
应用数据,以便在刷机或者数据丢失后恢复应用,用户即可通过adb backup和adb restore来进行
对应用数据的备份和恢复。为true表示允许,为false则表示不允许。
android:icon,指定App在手机屏幕上显示的图标。
android:label,指定App在手机屏幕上显示的名称。
android:roundIcon,指定App的圆角图标。
android:supportsRtl,是否支持阿拉伯语/波斯语这种从右往左的文字排列顺序。为true表示支
持,为false则表示不支持。
android:theme,指定App的显示风格。
注意到application下面还有个activity节点,它是活动页面的注册声明,只有在AndroidManifest.xml中
正确配置了activity节点,才能在运行时访问对应的活动页面。初始配置的MainActivity正是App的默认
主页,之所以说该页面是App主页,是因为它的activity节点内部还配置了以下的过滤信息:
1 2 3 4 <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
其中action节点设置的android.intent.action.MAIN表示该页面是App的入口页面,启动App时会最先打
开该页面。而category节点设置的android.intent.category.LAUNCHER决定了是否在手机屏幕上显示
App图标,如果同时有两个activity节点内部都设置了android.intent.category.LAUNCHER,那么桌面就
会显示两个App图标。
安卓基础 页面显示和逻辑处理 利用XML标记描绘应用界面,使用Java代码书写程序逻辑。
把App的界面设计与代码逻辑分开的好处:
使用XML文件描述APP界面,可以很方便的在Android Studio上预览界面效果
一个界面布局可以被多处代码复用,反过来,一个Java代码也可能适配多个界面布局
Activity的创建和跳转 完整的页面创建过程包括三个步骤:
在layout目录下创建XML文件
创建与XML文件对应的Java代码
在AndroidManifest.xml中注册页面配置
简单控件
设置文本
设置文本内容有两种方式
在XML文件中通过属性android:text设置文本
在Java代码中调用文本视图对象的setText方法设置文本
在XML中设置文本
TextView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="vertical" android:layout_width ="match_parent" android:layout_height ="match_parent" > <TextView android:id ="@+id/tv_hello" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="hello world!" /> </LinearLayout >
在Java代码中调用文本视图对象设置文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import android.os.Bundle;import android.os.PersistableBundle;import android.widget.TextView;import androidx.annotation.Nullable;import androidx.appcompat.app.AppCompatActivity;public class TextViewActivity extends AppCompatActivity { @Override public void onCreate (@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { super .onCreate(savedInstanceState, persistentState); setContentView(R.layout.activity_text_view); TextView textView = findViewById(R.id.tv_hello); textView.setText("你好,世界!" ); } }
在XML文件中通过属性android:text设置文本,这种方式中呢,可能会给你一个警告,说需要在一个xml文件中进行字符串的定义,这样会比较规整一些,可以定义也可以不定义,不影响情况
你定义后,可以通过R.string的方式来获取对应的字符串
设置文本大小
在Java代码中调用setTextSize方法,即可指定文本大小。
在XML文件中通过属性android:textSize指定文本大小,此时需要指定字号单位。
px:它是手机屏幕的最小显示单位,与设备的显示屏有关。
dp:它是与设备无关的显示单位,只与屏幕的尺寸有关。
sp:它专门用来设置字体大小,在系统设置中可以调整字体大小【此处会影响系统字体大小】。
演示一下如何使用
xml中的使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="vertical" android:layout_width ="match_parent" android:layout_height ="match_parent" > <TextView android:id ="@+id/tv_hello" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="hello world!" android:textSize ="30sp" /> </LinearLayout >
在Java中的使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import android.os.Bundle;import android.os.PersistableBundle;import android.widget.TextView;import androidx.annotation.Nullable;import androidx.appcompat.app.AppCompatActivity;public class TextViewActivity extends AppCompatActivity { @Override public void onCreate (@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { super .onCreate(savedInstanceState, persistentState); setContentView(R.layout.activity_text_view); TextView textView = findViewById(R.id.tv_hello); textView.setText("你好,世界!" ); textView.setTextSize(30 ); } }
设置文本的颜色 在Java代码中调用setTextColor方法即可设置文本颜色,具体色值可从Color类取。
Color类中的颜色类型
说明
Color类中的颜色类型
说明
BLACK
黑色
GREEN
绿色
DKGRAY
深灰
BLUE
蓝色
GRAY
灰色
YELLOW
黄色
LTGRAY
浅灰
CYAN
青色
WHITE
白色
MAGENTA
玫红
RED
红色
TRANSPARENT
透明
通过java代码设置对应的颜色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import androidx.appcompat.app.AppCompatActivity;import android.graphics.Color;import android.os.Bundle;import android.widget.TextView;public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_text_view); TextView textView = findViewById(R.id.tv_hello); textView.setText("你好,世界!" ); textView.setTextSize(40 ); textView.setTextColor(Color.GREEN); } }
在XML文件中通过属性android:textColor指定文本颜色,色值由透明度alpha和RGB三原色(红色red,绿色green、蓝色blue)联合定义。
色值由八位十六进制数与六位十六进制数两种表达方式,例如八位编码FFEEDDCC中,FF表示透明度,EE表示红色浓度,DD表示绿色浓度,CC表示蓝色浓度。
透明度为FF表示完全不透明,为00表示完全透明。RGB三色的数值越大,表示颜色越浓,也就越亮;数值越小,表示颜色越淡,也就越暗。
设置xml中的颜色
1 2 3 4 5 6 <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="this is example" android:textColor ="#ff00ff00" />
在java中也可以这样设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import androidx.appcompat.app.AppCompatActivity;import android.graphics.Color;import android.os.Bundle;import android.widget.TextView;public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_text_view); TextView textView = findViewById(R.id.tv_hello); textView.setText("你好,世界!" ); textView.setTextSize(40 ); textView.setTextColor(Color.GREEN); TextView textView1 = findViewById(R.id.tv_hello2); textView1.setTextColor(0xff6900 ); } }
在android studio中,有一些颜色已经被定义好了,可以直接使用,颜色在res/values/colors文件中
使用方式:
1 2 3 4 5 6 7 8 <TextView android:id ="@+id/tv_hello2" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="this is example" android:textColor ="@color/black" />
1 2 textView1.setBackgroundResource(R.color.black);
视图基础 设置视图的宽高
视图宽度通过属性android:layout_width表达,视图高度通过属性android:layout_height表达,宽高的取值主要有下列三种:
match_parent:表示与上级视图保持一致。
wrap_content:表示与内容自适应
以dp为单位的具体尺寸
1 2 3 4 5 6 7 8 9 10 11 <LinearLayout android:layout_width ="200px" android:layout_height ="wrap_content" > <TextView android:id ="@+id/tv_hello" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="hello world!" android:textSize ="30sp" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import androidx.appcompat.app.AppCompatActivity;import android.graphics.Color;import android.os.Bundle;import android.view.ViewGroup;import android.widget.TextView;import fun.eastwind.myapplication7.utils.Utils;public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_text_view); TextView textView = findViewById(R.id.tv_hello); textView.setText("你好,世界!" ); textView.setTextSize(40 ); textView.setTextColor(Color.GREEN); ViewGroup.LayoutParams layoutParams = textView.getLayoutParams(); layoutParams.width = Utils.dip2px(this ,300 ); textView.setLayoutParams(layoutParams); } }
这里默认是px作为单位,我们需要将px转为dp,编写一个工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 import android.content.Context;public class Utils { public static int dip2px (Context context,float dpVal) { float density = context.getResources().getDisplayMetrics().density; return (int ) (dpVal * density + 0.5f ); } }
设置视图的间距 设置视图的间距有两种方式:
采用layout_margin(外间距)属性,它指定了当前视图与周围平级视图之间的距离。包括layout_margin、layout_marginLeft、layout_marginTop、layout_marginRight、layout_marginBottom
采用padding(内间距)属性,它指定了当前视图与内部下级视图之间的距离。包括padding、paddingLeft、paddingTop、paddingRight、paddingBottom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="vertical" android:layout_width ="match_parent" android:layout_height ="match_parent" android:background ="#00AAFF" > <LinearLayout android:layout_width ="match_parent" android:background ="#FFFF99" android:layout_margin ="20dp" android:padding ="60dp" android:layout_height ="match_parent" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="match_parent" android:background ="#ff0000" /> </LinearLayout > </LinearLayout >
设置视图的对齐方式 设置视图的对齐方式有两种途径:
采用layout_gravity属性,它指定了当前视图相对于上级视图的对齐方式。
采用gravity属性,它指定了下级视图相对于当前视图的对齐方式。
layout_gravity与gravity的取值包括:left、top、right、bottom,还可以用竖线连接各取值,例如”left|top”表示既靠左又靠上,也就是左上角
以上图为例,当前布局与父容器的位置指定是通过layout_gravity,而让其指定子容器与当前容器的对齐方式是使用gravity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="horizontal" android:layout_width ="match_parent" android:layout_height ="match_parent" android:padding ="15dp" android:background ="#00AAFF" > <LinearLayout android:layout_width ="0dp" android:layout_gravity ="bottom" android:layout_weight ="1" android:padding ="10dp" android:background ="#E80E0E" android:gravity ="left" android:layout_height ="200dp" > <LinearLayout android:layout_width ="50dp" android:padding ="10dp" android:background ="#AE0EE8" android:layout_height ="50dp" > </LinearLayout > </LinearLayout > <LinearLayout android:padding ="10dp" android:layout_width ="0dp" android:layout_weight ="1" android:layout_gravity ="top" android:background ="#0EE83D" android:gravity ="right|bottom" android:layout_height ="200dp" > <LinearLayout android:layout_width ="50dp" android:padding ="10dp" android:background ="#AE0EE8" android:layout_height ="50dp" /> </LinearLayout > </LinearLayout >
结果图如下
常用布局 线性布局LinearLayout 线性布局内部的各视图有两种排列方式:
orientation属性值为horizontal时,内部视图在水平方向从左往右排列。
orientation属性值为vertical时,内部视图在垂直方向从上往下排列
如果不指定orientation属性,则LinearLayout默认水平方向排列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="one" android:textSize ="17sp" android:background ="#ff6900" /> <TextView android:textSize ="17sp" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:background ="#E91E63" android:text ="two" /> </LinearLayout > <LinearLayout android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="one" android:textSize ="17sp" android:background ="#ff6900" /> <TextView android:textSize ="17sp" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:background ="#E91E63" android:text ="two" /> </LinearLayout > </LinearLayout >
权重 线性布局的权重概念,指的是线性布局的下级视图各自拥有多大比例的宽高。
权重属性名叫layout_weight,但该属性不在LinearLayout节点设置,而在线性布局的直接下级视图设置,表示该下级视图占据的宽高比例。
layout_width填0dp时,layout_weight表示水平方向的宽度比例。
layout_height填0dp时,layout_weight表示垂直方向的高度比例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <TextView android:layout_width ="0dp" android:layout_weight ="1" android:textSize ="17sp" android:text ="hello" android:layout_height ="wrap_content" /> <TextView android:layout_width ="0dp" android:layout_weight ="1" android:textSize ="17sp" android:text ="hello" android:layout_height ="wrap_content" /> <TextView android:layout_width ="0dp" android:layout_weight ="1" android:textSize ="17sp" android:text ="hello" android:layout_height ="wrap_content" /> </LinearLayout > </LinearLayout >
相对布局RelativeLayout 相对布局的下级视图位置由其他视图决定。用于确定下级视图位置的参照物分两种:
与该视图自身平级的视图;
该视图的上级视图(也就是它归属的RelativeLayout)
如果不设定下级视图的参照物,那么下级视图默认显示在RelativeLayout内部的左上角
相对位置的取值
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <?xml version="1.0" encoding="utf-8" ?> <RelativeLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="150dp" tools:context =".MainActivity3" > <TextView android:layout_width ="wrap_content" android:text ="hello world!" android:layout_alignTop ="@id/centerForParent" android:textColor ="#ffffff" android:layout_centerVertical ="true" android:layout_toRightOf ="@id/centerForParent" android:background ="#ff6900" android:layout_height ="wrap_content" /> <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_alignParentLeft ="true" android:background ="#ff6900" android:text ="hello world!22222" android:textColor ="#ffffff" android:textSize ="17sp" /> <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_centerVertical ="true" android:background ="#ff6900" android:text ="hello world!22222" android:textColor ="#ffffff" android:textSize ="17sp" /> <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_centerHorizontal ="true" android:background ="#ff6900" android:text ="hello world!22222" android:textColor ="#ffffff" android:textSize ="17sp" /> <TextView android:id ="@+id/centerForParent" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_centerInParent ="true" android:background ="#ff6900" android:text ="hello world!" android:textColor ="#ffffff" android:textSize ="17sp" /> </RelativeLayout >
网格布局GridLayout 网格布局支持多行多列的表格排列。
网格布局默认从左到右、从上到下排列,它新增了两个属性:
columnCount属性,它指定了网格的列数,即每行能放多少个视图;
rowCount属性,它指定了网格的行数,即每列能放多少个视图;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?xml version="1.0" encoding="utf-8" ?> <GridLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:columnCount ="2" android:rowCount ="2" android:layout_height ="match_parent" > <TextView android:layout_height ="60dp" android:layout_width ="0dp" android:layout_columnWeight ="1" android:background ="#ffcccc" android:text ="浅红色" android:gravity ="center" android:textColor ="#000000" android:textSize ="17sp" /> <TextView android:layout_width ="0dp" android:layout_columnWeight ="1" android:layout_height ="60dp" android:background ="#ffaa00" android:text ="橙色" android:gravity ="center" android:textColor ="#000000" android:textSize ="17sp" /> <TextView android:layout_width ="0dp" android:layout_columnWeight ="1" android:layout_height ="60dp" android:background ="#ffcccc" android:text ="浅红色" android:gravity ="center" android:textColor ="#000000" android:textSize ="17sp" /> <TextView android:layout_width ="0dp" android:layout_columnWeight ="1" android:layout_height ="60dp" android:background ="#ffaa00" android:gravity ="center" android:text ="橙色" android:textColor ="#000000" android:textSize ="17sp" /> </GridLayout >
滚动视图有两种:
ScrollView,它是垂直方向的滚动视图;垂直方向滚动时,layout_width属性值设置为match_parent,layout_height属性值设置为wrap_content。
HorizontalScrollView,它是水平方向的滚动视图;水平方向滚动时,layout_width属性值设置为wrap_content,layout_height属性值设置为match_parent。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <HorizontalScrollView android:layout_width ="wrap_content" android:layout_height ="200dp" > <LinearLayout android:layout_width ="wrap_content" android:orientation ="horizontal" android:layout_height ="match_parent" > <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 ="wrap_content" android:orientation ="vertical" android:layout_height ="match_parent" > <View android:layout_width ="match_parent" android:layout_height ="400dp" android:background ="#8BC34A" /> <View android:layout_width ="match_parent" android:layout_height ="400dp" android:background ="#FDFD24" /> </LinearLayout > </ScrollView > </LinearLayout >
按钮触控 按钮控件Button由TextView派生而来,它们之间的区别有:
Button拥有默认的按钮背景,而TextView默认无背景;
Button的内部文本默认居中对齐,而TextView文本默认靠左对齐;
Button会默认将英文字母转为大写,而TextView保持原始的英文大小写;
与TextView相比,Button增加了两个新属性:
textAllCaps属性,它指定了是否将英文字母转为大写,如true是表示自动转为大写,为false表示不做大写转换
onClick属性,它用来接管用户的点击动作,指定了点击按钮时要触发哪个方法;
onClick方法已经过时了,但是依然可以被使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <TextView android:id ="@+id/show_time" android:layout_width ="match_parent" android:gravity ="center" android:text ="点击按钮后显示时间" android:textSize ="30sp" android:layout_height ="wrap_content" /> <TextView android:gravity ="center" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="下面的按钮英文默认大写" android:textColor ="@color/black" android:textSize ="17sp" /> <Button android:onClick ="doClick" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="Hello World点击后显示时间" /> <TextView android:gravity ="center" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="下面的按钮英文保持不变" android:textColor ="@color/black" android:textSize ="17sp" /> <Button android:textAllCaps ="true" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="Hello World" /> </LinearLayout >
这里的方法是在点击后获取当前的时间以及对应按钮的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package fun.eastwind.myapplication7;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;import java.text.SimpleDateFormat;import java.util.Date;public class MainActivity6 extends AppCompatActivity { private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main6); textView = findViewById(R.id.show_time); } public void doClick (View view) { textView.setText(String.format("%s you click for this button,this button" + " content is %s" ,getNowTime(), ((TextView)view).getText() )); } public String getNowTime () { SimpleDateFormat sdf = new SimpleDateFormat ("HH:mm:ss" ); return sdf.format(new Date ()); } }
这里有一个小技巧,如果你创建了多个activity,想单独查看一个activity时,你可以这样做
打开AndroidManifest.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?xml version="1.0" encoding="utf-8" ?> <manifest xmlns:android ="http://schemas.android.com/apk/res/android" > <application android:allowBackup ="true" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:roundIcon ="@mipmap/ic_launcher_round" android:supportsRtl ="true" android:theme ="@style/Theme.Lulu" > <activity android:name =".MainActivity5" android:exported ="false" /> <activity android:name =".MainActivity4" android:exported ="false" /> <activity android:name =".MainActivity3" android:exported ="false" /> <activity android:name =".MainActivity2" android:exported ="false" /> <activity android:name =".MainActivity6" android:exported ="true" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > </application > </manifest >
假设文件像里面这样繁杂,你可以找到你想修改的activity的名称,然后注释掉同名的内容,并将android:name修改为你想使用的activity即可
点击事件 监听器,意思是专门监听控件的动作行为。只有控件发生了指定的动作,监听器才会触发开关去执行对应的代码逻辑。
按钮控件有两种常用的监听器:
点击监听器,通过setOnClickListener方法设置。按钮被按住少于500毫秒时,会触发点击事件。
长按监听器,通过setOnLongClickListener方法设置。按钮被按住超过500毫秒时,就会触发长按事件。
示例代码
布局文件xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <Button android:id ="@+id/btn_click" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="指定单独的点击监听器" android:textColor ="#000000" android:textSize ="15sp" /> <TextView android:id ="@+id/tv_content" android:layout_width ="match_parent" android:padding ="5dp" android:gravity ="center" android:textColor ="#000000" android:textSize ="15sp" android:text ="点击后设置内容" android:layout_height ="wrap_content" /> </LinearLayout >
逻辑代码实现了对应的接口,并实现里面的方法后,通过方法进行编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class ButtonClickActivity extends AppCompatActivity { private Button button; private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_button_click); button = findViewById(R.id.btn_click); textView = findViewById(R.id.tv_content); button.setOnClickListener(new MyOnClickListener (textView)); } static class MyOnClickListener implements View .OnClickListener{ private final TextView textView; public MyOnClickListener (TextView textView) { this .textView = textView; } @Override public void onClick (View v) { textView.setText("you clicked this button" ); } } }
还有另一种情况,当需要设置点击事件的按钮太多时,需要进行太多的类创建,太过麻烦,这里提供另一种方法
在原先的视图上再次添加一个按钮
1 2 3 4 5 6 7 8 <Button android:id ="@+id/btn_public" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="指定公共的点击监听器" android:textColor ="#000000" android:textSize ="15sp" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class ButtonClickActivity extends AppCompatActivity implements View .OnClickListener { private Button button; private TextView textView; private Button button_public; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_button_click); button = findViewById(R.id.btn_click); textView = findViewById(R.id.tv_content); button.setOnClickListener(new MyOnClickListener (textView)); button_public = findViewById(R.id.btn_public); button_public.setOnClickListener(this ); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.btn_public: textView.setText("you clicked this button,but this button is public" ); break ; } } static class MyOnClickListener implements View .OnClickListener{ private final TextView textView; public MyOnClickListener (TextView textView) { this .textView = textView; } @Override public void onClick (View v) { textView.setText("you clicked this button" ); } } }
长按点击事件 该事件可以通过上述两种方法来实现,也可以通过下面这种方法来实现
设置长按的按钮
1 2 3 4 5 6 7 8 <Button android:id ="@+id/btn_long" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="指定长按的点击监听器" android:textColor ="#000000" android:textSize ="15sp" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class ButtonClickActivity extends AppCompatActivity implements View .OnClickListener { private Button button; private TextView textView; private Button button_public; private Button button_long; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_button_click); button = findViewById(R.id.btn_click); textView = findViewById(R.id.tv_content); button.setOnClickListener(new MyOnClickListener (textView)); button_public = findViewById(R.id.btn_public); button_public.setOnClickListener(this ); button_long = findViewById(R.id.btn_long); button_long.setOnLongClickListener((View.OnLongClickListener) v -> { textView.setText("you clicked this button,but this button is long time" ); return true ; }); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.btn_public: textView.setText("you clicked this button,but this button is public" ); break ; } } static class MyOnClickListener implements View .OnClickListener{ private final TextView textView; public MyOnClickListener (TextView textView) { this .textView = textView; } @Override public void onClick (View v) { textView.setText("you clicked this button" ); } } }
禁用和恢复按钮 在实际业务中,按钮通常拥有两种状态,即不可用状态与可用状态,它们在外观和功能上的区别如下:
不可用按钮:按钮不允许点击,即使点击也没反应,同时按钮文字为灰色;
可用按钮:按钮运行点击,点击按钮会触发点击事件,同时按钮文字为正常的黑色;
是否允许点击由enabled属性控制,属性值为true时表示允许点击,为false时表示不允许点击
xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity7" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <Button android:id ="@+id/enable" android:layout_weight ="1" android:layout_width ="0dp" android:text ="启用测试按钮" android:layout_height ="wrap_content" android:textSize ="17sp" /> <Button android:id ="@+id/disable" android:layout_weight ="1" android:layout_width ="0dp" android:text ="禁用测试按钮" android:layout_height ="wrap_content" android:textSize ="17sp" /> </LinearLayout > <Button android:enabled ="false" android:layout_width ="match_parent" android:text ="测试按钮" android:id ="@+id/test" android:layout_height ="wrap_content" android:textSize ="17sp" /> <TextView android:id ="@+id/txt" android:layout_width ="wrap_content" android:text ="这里查看测试按钮的点击结果" android:layout_height ="wrap_content" /> </LinearLayout >
method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package fun.eastwind.myapplication7;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class MainActivity7 extends AppCompatActivity implements View .OnClickListener { private Button button; private Button disableButton; private Button test_button; private TextView txt; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main7); button = findViewById(R.id.enable); button.setOnClickListener(this ); disableButton = findViewById(R.id.disable); disableButton.setOnClickListener(this ); test_button = findViewById(R.id.test); test_button.setOnClickListener(this ); txt = findViewById(R.id.txt); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.enable: test_button.setEnabled(true ); break ; case R.id.disable: test_button.setEnabled(false ); break ; case R.id.test: txt.setText("update is ok!" ); break ; } } }
图像视图ImageView 图像视图展示的图片通常位于res/drawable***目录,设置图像视图的显示图片有两种方式:
在XML文件中,通过属性android:src设置图片资源,属性值格式形如“@drawable/不含拓展名的图片名称”。
在Java代码中,调用setImageResource方法设置图片资源,方法参数格式形如”R.drawable.不含拓展名的图片名称“。
我们可以提前将一张图片放置在drawable目录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity7" > <ImageView android:id ="@+id/img_view" android:layout_width ="match_parent" android:layout_margin ="10dp" android:src ="@drawable/blog" android:layout_height ="300dp" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.ImageView;import android.widget.TextView;public class MainActivity7 extends AppCompatActivity { private ImageView imageView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main7); imageView = findViewById(R.id.img_view); imageView.setImageResource(R.drawable.ic_launcher_background); } }
图像视图的缩放类型 ImageView本身默认图片居中显示,若要改变图片的显示方式,可通过scaleType属性设定,该属性的取值说明如下:
使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity7" > <ImageView android:scaleType ="fitXY" android:id ="@+id/img_view" android:layout_width ="match_parent" android:layout_margin ="10dp" android:src ="@drawable/blog" android:layout_height ="300dp" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.ImageView;import android.widget.TextView;public class MainActivity7 extends AppCompatActivity { private ImageView imageView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main7); imageView = findViewById(R.id.img_view); imageView.setImageResource(R.drawable.ic_launcher_background); imageView.setScaleType(ImageView.ScaleType.FIT_START); } }
ImageButton是显示图片的图像按钮,但它继承自ImageView,而非继承Button。
ImageButton和Button之间的区别有:
Button即可显示文本也可显示图片,ImageButton只能显示图片不能显示文本。
ImageButton上的图像可按比例缩放,而Button通过背景设置的图像会拉伸变形。
Button只能靠背景显示一张图片,而ImageButton可分别在前景和背景显示图片,从而实现两张图片叠加的效果
在某些场合,有的字符无法由输入法打出来,或者某些文字以特殊字体展示,就适合先切图再放到ImageButton。例如:开平方符号等等
ImageButton与ImageView之间的区别有:
ImageButton有默认的按钮背景,ImageView默认无背景。
ImageButton默认的缩放类型为center,而ImageView默认的缩放类型为fitCenter。
同时展示文本与图像 同时展示文本与图像的可能途径包括:
利用LinearLayout对ImageView和TextView组合布局。
通过按钮控件Button的drawable***属性设置文本周围的图标
drawableTop:指定文字上方的图片。
drawableBottom:指定文字下方的图片。
drawableLeft:指定文字左边的图片。
drawableRight:指定文字右边的图片。
drawablePadding:指定图片与文字的间距
样式无效的情况 如果在编写代码时,发现样式不起作用,可以这样设置
修改themes.xml文件的样式,修改为如下内容
1 parent="Theme.MaterialComponents.DayNight.DarkActionBar.Bridge"
Activity Activity的启动和结束 从当前页面跳到新页面,跳转代码如下:
startActivity(new Intent(源代码.this,目标页面.class))
从当前页面回到上一个页面,相当于关闭当前页码,返回代码如下:
页面1
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:gravity ="center" android:layout_height ="match_parent" > <Button android:id ="@+id/btn_act_next" android:layout_width ="wrap_content" android:text ="跳转下一个页面" android:layout_height ="wrap_content" /> </LinearLayout >
页面2
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:gravity ="center" android:layout_height ="match_parent" > <Button android:id ="@+id/btn_act_last" android:layout_width ="wrap_content" android:text ="跳转到上一个页面" android:layout_height ="wrap_content" /> </LinearLayout >
页面1跳转到页面2的具体功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.view.View;public class MainActivity extends AppCompatActivity implements View .OnClickListener { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_act_next).setOnClickListener(this ); } @Override public void onClick (View v) { startActivity(new Intent (this , MainActivity2.class)); } }
页面2返回页面1的具体功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;public class MainActivity2 extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main2); findViewById(R.id.btn_act_last).setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { finish(); } }); } }
Activity的生命周期
onCreate:创建活动。把页面布局加载进内存,进入了初始状态。
onStart:开始活动。把活动页面显示在屏幕上,进入了就绪状态。
onResume:恢复活动。活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、运行用户输入文字等等。
onPause:暂停活动。页面进入暂停状态,无法与用户正常交互。
onStop:停止活动。页面将不在屏幕显示。
onDestroy:销毁活动。回收活动占用的系统资源,把页面从内存中清除。
onRestart:重启活动。重新加载内存中的页面数据。
onNewIntent:重用已有的活动实例
各状态之间的切换流程
打开新页面的方法调用顺序为:
onCreate->onStart->onResume
关闭旧页面的方法调用顺序为:
onPause->onStop->onDestroy
Activity的启动模式 某App先后打开两个活动,此时活动栈的变动情况如下图所示:
依次结束已经打开的两个活动,此时活动栈的变动情况如下图所示:
我们可以修改Activity的启动模式
在配置文件中指定启动模式 launchMode属性值说明如下
launchMode属性值
说明
standard
标准模式,无论何时启动哪个活动,都是重新创建该页面的实例并放入栈顶。如果不指定launchMode属性,则默认为标准模式
singleTop
启动新活动时,判断如果栈顶正好就是该活动的实例,则重用该实例;否则创建新的实例并放入栈顶,也就是按照standard模式处理
singleTask
启动新活动时,判断如果栈中存在该活动的实例,则重用该实例,并清除位于该实例上面的所有实例;否则按standard模式处理
singleInstance
启动新活动时,将该活动的实例放入一个新栈中,原栈的实例列表保持不变
在AndroidManifest.xml文件中进行配置
1 2 3 4 5 6 7 8 9 10 <activity android:name =".MainActivity" android:launchMode ="standard" android:exported ="true" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity >
默认启动模式standard 该模式可以被设定,不在manifest设定时候,Activity的默认模式是standard。在该模式下,启动的Activity会按照启动顺序被依次压入Task栈中。
栈顶复用模式singleTop 在该模式下,如果栈顶Activity为我们要新建的Activity(目标Activity),那么就不会重复创建新的Activity。
应用场景
适合开启渠道多、多应用开启调用的Activity,通过这种设置可以避免已经创建过的Activity被重复创建,多数通过动态设置使用。
栈内复用模式singleTask 与singleTop模式相似,只不过singleTop模式是只针对栈顶的元素,而singleTask模式下,如果task栈内存在目标Activity实例,则将task内的对应Activity实例之上的所有Activity弹出栈,并将Activity置于栈顶,获得焦点。
应用场景
程序主界面 :我们不希望主界面被创建多次,而且在主界面退出的时候,退出整个App是最好的效果
耗费系统资源的Activity :对于那些极其耗费系统资源的Activity,我们可以考虑将其设为singleTask模式,减少资源耗费。
全局唯一模式singleInstance 在该模式下,我们会为目标Activity创建一个新的Task栈,将目标Activity放入新的Task,并让目标Activity获得焦点。新的Task有且只有这一个Activity实例。 如果已经创建过目标Activity实例,则不会创建新的Task,而是将以前创建过的Activity唤醒。
如果我们想在跳转页面时,主动修改启动模式,可以这样做
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.view.View;public class MainActivity extends AppCompatActivity implements View .OnClickListener { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_act_next).setOnClickListener(this ); } @Override public void onClick (View v) { Intent intent = new Intent (this , MainActivity2.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }
在代码里设置启动标志 启动标志的取值说明如下:
Intent.FLAG_ACTIVITY_NEW_TASK:开辟一个新的任务栈
Intent.FLAG_ACTIVITY_SINGLE_TOP:当栈顶为待跳转实例时,则重用栈顶的实例
Intent.FLAG_ACTIVITY_CLEAR_TOP:当栈中存在待跳转的活动实例时,则重新创建一个新实例,并清除原实例上方的所有实例
Intent.FLAG_ACTIVITY_NO_HISTORY:栈中不保存新启动的活动实例
Intent.FLAG_ACTIVITY_CLEAR_TASK:跳转到新页面时,栈中原有实例都被清空
实现登录效果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.view.View;public class MainActivity extends AppCompatActivity implements View .OnClickListener { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_act_next).setOnClickListener(this ); } @Override public void onClick (View v) { Intent intent = new Intent (this , MainActivity2.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }
显式Intent和隐式Intent Intent是各个组件之间信息沟通的桥梁,它用于Android各组件之间的通信,主要完成下列工作:
标明本次通信请求从哪里来、到哪里去、要怎么走。
发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容。
Intent的组成部分
元素名称
设置方法
说明与用途
Component
setComponent
组件,它指定意图的来源与目标
Action
setAction
动作,它指定意图的动作行为
Data
setData
即Uri,它指定动作要操纵的数据路径
Category
addCategory
类别,它指定意图的操作类别
Type
setType
数据类型,它指定消息的数据类型
Extras
putExtras
扩展信息,它指定装载的包裹信息
Flags
setFlags
标志位,它指定活动的启动标志
显式Intent 显示Intent,直接指定来源活动与目标活动,属于精确匹配。它有三种构建方式:
1 2 3 4 5 @Override public void onClick (View v) { Intent intent = new Intent (this , MainActivity2.class); startActivity(intent); }
1 2 3 4 5 6 @Override public void onClick (View v) { Intent intent = new Intent (); intent.setClass(this , MainActivity2.class); startActivity(intent); }
1 2 3 4 5 6 7 @Override public void onClick (View v) { Intent intent = new Intent (); ComponentName componentName = new ComponentName (this , MainActivity2.class); intent.setComponent(componentName); startActivity(intent); }
隐式Intent 隐式Intent不指明去哪个页面
基础页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:gravity ="center" android:orientation ="vertical" android:layout_height ="match_parent" > <TextView android:layout_width ="wrap_content" android:text ="点击按钮向12345拨号" android:layout_marginBottom ="10dp" android:layout_height ="wrap_content" /> <Button android:id ="@+id/btn_act_next" android:layout_width ="wrap_content" android:text ="跳转拨号页面" android:layout_height ="wrap_content" /> <Button android:id ="@+id/btn_act_next2" android:layout_width ="wrap_content" android:text ="跳转短信页面" android:layout_height ="wrap_content" /> <Button android:id ="@+id/btn_act_next3" android:layout_width ="wrap_content" android:text ="跳转自己的项目页面" android:layout_height ="wrap_content" /> </LinearLayout >
功能代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.view.View;public class MainActivity extends AppCompatActivity implements View .OnClickListener { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_act_next).setOnClickListener(this ); findViewById(R.id.btn_act_next2).setOnClickListener(this ); findViewById(R.id.btn_act_next3).setOnClickListener(this ); } @Override public void onClick (View v) { String phoneNo = "12345" ; Intent intent = new Intent (); switch (v.getId()){ case R.id.btn_act_next: intent.setAction(Intent.ACTION_DIAL); Uri uri = Uri.parse("tel:" + phoneNo); intent.setData(uri); startActivity(intent); break ; case R.id.btn_act_next2: intent.setAction(Intent.ACTION_SENDTO); Uri uri2 = Uri.parse("sms:" + phoneNo); intent.setData(uri2); startActivity(intent); break ; case R.id.btn_act_next3: intent.setAction("android.intent.action.NING" ); intent.addCategory(Intent.CATEGORY_DEFAULT); startActivity(intent); break ; } } }
向下一个Activity发送数据 Intent使用Bundle对象存放待传递的数据信息。
Bundle对象操作各类型数据的读写方法说明见下表
数据类型
读方法
写方法
整型数
getInt
putInt
浮点数
getFloat
putFloat
双精度数
getDouble
putDouble
布尔值
getBoolean
putBoolean
字符串
getString
putString
字符串数组
getStringArray
putStringArray
字符串列表
getStringArrayList
putStringArrayList
可序列化结构
getSerializable
putSerializable
在代码中发送消息包裹,调用意图对象的putExtras方法,即可存入消息包裹。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:gravity ="center" android:orientation ="vertical" android:layout_height ="match_parent" > <TextView android:id ="@+id/txt" android:layout_width ="wrap_content" android:text ="今天天气真不错啊!" android:layout_height ="wrap_content" /> <Button android:id ="@+id/send_txt" android:layout_width ="wrap_content" android:text ="发送以上文字" android:layout_height ="wrap_content" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.view.View;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View .OnClickListener { private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.send_txt).setOnClickListener(this ); textView = findViewById(R.id.txt); } @Override public void onClick (View v) { String content = textView.getText().toString(); Intent intent = new Intent (this , MainActivity2.class); Bundle bundle = new Bundle (); bundle.putString("request_time" , String.valueOf(System.currentTimeMillis())); bundle.putString("request_content" ,content); intent.putExtras(bundle); startActivity(intent); } }
在代码中接收消息包裹,调用意图对象的getExtras方法,即可取出消息包裹。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:gravity ="center" android:orientation ="vertical" android:layout_height ="match_parent" > <TextView android:id ="@+id/receive" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> <Button android:id ="@+id/btn_act_last" android:layout_width ="wrap_content" android:text ="跳转到上一个页面" android:layout_height ="wrap_content" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.TextView;public class MainActivity2 extends AppCompatActivity { private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main2); textView = findViewById(R.id.receive); Bundle res = getIntent().getExtras(); String request_time = res.getString("request_time" ); String request_content = res.getString("request_content" ); textView.setText("currentTime is " + request_time + "content is" + request_content); } }
向上一个Activity返回数据 处理下一个页面的应答数据,详细步骤说明如下:
startActivityForResult方法已过时,以下代码示例中会说明新的使用方法
上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作
下一个页面接收并解析请求数据,进行相应处理
下一个页面在返回上一个页面时,打包应答数据并调用setResult方法返回数据包裹
上一个页面重写方法onActivityResult,解析获得下一个页面的返回数据
页面2的页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:gravity ="center" android:orientation ="vertical" android:layout_height ="match_parent" > <TextView android:id ="@+id/receive" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> <Button android:id ="@+id/btn_act_last" android:layout_width ="wrap_content" android:text ="跳转到上一个页面" android:layout_height ="wrap_content" /> </LinearLayout >
页面1的页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:gravity ="center" android:orientation ="vertical" android:layout_height ="match_parent" > <TextView android:id ="@+id/txt" android:layout_width ="wrap_content" android:text ="今天天气真不错啊!" android:layout_height ="wrap_content" /> <Button android:id ="@+id/receive_txt" android:layout_width ="wrap_content" android:text ="回到上一个页面,表示接收到数据" android:layout_height ="wrap_content" /> </LinearLayout >
页面2跳转到页面1及页面1结束时返回给页面2的结果操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;import androidx.activity.result.ActivityResultLauncher;import androidx.activity.result.contract.ActivityResultContracts;import androidx.appcompat.app.AppCompatActivity;public class MainActivity2 extends AppCompatActivity implements View .OnClickListener { private TextView textView; private Button button; private ActivityResultLauncher<Intent> intentActivityResultLauncher; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main2); textView = findViewById(R.id.receive); button = findViewById(R.id.btn_act_last); button.setOnClickListener(this ); intentActivityResultLauncher = registerForActivityResult (new ActivityResultContracts .StartActivityForResult(), result -> { if (result != null ){ Intent data = result.getData(); if (data != null && result.getResultCode() == Activity.RESULT_OK){ Bundle extras = data.getExtras(); String res1 = extras.getString("res" ); textView.setText(res1); } } }); } @Override public void onClick (View v) { Intent intent = new Intent (this , MainActivity.class); Bundle bundle = new Bundle (); bundle.putString("val" ,"this is val" ); intent.putExtras(bundle); intentActivityResultLauncher.launch(intent); } }
页面1接收页面2并返回给页面2数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import androidx.appcompat.app.AppCompatActivity;import android.app.Activity;import android.content.ComponentName;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class MainActivity extends AppCompatActivity { private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.txt); Bundle bundle = getIntent().getExtras(); textView.setText(bundle.getString("val" )); Button button = findViewById(R.id.receive_txt); button.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { Intent intent = new Intent (); Bundle bundle = new Bundle (); bundle.putString("res" ,"for ok!" ); intent.putExtras(bundle); setResult(Activity.RESULT_OK,intent); finish(); } }); } }
利用资源文件配置字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:gravity ="center" android:layout_height ="match_parent" > <TextView android:id ="@+id/tv_resource" android:text ="@string/app_name" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> </LinearLayout >
strings.xml
1 2 3 4 <resources > <string name ="app_name" > My Application</string > <string name ="weather" > rain</string > </resources >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.widget.TextView;public class MainActivity3 extends AppCompatActivity { private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main3); textView = findViewById(R.id.tv_resource); String weather = getString(R.string.weather); textView.setText(weather); } }
利用元数据传递配置信息 在整合第三方sdk时(高德地图、微信登录等),需要对应的token,就可以放到此处
先在该文件提前放置meta-data
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?xml version="1.0" encoding="utf-8" ?> <manifest xmlns:android ="http://schemas.android.com/apk/res/android" > <application android:allowBackup ="true" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:roundIcon ="@mipmap/ic_launcher_round" android:supportsRtl ="true" android:theme ="@style/Theme.Lulu" > <activity android:name =".MainActivity" android:exported ="false" /> <activity android:name =".MainActivity3" android:exported ="true" android:launchMode ="standard" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > <meta-data android:name ="weather" android:value ="@string/weather" /> </activity > </application > </manifest >
在Java代码中,获取元数据信息的步骤分为下列三步:
调用getPackageManager方法获得当前应用的包管理器;
调用包管理器的getActivityInfo方法获得当前活动的信息对象;
活动信息对象的metaData是Bundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import androidx.appcompat.app.AppCompatActivity;import android.content.pm.ActivityInfo;import android.content.pm.PackageManager;import android.os.Bundle;import android.widget.TextView;public class MainActivity3 extends AppCompatActivity { private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main3); textView = findViewById(R.id.tv_resource); PackageManager pm = getPackageManager(); try { ActivityInfo info = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); Bundle metaData = info.metaData; String weather = metaData.getString("weather" ); textView.setText(weather); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } }
给应用界面注册快捷方式 元数据不仅能传递简单的字符串参数,还能传送更复杂的资源数据,比如支付宝的快捷方式菜单。
先在res下创建一个目录xml,再到xml中创建一个XML文件(Values XML File)
按照以下模板来进行编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?xml version="1.0" encoding="utf-8" ?> <shortcuts xmlns:android ="http://schemas.android.com/apk/res/android" > <shortcut android:shortcutId ="first" android:enabled ="true" android:icon ="@mipmap/ic_launcher" android:shortcutShortLabel ="@string/first" android:shortcutLongLabel ="@string/startOrStop" > <intent android:action ="android.intent.action.VIEW" android:targetPackage ="fun.eastwind.myapplication8" android:targetClass ="fun.eastwind.myapplication8.MainActivity3" /> <categories android:name ="android.shortcut.conversation" /> </shortcut > <shortcut android:shortcutId ="second" android:enabled ="true" android:icon ="@mipmap/ic_launcher" android:shortcutShortLabel ="@string/second" android:shortcutLongLabel ="@string/jump" > <intent android:action ="android.intent.action.VIEW" android:targetPackage ="fun.eastwind.myapplication8" android:targetClass ="fun.eastwind.myapplication8.MainActivity2" /> <categories android:name ="android.shortcut.conversation" /> </shortcut > </shortcuts >
strings.xml
1 2 3 4 5 6 7 8 <resources > <string name ="app_name" > My Application</string > <string name ="weather" > rain</string > <string name ="first" > first</string > <string name ="startOrStop" > 启停活动</string > <string name ="second" > second</string > <string name ="jump" > 来回跳转</string > </resources >
AndroidManifest.xml
1 2 3 4 5 6 7 <activity android:name =".MainActivity2" android:exported ="true" /> <activity android:name =".MainActivity" android:exported ="true" />
中级控件 图形定制 图像Drawable
Drawable类型表达了各种各样的图形,包括图片、色块、画板、背景等。
包含图片在内的图像文件放在res目录的各个drawable目录下,其中drawable目录一般保存描述性的XML文件,而图片文件一般放在具体分辨率的drawable目录下。
各视图的background属性、ImageView和ImageButton的src属性、TextView和Button四个方向的drawable系列属性都可以引用图形文件
Android把所有能够显示的图形都抽象为Drawable类(可绘制的)。这里的图形不止是图片,还包括色
块、画板、背景等。
包含图片在内的图形文件放在res目录的各个drawable目录下,其中drawable目录一般保存描述性的
XML文件,而图片文件一般放在具体分辨率的drawable目录下。例如:
drawable-ldpi里面存放低分辨率的图片(如240×320),现在基本没有这样的智能手机了。
drawable-mdpi里面存放中等分辨率的图片(如320×480),这样的智能手机已经很少了。
drawable-hdpi里面存放高分辨率的图片(如480×800),一般对应4英寸~4.5英寸的手机(但不
绝对,同尺寸的手机有可能分辨率不同,手机分辨率就高不就低,因为分辨率低了屏幕会有模糊的
感觉)。
drawable-xhdpi里面存放加高分辨率的图片(如720×1280),一般对应5英寸~5.5英寸的手机。
drawable-xxhdpi里面存放超高分辨率的图片(如1080×1920),一般对应6英寸~6.5英寸的手
机。
drawable-xxxhdpi里面存放超超高分辨率的图片(如1440×2560),一般对应7英寸以上的平板电
脑。
基本上,分辨率每加大一级,宽度和高度就要增加二分之一或三分之一像素。如果各目录存在同名图
片,Android就会根据手机的分辨率分别适配对应文件夹里的图片。在开发App时,为了兼容不同的手机
屏幕,在各目录存放不同分辨率的图片,才能达到最合适的显示效果。例如,在drawable-hdpi放了一
张背景图片bg.png(分辨率为480×800),其他目录没放,使用分辨率为480×800的手机查看该App界
面没有问题,但是使用分辨率为720×1280的手机查看该App会发现背景图片有点模糊,原因是Android
为了让bg.png适配高分辨率的屏幕,强行把bg.png拉伸到了720×1280,拉伸的后果是图片变模糊了。
在XML布局文件中引用图形文件可使用“@drawable/不含扩展名的文件名称”这种形式。
形状图形
Shape图形又称形状图形,它用来描述常见的几何形状,包括矩形、圆角矩形、图形、椭圆等等。
形状图形的定义文件是以shape标签为根节点的XML描述文件,它支持四种类型的形状:
rectangle:矩形。默认值
oval:椭圆。此时corners节点会失效
line:直线。此时必须设置stroke节点,不然会报错
ring:圆环
shape是形状图形文件的根节点,它描述了当前是哪种几何图形。下面是shape节点的常用属性说明。
形状类型
说明
rectangle
矩形。默认值
oval
椭圆。此时corners节点会失效
line
直线。此时必须设置stroke节点,不然会报错
ring
圆环
size(尺寸)
size是shape的下级节点,它描述了形状图形的宽高尺寸。若无size节点,则表示宽高与宿主视图一样大
小。下面是size节点的常用属性说明。
height:像素类型,图形高度。
width:像素类型,图形宽度。
stroke(描边)
stroke是shape的下级节点,它描述了形状图形的描边规格。若无stroke节点,则表示不存在描边。下
面是stroke节点的常用属性说明。
color:颜色类型,描边的颜色。
dashGap:像素类型,每段虚线之间的间隔。
dashWidth:像素类型,每段虚线的宽度。若dashGap和dashWidth有一个值为0,则描边为实线。
width:像素类型,描边的厚度。
corners(圆角)
corners是shape的下级节点,它描述了形状图形的圆角大小。若无corners节点,则表示没有圆角。下
面是corners节点的常用属性说明。
bottomLeftRadius:像素类型,左下圆角的半径。
bottomRightRadius:像素类型,右下圆角的半径。
topLeftRadius:像素类型,左上圆角的半径。
topRightRadius:像素类型,右上圆角的半径。
radius:像素类型,4个圆角的半径(若有上面4个圆角半径的定义,则不需要radius定义)。
solid(填充)
solid是shape的下级节点,它描述了形状图形的填充色彩。若无solid节点,则表示无填充颜色。下面是
solid节点的常用属性说明。
padding(间隔)
padding是shape的下级节点,它描述了形状图形与周围边界的间隔。若无padding节点,则表示四周不
设间隔。下面是padding节点的常用属性说明。
top:像素类型,与上方的间隔。
bottom:像素类型,与下方的间隔。
left:像素类型,与左边的间隔。
right:像素类型,与右边的间隔。
gradient(渐变)
gradient是shape的下级节点,它描述了形状图形的颜色渐变。若无gradient节点,则表示没有渐变效
果。下面是gradient节点的常用属性说明。
angle:整型,渐变的起始角度。为0时表示时钟的9点位置,值增大表示往逆时针方向旋转。例如,值为90表示6点位置,值为180表示3点位置,值为270表示0点/12点位置。
type:字符串类型,渐变类型。
渐变类型的取值说明
渐变类型
说明
linear
线性渐变,默认值
radial
放射渐变,起始颜色就是圆心颜色
sweep
滚动渐变,即一个线段以某个端点为圆心做360度旋转
centerX:浮点型,圆心的X坐标。当android:type=”linear”时不可用。
centerY:浮点型,圆心的Y坐标。当android:type=”linear”时不可用。
gradientRadius:整型,渐变的半径。当android:type=”radial”时需要设置该属性。
centerColor:颜色类型,渐变的中间颜色。
startColor:颜色类型,渐变的起始颜色。
endColor:颜色类型,渐变的终止颜色。
useLevel:布尔类型,设置为true为无渐变色、false为有渐变色。
在实际开发中,形状图形主要使用3个节点:stroke(描边)、corners(圆角)和solid(填充)。至于shape根节点的属性一般不用设置(默认矩形即可)。
下面是一个xml页面,带有两个按钮,点击后可以产生不同的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <View android:layout_margin ="10dp" android:id ="@+id/v_content" android:layout_width ="match_parent" android:layout_height ="200dp" /> <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <Button android:layout_width ="0dp" android:layout_weight ="1" android:text ="圆角矩形背景" android:id ="@+id/btn_rect" android:layout_height ="wrap_content" /> <Button android:layout_width ="0dp" android:layout_weight ="1" android:text ="椭圆背景" android:id ="@+id/btn_oval" android:layout_height ="wrap_content" /> </LinearLayout > </LinearLayout >
通过在drawable上创建对应形状的xml文件来创建两个形状
矩形
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8" ?> <shape xmlns:android ="http://schemas.android.com/apk/res/android" > <solid android:color ="#ffdd66" /> <stroke android:width ="1dp" android:color ="#aaaaaa" /> <corners android:radius ="10dp" /> </shape >
椭圆
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="utf-8" ?> <shape xmlns:android ="http://schemas.android.com/apk/res/android" android:shape ="oval" > <solid android:color ="#ff66aa" /> <stroke android:width ="1dp" android:color ="#aaaaaa" /> </shape >
设置view资源显示的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;public class MainActivity extends AppCompatActivity implements View .OnClickListener { private View view; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); view = findViewById(R.id.v_content); findViewById(R.id.btn_rect).setOnClickListener(this ); findViewById(R.id.btn_oval).setOnClickListener(this ); view.setBackgroundResource(R.drawable.shape_oval_rose); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.btn_rect: view.setBackgroundResource(R.drawable.shape_rect_gold); break ; case R.id.btn_oval: view.setBackgroundResource(R.drawable.shape_oval_rose); break ; } } }
九宫格图片 将某张图片设置成视图背景时,如果图片尺寸太小,则系统会自动拉伸图片使之填满背景。
可是一旦图片拉得过大,其画面容易变得模糊。
我们创建一个文件夹(drawable-xhdpi)在res下,并拖入一张图片到这个文件夹下
右击这张图片,找到create 9-patch file,进行创建
就可以创建出对应的9宫格图片了
状态列表图形 Button按钮的背景在正常情况下是凸起的,在按下时是凹陷的,从按下到弹起的过程,用户便能知道点击了这个按钮。
状态列表图形不仅用于按钮控件,还可用于其他拥有多种状态的控件
状态类型的属性名称
说明
适用的控件
state_pressed
是否按下
按钮Button
state_checked
是否勾选
复选框CheckBox、单选按钮RadioButton
state_focused
是否获取焦点
文本编辑框EditText
state_selected
是否选中
各控件通用
在drawable目录下创建一个selector文件(btn_nine_selector),对按钮的样式进行设置
这里的drawable是自己画的图形
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:state_pressed ="true" android:drawable ="@drawable/shape_oval_rose" /> <item android:drawable ="@drawable/shape_rect_gold" /> </selector >
在这里使用对应的选择器来作为背景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <Button android:layout_width ="0dp" android:layout_weight ="1" android:text ="默认样式的按钮" android:id ="@+id/btn_rect" android:layout_height ="wrap_content" /> <Button android:layout_width ="0dp" android:layout_weight ="1" android:background ="@drawable/btn_nine_selector" android:text ="定制样式的按钮" android:id ="@+id/btn_oval" android:layout_height ="wrap_content" /> </LinearLayout > </LinearLayout >
选择按钮 复选框CheckBox CompoundButton类是抽象的复合按钮,由它派生而来的子类包括:复选框CheckBox、单选按钮RadioButton以及开关按钮Switch
下图描述了复合按钮的继承关系:
xml页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <CheckBox android:id ="@+id/default_activity_button" android:layout_width ="match_parent" android:text ="系统默认的多选框" android:gravity ="center" android:layout_height ="wrap_content" /> <CheckBox android:checked ="true" android:layout_marginTop ="10dp" android:button ="@drawable/check_box" android:id ="@+id/custom_checkbox" android:layout_width ="match_parent" android:text ="定制版的多选框" android:gravity ="center" android:layout_height ="wrap_content" /> </LinearLayout >
选中或未选中时的效果
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:state_checked ="true" android:drawable ="@drawable/shape_oval_rose" /> <item android:drawable ="@drawable/ic_launcher_background" /> </selector >
为checkbox设置监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.widget.CheckBox;import android.widget.CompoundButton;public class MainActivity2 extends AppCompatActivity implements CompoundButton .OnCheckedChangeListener { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main2); CheckBox checkBox = findViewById(R.id.custom_checkbox); CheckBox checkBox2 = findViewById(R.id.default_activity_button); checkBox.setOnCheckedChangeListener(this ); checkBox2.setOnCheckedChangeListener(this ); } @Override public void onCheckedChanged (CompoundButton buttonView, boolean isChecked) { String check = isChecked ? "勾选" : "取消勾选" ; String format = String.format("您" + check + "了该多选框" ); buttonView.setText(format); } }
开关按钮Switch Switch是开关按钮,它在选中与取消选中时可展现的页面元素比复选框丰富。
Switch控件新添加的XML属性说明如下:
textOn:设置右侧开启时的文本
textOff:设置左侧关闭时的文本
track:设置开关轨道的背景
thumb:设置开关标识的图标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <LinearLayout android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <TextView android:layout_width ="0dp" android:layout_weight ="1" android:text ="Switch开关:" android:layout_gravity ="start" android:padding ="5dp" android:layout_height ="wrap_content" /> <Switch android:id ="@+id/sw_status" android:layout_width ="80dp" android:layout_gravity ="end" android:layout_height ="30dp" /> </LinearLayout > <TextView android:id ="@+id/sw_btn" android:text ="Switch目前为关" android:padding ="5dp" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.widget.CompoundButton;import android.widget.Switch;import android.widget.TextView;public class MainActivity3 extends AppCompatActivity implements CompoundButton .OnCheckedChangeListener { private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main3); Switch aSwitch = findViewById(R.id.sw_status); textView = findViewById(R.id.sw_btn); aSwitch.setOnCheckedChangeListener(this ); } @Override public void onCheckedChanged (CompoundButton buttonView, boolean isChecked) { String isStatus = isChecked ? "开" : "关" ; textView.setText(isStatus); } }
仿iOS的开关按钮 借助状态列表图形StateListDrawable,分别定义已选中时候的“开”图形,以及未选中时候的“关”图形。
然后将CheckBox控件的background属性设置为该状态图形。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <LinearLayout android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <TextView android:layout_width ="0dp" android:layout_weight ="1" android:text ="Switch开关:" android:layout_gravity ="start" android:padding ="5dp" android:layout_height ="wrap_content" /> <CheckBox android:id ="@+id/ck_status" android:layout_width ="60dp" android:layout_height ="30dp" android:layout_gravity ="end" android:background ="@drawable/switch_selector" android:button ="@null" /> </LinearLayout > <TextView android:id ="@+id/sw_btn" android:text ="Switch目前为关" android:padding ="5dp" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout >
对应的checkBox的资源图片
1 2 3 4 5 <?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:state_checked ="true" android:drawable ="@drawable/open" /> <item android:drawable ="@drawable/close" /> </selector >
单选按钮要在一组按钮中选择其中一项,并且不能多选,这要求有个容器确定这组按钮的范围,这个容器便是单选组RadioGroup。
RadioGroup实质上是个布局,同一组RadioButton都要放在同一个RadioGroup节点下。
除了RadioButton,也允许放置其他控件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <RadioGroup android:id ="@+id/gender" android:gravity ="center" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="wrap_content" > <RadioButton android:id ="@+id/boy" android:text ="男" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> <RadioButton android:text ="女" android:id ="@+id/girl" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> </RadioGroup > <TextView android:id ="@+id/text" android:padding ="10dp" android:layout_width ="wrap_content" android:text ="点击单选按钮后有不同的效果" android:layout_height ="wrap_content" /> </LinearLayout >
具体功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import android.os.Bundle;import android.widget.RadioGroup;import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;public class MainActivity3 extends AppCompatActivity implements RadioGroup .OnCheckedChangeListener { private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main3); RadioGroup radioGroup = findViewById(R.id.gender); radioGroup.setOnCheckedChangeListener(this ); textView = findViewById(R.id.text); } @Override public void onCheckedChanged (RadioGroup group, int checkedId) { switch (checkedId){ case R.id.boy: textView.setText("I\'m boy" ); break ; case R.id.girl: textView.setText("I\'m girl" ); } } }
单选组的用法 判断选中了哪个单选按钮,通常不是监听某个单选按钮,而是监听单选组的选中事件。
下面是RadioGroup常用的3个方法:
check:选中指定资源编号的单选按钮。
getCheckedRadioButtonId:获取选中状态单选按钮的资源编号。
setOnCheckedChangeListener:设置单选按钮勾选变化的监听器。
文本输入 编辑框EditText 编辑框EditText用于接收软键盘输入的文字,例如用户名、密码、评价内容等,它由文本视图派生而
来,除了TextView已有的各种属性和方法,EditText还支持下列XML属性。
inputType:指定输入的文本类型。输入类型的取值说明见表5-4,若同时使用多种文本类型,则可
使用竖线“|”把多种文本类型拼接起来。
maxLength:指定文本允许输入的最大长度。
hint:指定提示文本的内容。
textColorHint:指定提示文本的颜色。
输入类型
说明
text
文本
textPassword
文本密码。
number
整型数
numberSigned
带符号的数字。允许在开头带负号“-”
numberDecimal
带小数点的数字
numberPassword
数字密码。
datetime
时间日期格式。除了数字外,还允许输入横线、斜杠、空格、冒号
data
日期格式。除了数字外,还允许输入横线“-”和斜杠“/”
time
时间格式。除了数字外,还允许输入冒号“:”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:padding ="10dp" android:layout_height ="match_parent" > <TextView android:layout_width ="wrap_content" android:text ="下面是登录信息" android:textSize ="16sp" android:layout_height ="wrap_content" /> <EditText android:textColorHint ="#d6d6d6" android:hint ="请输入用户名" android:inputType ="text" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <EditText android:textColorHint ="#d6d6d6" android:hint ="请输入密码" android:inputType ="textPassword" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout >
我们还可以对边框的形状进行修改,修改为圆角矩形
通过selector选择器,来修改对应的输入框背景即可
创建选择器
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:state_focused ="true" android:drawable ="@drawable/edit_focus_shape" /> <item android:drawable ="@drawable/edit_focus_nor" /> </selector >
选择器中对应的资源
edit_focus_shape
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8" ?> <shape xmlns:android ="http://schemas.android.com/apk/res/android" > <solid android:color ="#ffffff" /> <stroke android:width ="1dp" android:color ="#0000ff" /> <corners android:radius ="5dp" /> <padding android:bottom ="2dp" android:left ="2dp" android:right ="2dp" android:top ="2dp" /> </shape >
edit_focus_nor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8" ?> <shape xmlns:android ="http://schemas.android.com/apk/res/android" > <solid android:color ="#ffffff" /> <stroke android:width ="1dp" android:color ="#aaaaaa" /> <corners android:radius ="5dp" /> <padding android:bottom ="2dp" android:left ="2dp" android:right ="2dp" android:top ="2dp" /> </shape >
在对应的输入框中进行设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:padding ="10dp" android:layout_height ="match_parent" > <TextView android:layout_width ="wrap_content" android:text ="下面是登录信息" android:textSize ="16sp" android:layout_height ="wrap_content" /> <EditText android:textColorHint ="#d6d6d6" android:hint ="请输入用户名" android:inputType ="text" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <EditText android:background ="@drawable/input_selector" android:textColorHint ="#d6d6d6" android:hint ="请输入密码" android:inputType ="textPassword" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout >
焦点变更监听器
编辑框点击两次后才会触发点击事件,因为第一次点击只触发焦点变更事件,第二次点击才触发点击事件
若要判断是否切换编辑框输入,应当监听焦点变更事件,而非监听点击事件。
调用编辑框对象的setOnFocusChangeListener方法,即可在光标切换之时(获得光标和失去光标)触发焦点变更事件。
下面是一个当手机号小于11位时,点击密码输入框后将焦点转移回手机输入框的代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:padding ="10dp" android:layout_height ="match_parent" > <EditText android:id ="@+id/phone" android:layout_width ="match_parent" android:hint ="请输入11位手机号" android:layout_height ="wrap_content" /> <EditText android:id ="@+id/pwd" android:layout_width ="match_parent" android:hint ="请输入6位密码" android:layout_height ="wrap_content" /> <Button android:layout_width ="match_parent" android:textSize ="20sp" android:text ="登录" android:layout_height ="wrap_content" /> <TextView android:id ="@+id/tip" android:layout_marginTop ="50dp" android:layout_gravity ="center" android:layout_width ="wrap_content" android:padding ="12dp" android:background ="#d6d8d7" android:text ="" android:layout_height ="wrap_content" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import android.os.Bundle;import android.view.View;import android.widget.EditText;import android.widget.RadioGroup;import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;public class MainActivity3 extends AppCompatActivity implements View .OnFocusChangeListener { private EditText editTextPhone; private EditText editTextPwd; private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main3); editTextPhone = findViewById(R.id.phone); editTextPwd = findViewById(R.id.pwd); textView = findViewById(R.id.tip); editTextPhone.setOnFocusChangeListener(this ); editTextPwd.setOnFocusChangeListener(this ); } @Override public void onFocusChange (View v, boolean hasFocus) { switch (v.getId()){ case R.id.pwd: if (editTextPhone.getText().length() < 11 ){ textView.setText("手机号不允许小于11位" ); editTextPhone.requestFocus(); } break ; } } }
文本变化监听器
调用编辑框对象的addTextChangedListener方法即可注册文本监听器。
文本监听器的接口名称为TextWatcher,该接口提供了3个监控方法,具体说明如下。
beforeTextChanged:在文本改变之前触发。
onTextChanged:在文本改变过程中触发
afterTextChanged:在文本改变之后触发
判断手机号输入满11位后自动关闭软键盘,或者密码输入满6位后自动关闭软键盘,此时要注册文本变化监听器。
达到指定位数便自动关闭键盘的功能,可以再分解为两个独立的功能点
如何关闭软键盘;
如何判断已输入的文字达到指定位数;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:padding ="10dp" android:layout_height ="match_parent" > <EditText android:id ="@+id/phone" android:layout_width ="match_parent" android:hint ="输入11位自动隐藏输入法" android:maxLength ="11" android:layout_height ="wrap_content" /> <EditText android:id ="@+id/pwd" android:maxLength ="6" android:layout_width ="match_parent" android:hint ="输入6位自动隐藏输入法" android:layout_height ="wrap_content" /> <Button android:layout_width ="match_parent" android:textSize ="20sp" android:text ="登录" android:layout_height ="wrap_content" /> <TextView android:id ="@+id/tip" android:layout_marginTop ="50dp" android:layout_gravity ="center" android:layout_width ="wrap_content" android:padding ="12dp" android:background ="#d6d8d7" android:text ="" android:layout_height ="wrap_content" /> </LinearLayout >
功能实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.view.View;import android.widget.EditText;import android.widget.RadioGroup;import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;public class MainActivity3 extends AppCompatActivity { private EditText editTextPhone; private EditText editTextPwd; private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main3); editTextPhone = findViewById(R.id.phone); editTextPwd = findViewById(R.id.pwd); textView = findViewById(R.id.tip); editTextPhone.addTextChangedListener(new HideTextWatcher (editTextPhone,11 )); editTextPwd.addTextChangedListener(new HideTextWatcher (editTextPwd,6 )); } private class HideTextWatcher implements TextWatcher { private EditText editText; private int mMaxLength; public HideTextWatcher (EditText editTextPhone, int maxLength) { editText = editTextPhone; mMaxLength = maxLength; } @Override public void beforeTextChanged (CharSequence s, int start, int count, int after) { } @Override public void onTextChanged (CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged (Editable s) { String strLen = s.toString(); if (strLen.length() == mMaxLength){ ViewUtil.hideOnInputMethod(MainActivity3.this ,editText); } } } }
关闭软键盘的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import android.app.Activity;import android.content.Context;import android.view.View;import android.view.inputmethod.InputMethodManager;public class ViewUtil { public static void hideOnInputMethod (Activity activity, View view) { InputMethodManager systemService = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); systemService.hideSoftInputFromWindow(view.getWindowToken(),0 ); } }
对话框 提醒对话框AlertDialog
AlertDialog可以完成常见的交互操作,例如提示、确认、选择等功能。AlertDialog借助建造器AlertDialog.Builder才能完成参数设置。
调用建造器的create方法生成对话框实例,再调用对话框实例的show方法,在页面上弹出提醒对话框。
使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?xml version="1.0" encoding="utf-8" ?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity4" > <Button android:id ="@+id/button" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="弹出提醒对话框" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.498" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toTopOf ="parent" app:layout_constraintVertical_bias ="0.221" /> <TextView android:id ="@+id/textView" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:textSize ="20sp" android:text ="" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.498" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toBottomOf ="@+id/button" app:layout_constraintVertical_bias ="0.135" /> </androidx.constraintlayout.widget.ConstraintLayout >
功能代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.content.DialogInterface;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class MainActivity4 extends AppCompatActivity implements View .OnClickListener { private Button button; private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main4); button = findViewById(R.id.button); textView = findViewById(R.id.textView); button.setOnClickListener(this ); } @Override public void onClick (View v) { AlertDialog.Builder builder = new AlertDialog .Builder(this ); builder.setTitle("尊敬的用户" ); builder.setMessage("你真的要卸载我吗?" ); builder.setPositiveButton("残忍卸载" , (dialog, which) -> { textView.setText("good bye" ); }); builder.setNegativeButton("我再想想" , (dialog, which) -> { textView.setText("hello,i go" ); }); AlertDialog alertDialog = builder.create(); alertDialog.show(); } }
日期对话框DatePickerDialog
日期选择器DatePicker可以让用户选择具体的年月日。
但DatePicker并非弹窗模式,而是在当前页面占据一块区域,并且不会自动关闭。
DatePickerDialog相当于在AlertDialog上装载了DatePicker,日期选择事件则由监听器OnDateSetListener负责响应,在该监听器的onDateSet方法中,开发者获取用户选择的具体日期,再做后续处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <?xml version="1.0" encoding="utf-8" ?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity4" > <DatePicker android:id ="@+id/datePicker" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:calendarViewShown ="false" android:datePickerMode ="spinner" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toTopOf ="parent" app:layout_constraintVertical_bias ="0.001" /> <Button android:id ="@+id/confirm_button_setDate" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_marginTop ="12dp" android:text ="选择日期" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toBottomOf ="@+id/datePicker" app:layout_constraintVertical_bias ="0.043" /> <Button android:id ="@+id/confirm_button" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_marginTop ="12dp" android:text ="确定" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toBottomOf ="@+id/datePicker" app:layout_constraintVertical_bias ="0.464" /> <TextView android:textSize ="20sp" android:id ="@+id/tv_date" android:text ="测试" android:gravity ="center" android:layout_width ="match_parent" android:layout_height ="wrap_content" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toTopOf ="@+id/confirm_button" app:layout_constraintVertical_bias ="0.395" /> </androidx.constraintlayout.widget.ConstraintLayout >
功能代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import android.app.DatePickerDialog;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.DatePicker;import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;public class MainActivity4 extends AppCompatActivity implements View .OnClickListener, DatePickerDialog.OnDateSetListener { private TextView textView; private Button button1; private DatePicker datePicker; private Button button2; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main4); button1 = findViewById(R.id.confirm_button); button2 = findViewById(R.id.confirm_button_setDate); textView = findViewById(R.id.tv_date); datePicker = findViewById(R.id.datePicker); button1.setOnClickListener(this ); button2.setOnClickListener(this ); } @Override public void onClick (View v) { int year = datePicker.getYear(); int month = datePicker.getMonth() + 1 ; int dayOfMonth = datePicker.getDayOfMonth(); switch (v.getId()){ case R.id.confirm_button: System.out.println("6666666666666666666666666666666666666666666666" ); textView.setText(String.format("%s年%s月%s日" ,year,month,dayOfMonth)); break ; case R.id.confirm_button_setDate: DatePickerDialog dialog = new DatePickerDialog (this , this , 2090 , 5 , 12 ); dialog.show(); break ; } } @Override public void onDateSet (DatePicker view, int year, int month, int dayOfMonth) { textView.setText(String.format("%s年%s月%s日" ,year,month,dayOfMonth)); } }
时间对话框TimePickerDialog
时间选择器TimePicker可以让用户选择具体的小时和分钟
TimePickerDialog的用法类似DatePickerDialog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?xml version="1.0" encoding="utf-8" ?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity4" > <TimePicker android:id ="@+id/time_picker" android:timePickerMode ="spinner" android:layout_width ="match_parent" android:layout_height ="wrap_content" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toTopOf ="parent" app:layout_constraintVertical_bias ="0.001" /> <Button android:id ="@+id/confirm_button" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_marginTop ="12dp" android:text ="确定" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toBottomOf ="@+id/time_picker" app:layout_constraintVertical_bias ="0.137" /> <TextView android:id ="@+id/tv_time" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_marginTop ="228dp" android:gravity ="center" android:text ="测试" android:textSize ="20sp" app:layout_constraintBottom_toBottomOf ="parent" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toBottomOf ="@+id/time_picker" app:layout_constraintVertical_bias ="0.0" /> </androidx.constraintlayout.widget.ConstraintLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import android.app.DatePickerDialog;import android.app.TimePickerDialog;import android.os.Build;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.DatePicker;import android.widget.TextView;import android.widget.TimePicker;import androidx.appcompat.app.AppCompatActivity;import java.util.Calendar;public class MainActivity4 extends AppCompatActivity implements View .OnClickListener, TimePickerDialog.OnTimeSetListener { private TextView textView; private Button button1; private TimePicker timePicker; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main4); button1 = findViewById(R.id.confirm_button); textView = findViewById(R.id.tv_time); timePicker = findViewById(R.id.time_picker); button1.setOnClickListener(this ); timePicker.setIs24HourView(true ); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.confirm_button: Calendar calendar = Calendar.getInstance(); TimePickerDialog timePickerDialog = new TimePickerDialog (this , this , calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true ); timePickerDialog.show(); break ; } } @Override public void onTimeSet (TimePicker view, int hourOfDay, int minute) { textView.setText(String.format("currentTime is %d 时 %d 分" ,hourOfDay,minute)); } }
案例:找回密码 登录页面一般有两种方式:
点击忘记密码后,会跳转到忘记密码对应的页面,在该页面输入和确认新密码,并校验找回密码的合法性(通过短信验证码检查)
登录页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:padding ="10dp" android:layout_height ="match_parent" > <RadioGroup android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <RadioButton android:id ="@+id/pwd_login" android:layout_width ="0dp" android:layout_weight ="1" android:checked ="true" android:text ="密码登录" android:layout_height ="wrap_content" /> <RadioButton android:id ="@+id/vertical_login" android:layout_width ="0dp" android:layout_weight ="1" android:text ="验证码登录" android:layout_height ="wrap_content" /> </RadioGroup > <LinearLayout android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="wrap_content" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:text ="手机号码:" android:textSize ="18sp" android:layout_height ="wrap_content" /> <EditText android:maxLength ="11" android:id ="@+id/et_phone" android:layout_width ="match_parent" android:hint ="请输入手机号码" android:textSize ="18sp" android:background ="@drawable/input_selector" android:layout_height ="wrap_content" /> </LinearLayout > <LinearLayout android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <TextView android:id ="@+id/tx_pwd" android:layout_width ="wrap_content" android:text ="登录密码:" android:textSize ="18sp" android:layout_height ="wrap_content" /> <EditText android:maxLength ="6" android:id ="@+id/pwd_vertical_code" android:layout_width ="0dp" android:layout_weight ="1" android:hint ="请输入密码" android:textSize ="18sp" android:background ="@drawable/input_selector" android:layout_height ="wrap_content" /> <Button android:id ="@+id/get_vertical" android:layout_width ="wrap_content" android:text ="忘记密码" android:layout_height ="wrap_content" /> </LinearLayout > </LinearLayout > <CheckBox android:id ="@+id/remember_pwd" android:layout_width ="wrap_content" android:visibility ="invisible" android:text ="记住密码" android:layout_height ="wrap_content" /> <Button android:id ="@+id/vertical_code_test" android:layout_width ="match_parent" android:text ="登录" android:layout_height ="wrap_content" /> </LinearLayout >
登录页面代码逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 import androidx.activity.result.ActivityResult;import androidx.activity.result.ActivityResultCallback;import androidx.activity.result.ActivityResultLauncher;import androidx.activity.result.contract.ActivityResultContracts;import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.view.View;import android.widget.Button;import android.widget.CheckBox;import android.widget.EditText;import android.widget.RadioButton;import android.widget.TextView;import android.widget.Toast;import java.util.Random;public class Login extends AppCompatActivity implements View .OnClickListener { private RadioButton pwd_button; private RadioButton vertical_button; private TextView textView_pwd; private Button get_vertical; private EditText editText_pwd; private CheckBox checkBox; private EditText editText; private ActivityResultLauncher<Intent> register; private Button vertical_code_test; private String verifyCode; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_login); pwd_button = findViewById(R.id.pwd_login); vertical_button = findViewById(R.id.vertical_login); textView_pwd = findViewById(R.id.tx_pwd); editText_pwd = findViewById(R.id.pwd_vertical_code); vertical_code_test = findViewById(R.id.vertical_code_test); get_vertical = findViewById(R.id.get_vertical); checkBox = findViewById(R.id.remember_pwd); pwd_button.setOnClickListener(this ); vertical_button.setOnClickListener(this ); get_vertical.setOnClickListener(this ); editText = findViewById(R.id.et_phone); editText.addTextChangedListener(new HideTextWatcher (editText,11 )); editText_pwd.addTextChangedListener(new HideTextWatcher (editText_pwd,6 )); vertical_code_test.setOnClickListener(this ); register = registerForActivityResult(new ActivityResultContracts .StartActivityForResult(), new ActivityResultCallback <ActivityResult>() { @Override public void onActivityResult (ActivityResult result) { Intent data = result.getData(); if (data != null && result.getResultCode() == Activity.RESULT_OK){ String new_pwd = data.getStringExtra("new_pwd" ); AlertDialog.Builder builder = new AlertDialog .Builder(Login.this ); builder.setTitle("密码" ); builder.setMessage("密码是" + new_pwd); builder.setPositiveButton("知道了" ,null ); } } }); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.pwd_login: pwd_button.setChecked(true ); textView_pwd.setText("登录密码" ); editText_pwd.setHint("请输入密码" ); get_vertical.setText("忘记密码" ); checkBox.setVisibility(View.VISIBLE); break ; case R.id.vertical_login: vertical_button.setChecked(true ); textView_pwd.setText("手机号码" ); editText_pwd.setHint("请输入验证码" ); get_vertical.setText("获取验证码" ); checkBox.setVisibility(View.GONE); break ; case R.id.get_vertical: String phone = editText.getText().toString(); if (phone.length() < 11 ){ Toast.makeText(this ,"请输入正确的手机号" ,Toast.LENGTH_SHORT); return ; } if (pwd_button.isChecked()){ Intent intent = new Intent (this ,ForgetPwd.class); intent.putExtra("phone" ,phone); register.launch(intent); } else if (vertical_button.isChecked()) { verifyCode = String.format("0%6d" ,new Random ().nextInt(999999 )); AlertDialog.Builder builder = new AlertDialog .Builder(this ); builder.setTitle("请记住验证码" ); builder.setMessage("手机" + phone + ",本次验证码为" + verifyCode); builder.setPositiveButton("好的" ,null ); AlertDialog alertDialog = builder.create(); alertDialog.show(); } break ; case R.id.vertical_code_test: if (vertical_button.isChecked()){ if (verifyCode.equals(editText_pwd)){ Toast.makeText(this ,"login success" ,Toast.LENGTH_SHORT); }else Toast.makeText(this ,"验证码错误" ,Toast.LENGTH_SHORT); } } } private class HideTextWatcher implements TextWatcher { private EditText mView; private int mLength; public HideTextWatcher (EditText et, int maxLength) { this .mView = et; this .mLength = maxLength; } @Override public void beforeTextChanged (CharSequence s, int start, int count, int after) { } @Override public void onTextChanged (CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged (Editable s) { if (s.toString().length() == mLength){ ViewUtil.hideOnInputMethod(Login.this ,mView); } } } }
忘记密码页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:padding ="10dp" android:layout_height ="match_parent" > <LinearLayout android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:textColor ="@color/black" android:textSize ="17sp" android:text ="输入新密码:" android:layout_height ="wrap_content" /> <EditText android:id ="@+id/new_pwd" android:layout_width ="0dp" android:layout_weight ="1" android:hint ="请输入新密码" android:layout_height ="wrap_content" /> </LinearLayout > <LinearLayout android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:textColor ="@color/black" android:textSize ="17sp" android:text ="确认新密码:" android:layout_height ="wrap_content" /> <EditText android:id ="@+id/confirm_new_pwd" android:layout_width ="0dp" android:layout_weight ="1" android:hint ="请再次输入新密码" android:layout_height ="wrap_content" /> </LinearLayout > <LinearLayout android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:textColor ="@color/black" android:textSize ="17sp" android:text ="验证码:" android:layout_height ="wrap_content" /> <EditText android:id ="@+id/vertical_code_forget" android:layout_width ="0dp" android:layout_weight ="1" android:hint ="请输入验证码" android:layout_height ="wrap_content" /> <Button android:id ="@+id/get_vertical_code" android:layout_width ="wrap_content" android:text ="获取验证码" android:layout_height ="wrap_content" /> </LinearLayout > <Button android:id ="@+id/pwd_confirm" android:text ="确定" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout >
忘记密码相关逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.Toast;import java.util.Random;public class ForgetPwd extends AppCompatActivity implements View .OnClickListener { private String mPhone; private Button button; private EditText confirm_new_pwd; private EditText new_pwd; private String verticalCode; private EditText vertical_code_forget; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_forget_pwd); button = findViewById(R.id.pwd_confirm); confirm_new_pwd = findViewById(R.id.confirm_new_pwd); new_pwd = findViewById(R.id.new_pwd); vertical_code_forget = findViewById(R.id.vertical_code_forget); button.setOnClickListener(this ); mPhone = getIntent().getStringExtra("phone" ); Button get_vertical_code = findViewById(R.id.get_vertical_code); get_vertical_code.setOnClickListener(this ); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.get_vertical_code: verticalCode = String.format("0%6d" , new Random ().nextInt(999999 )); AlertDialog.Builder builder = new AlertDialog .Builder(this ); builder.setTitle("验证码" ); builder.setMessage("验证码为" + verticalCode); builder.setPositiveButton("知道了" ,null ); break ; case R.id.pwd_confirm: if (!new_pwd.getText().toString().equals(confirm_new_pwd.getText().toString())){ Toast.makeText(this ,"密码与确认密码不一致" ,Toast.LENGTH_SHORT); return ; } if (!verticalCode.equals(vertical_code_forget.getText().toString())){ Toast.makeText(this ,"验证码不正确" ,Toast.LENGTH_SHORT); return ; } Toast.makeText(this ,"密码修改成功" ,Toast.LENGTH_SHORT); Intent intent = new Intent (); intent.putExtra("new_pwd" ,new_pwd.getText().toString()); setResult(Activity.RESULT_OK,intent); finish(); break ; } } }
输入框背景
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8" ?> <shape xmlns:android ="http://schemas.android.com/apk/res/android" > <solid android:color ="#FFFDFD" /> <stroke android:width ="1dp" android:color ="#aaaaaa" /> <corners android:radius ="10dp" /> </shape >
数据存储 共享参数SharedPreferences
SharedPreferences是Android的一个轻量级存储工具,采用的存储结构是Key-Value的键值对方式。
共享参数的存储介质是符合XML规范的配置文件。保存路径是:/data/data/应用包名/shared_prefs/文件名.xml
共享参数主要适用于如下场合:
简单且孤立的数据。若是复杂且相互间有关的数据,则要保存在数据库中。
文本形式的数据。若是二进制数据,则保存在文件中。
需要持久化存储的数据。在App退出后再次启动时,之前保存的数据仍然有效。
实际开发中,共享参数经常存储的数据有App的个性化配置信息、用户使用App的行为信息、临时需要保存的片段信息等
用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:padding ="10dp" android:layout_height ="match_parent" > <LinearLayout android:layout_marginBottom ="10dp" android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:text ="姓名:" android:textSize ="18sp" android:layout_height ="wrap_content" /> <EditText android:id ="@+id/name" android:layout_width ="0dp" android:layout_weight ="1" android:background ="@drawable/input_shape" android:hint ="请输入姓名" android:layout_height ="wrap_content" /> </LinearLayout > <LinearLayout android:layout_width ="match_parent" android:orientation ="horizontal" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:text ="年龄:" android:textSize ="18sp" android:layout_height ="wrap_content" /> <EditText android:id ="@+id/age" android:background ="@drawable/input_shape" android:layout_width ="0dp" android:layout_weight ="1" android:hint ="请输入年龄" android:layout_height ="wrap_content" /> </LinearLayout > <CheckBox android:id ="@+id/marry" android:layout_width ="wrap_content" android:text ="已婚" android:layout_height ="wrap_content" /> <Button android:id ="@+id/save_share" android:layout_width ="match_parent" android:text ="保存到共享参数" android:layout_height ="wrap_content" /> <TextView android:layout_width ="wrap_content" android:id ="@+id/show_nameAndAge" android:layout_height ="wrap_content" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import androidx.appcompat.app.AppCompatActivity;import android.content.Context;import android.content.SharedPreferences;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.CheckBox;import android.widget.EditText;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View .OnClickListener { private EditText age; private EditText name; private CheckBox marry; private Button save_share; private SharedPreferences config; private TextView show_nameAndAge; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); age = findViewById(R.id.age); name = findViewById(R.id.name); marry = findViewById(R.id.marry); save_share = findViewById(R.id.save_share); save_share.setOnClickListener(this ); config = getSharedPreferences("config" , Context.MODE_PRIVATE); show_nameAndAge = findViewById(R.id.show_nameAndAge); reload(); } private void reload () { String names = config.getString("name" , "" ); int ages = config.getInt("age" , 0 ); if (!names.equals("" ) && ages != 0 ){ show_nameAndAge.setText("name is " + names + "age is " + ages); } } @Override public void onClick (View v) { String age = this .age.getText().toString(); String name = this .name.getText().toString(); SharedPreferences.Editor edit = config.edit(); edit.putString("name" ,name); edit.putInt("age" , Integer.parseInt(age)); edit.putBoolean("is_marry" ,marry.isChecked()); edit.commit(); } }
存储卡的文件操作 私有存储空间与公共存储空间 Android把外部存储分成了两块区域,一块是所有应用均可访问的公共空间,另一块是只有应用自己才可访问的私有空间。
如果运行后没有显示,可能是没有权限的问题,访问不到的同学,新增一个Android 12(API31)的虚拟手机,在里面运行项目就能看到了
具体示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:padding ="10dp" android:layout_height ="match_parent" > <LinearLayout android:orientation ="horizontal" android:layout_width ="match_parent" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:textSize ="18sp" android:text ="姓名:" android:layout_height ="wrap_content" /> <EditText android:id ="@+id/input_name" android:paddingLeft ="5dp" android:background ="@drawable/input_background" android:layout_width ="0dp" android:layout_weight ="1" android:hint ="请输入姓名" android:layout_height ="wrap_content" /> </LinearLayout > <Button android:id ="@+id/read_content" android:layout_width ="match_parent" android:text ="读取" android:layout_height ="wrap_content" /> <Button android:id ="@+id/write_content" android:layout_width ="match_parent" android:text ="写入" android:layout_height ="wrap_content" /> <TextView android:id ="@+id/show_content" android:textSize ="20sp" android:layout_gravity ="center" android:layout_width ="wrap_content" android:text ="展示" android:layout_height ="wrap_content" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.os.Environment;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import android.widget.Toast;import java.io.File;public class MainActivity5 extends AppCompatActivity implements View .OnClickListener { private EditText input_name; private Button write_content; private Button read_content; private TextView show_content; private String path; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main5); input_name = findViewById(R.id.input_name); read_content = findViewById(R.id.read_content); write_content = findViewById(R.id.write_content); show_content = findViewById(R.id.show_content); read_content.setOnClickListener(this ); write_content.setOnClickListener(this ); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.read_content: String name = input_name.getText().toString(); String directory = null ; directory = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(); String fileName = System.currentTimeMillis() + ".txt" ; path = directory + File.separatorChar + fileName; Log.d("ning" ,path); FileUtil.saveText(path,name); Toast.makeText(this ,"保存成功" ,Toast.LENGTH_SHORT); break ; case R.id.write_content: String content = FileUtil.openText(path); show_content.setText(content); break ; } } }
文件读写工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;public class FileUtil { public static void saveText (String path,String txt) { BufferedWriter os = null ; try { os = new BufferedWriter (new FileWriter (path)); } catch (IOException e) { e.printStackTrace(); } finally { try { if (os != null ) { os.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static String openText (String path) { BufferedReader is = null ; StringBuilder stringBuilder = new StringBuilder (); try { is = new BufferedReader (new FileReader (path)); String line = null ; while ((line = is.readLine()) != null ){ stringBuilder.append(line); } }catch (Exception e) { e.printStackTrace(); } finally { try { if (is != null ) { is.close(); } } catch (IOException e) { e.printStackTrace(); } } return stringBuilder.toString(); } }
在存储卡中读写图片文件
Android的位图工具是Bitmap,App读写Bitmap可以使用性能更好的BufferedOutputStream和BufferedInputStream。
Android还提供了BitmapFactory工具用于读取各种来源的图片,相关方法如下:
decodeResource:该方法可从资源文件中读取图片信息。
decodeFile:该方法可将指定路径的图片读取到Bitmap对象。
decodeStream:该方法从输入流中读取位图数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <Button android:id ="@+id/save" android:layout_width ="match_parent" android:text ="保存" android:layout_height ="wrap_content" /> <Button android:id ="@+id/read" android:layout_width ="match_parent" android:text ="读取" android:layout_height ="wrap_content" /> <ImageView android:id ="@+id/img_view" android:layout_width ="match_parent" android:scaleType ="fitCenter" android:layout_height ="400dp" /> </LinearLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import androidx.appcompat.app.AppCompatActivity;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.view.View;import android.widget.Button;import android.widget.ImageView;import android.widget.Toast;import java.io.File;public class MainActivity6 extends AppCompatActivity implements View .OnClickListener { private String path; private ImageView imageView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main6); Button read = findViewById(R.id.read); Button save = findViewById(R.id.save); imageView = findViewById(R.id.img_view); read.setOnClickListener(this ); save.setOnClickListener(this ); } @Override public void onClick (View v) { switch (v.getId()){ case R.id.save: path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + System.currentTimeMillis() + File.separatorChar + ".jpeg" ; Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.bg); FileUtil.saveImg(path,b1); Toast.makeText(this ,"保存成功" ,Toast.LENGTH_SHORT); break ; case R.id.read: imageView.setImageURI(Uri.parse(path)); break ; } } }
读写图片的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 import android.graphics.Bitmap;import android.graphics.BitmapFactory;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;public class FileUtil { public static void saveText (String path,String txt) { BufferedWriter os = null ; try { os = new BufferedWriter (new FileWriter (path)); } catch (IOException e) { e.printStackTrace(); } finally { try { if (os != null ) { os.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static String openText (String path) { BufferedReader is = null ; StringBuilder stringBuilder = new StringBuilder (); try { is = new BufferedReader (new FileReader (path)); String line = null ; while ((line = is.readLine()) != null ){ stringBuilder.append(line); } }catch (Exception e) { e.printStackTrace(); } finally { try { if (is != null ) { is.close(); } } catch (IOException e) { e.printStackTrace(); } } return stringBuilder.toString(); } public static void saveImg (String path, Bitmap b1) { FileOutputStream fos = null ; try { fos = new FileOutputStream (path); b1.compress(Bitmap.CompressFormat.JPEG,100 ,fos); }catch (Exception e){ e.printStackTrace(); }finally { try { if (fos != null ) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static Bitmap openImg (String path) { Bitmap bitmap = null ; FileInputStream fis = null ; try { fis = new FileInputStream (path); bitmap = BitmapFactory.decodeStream(fis); return bitmap; }catch (Exception e){ e.printStackTrace(); }finally { try { if (fis != null ) { fis.close(); } } catch (IOException e) { e.printStackTrace(); } } return null ; } }
高级控件 下拉列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <TextView android:layout_width ="match_parent" android:text ="下拉模式的列表框" android:layout_height ="wrap_content" /> <Spinner android:id ="@+id/spinner_dropdown" android:layout_width ="match_parent" android:spinnerMode ="dropdown" android:layout_height ="wrap_content" /> </LinearLayout >
适配器Adapter 适配器负责从数据集合中取出对应的数据显示到条目布局上。
数组适配器ArrayAdapter
最简单的适配器,只展示一行文字。
运用数组适配器分成下列步骤:
编写列表项的XML文件,内部布局只有一个TextView标签
调用ArrayAdapter的构造方法,填入待展现的字符串数组,以及列表项的XML文件(R.layout.item_select)
调用下拉框控件的setAdapter方法,传入第二步得到的适配器实例
构建对应的列表项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="utf-8" ?> <TextView xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" android:orientation ="vertical" android:layout_width ="match_parent" android:gravity ="center" android:textColor ="#0000ff" tools:text ="火星" android:layout_height ="50dp" > </TextView >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.AdapterView;import android.widget.ArrayAdapter;import android.widget.Spinner;import android.widget.Toast;public class MainActivity extends AppCompatActivity implements AdapterView .OnItemSelectedListener { private Spinner spinner; private final static String[] starArray = {"1" ,"2" ,"3" }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); spinner = findViewById(R.id.spinner_dropdown); ArrayAdapter<String> arrayAdapter = new ArrayAdapter <>(this , R.layout.item_selector, starArray); spinner.setAdapter(arrayAdapter); spinner.setSelection(0 ); spinner.setOnItemSelectedListener(this ); } @Override public void onItemSelected (AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this ,"当前被点击的是" + starArray[position],Toast.LENGTH_SHORT).show(); } @Override public void onNothingSelected (AdapterView<?> parent) { } }
并且,当Spinner为dialog模式时,还可以设置标题
1 2 3 4 5 6 7 <Spinner android:id ="@+id/spinner_dropdown" android:layout_width ="match_parent" android:spinnerMode ="dialog" android:layout_height ="wrap_content" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.AdapterView;import android.widget.ArrayAdapter;import android.widget.Spinner;import android.widget.Toast;public class MainActivity extends AppCompatActivity implements AdapterView .OnItemSelectedListener { private Spinner spinner; private final static String[] starArray = {"1" ,"2" ,"3" }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); spinner = findViewById(R.id.spinner_dropdown); ArrayAdapter<String> arrayAdapter = new ArrayAdapter <>(this , R.layout.item_selector, starArray); spinner.setAdapter(arrayAdapter); spinner.setPrompt("标题" ); spinner.setSelection(0 ); spinner.setOnItemSelectedListener(this ); } @Override public void onItemSelected (AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this ,"当前被点击的是" + starArray[position],Toast.LENGTH_SHORT).show(); } @Override public void onNothingSelected (AdapterView<?> parent) { } }
简单适配器SimpleAdapter SimpleAdapter允许在列表项中同时展示文本与图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:orientation ="vertical" android:layout_height ="match_parent" > <TextView android:layout_width ="match_parent" android:textSize ="17sp" android:text ="SimAdaptor" android:gravity ="center" android:layout_height ="wrap_content" /> <Spinner android:id ="@+id/simple_spinner" android:layout_width ="match_parent" android:spinnerMode ="dialog" android:layout_height ="wrap_content" /> </LinearLayout >
对应的每一项数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="horizontal" xmlns:tools ="http://schemas.android.com/tools" android:layout_marginBottom ="10dp" android:layout_width ="match_parent" android:layout_height ="wrap_content" > <ImageView android:id ="@+id/iv_item" android:layout_width ="0dp" android:layout_weight ="1" tools:src ="@drawable/ic_launcher_background" android:layout_height ="50dp" /> <TextView android:id ="@+id/tv_item" android:layout_width ="0dp" android:layout_weight ="3" tools:text ="地球" android:textSize ="17sp" android:textColor ="#ff0000" android:gravity ="center" android:layout_height ="wrap_content" /> </LinearLayout >
功能代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.AdapterView;import android.widget.SimpleAdapter;import android.widget.Spinner;import android.widget.Toast;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class MainActivity2 extends AppCompatActivity implements AdapterView .OnItemSelectedListener { private Spinner spinner; public static final int [] iconArray = { R.drawable.ic_launcher_background, R.drawable.ic_launcher_background }; public static final String[] nameArray = { "1" ,"2" }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main2); spinner = findViewById(R.id.simple_spinner); List<Map<String,Object>> list = new ArrayList <>(); for (int i = 0 ; i < iconArray.length; i++) { Map<String,Object> item = new HashMap <>(); item.put("icon" ,iconArray[i]); item.put("name" ,nameArray[i]); list.add(item); } SimpleAdapter simpleAdapter = new SimpleAdapter (this , list, R.layout.item_simple, new String []{"icon" , "name" }, new int []{R.id.iv_item, R.id.tv_item}); spinner.setAdapter(simpleAdapter); spinner.setPrompt("标题" ); spinner.setSelection(0 ); spinner.setOnItemSelectedListener(this ); } @Override public void onItemSelected (AdapterView<?> parent, View view, int position, long id) { Toast.makeText(this ,"选中了" + nameArray[position],Toast.LENGTH_SHORT).show(); } @Override public void onNothingSelected (AdapterView<?> parent) { } }
广播 收发标准广播 广播的收发过程分为三个步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <Button android:id ="@+id/btn_send_standard" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="发送标准广播" android:textSize ="17sp" /> </LinearLayout >
发送标准广播及开关广播接收器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.view.View;import android.widget.Button;import fun.eastwind.myapplication.receiver.StandardReceiver;public class MainActivity extends AppCompatActivity implements View .OnClickListener { private StandardReceiver standardReceiver; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_send_standard).setOnClickListener(this ); } @Override public void onClick (View v) { Intent intent = new Intent (StandardReceiver.STANDARD_ACTION); sendBroadcast(intent); } @Override protected void onStart () { super .onStart(); standardReceiver = new StandardReceiver (); IntentFilter filter = new IntentFilter (StandardReceiver.STANDARD_ACTION); registerReceiver(standardReceiver,filter); } @Override protected void onStop () { super .onStop(); unregisterReceiver(standardReceiver); } }
定义广播接收器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;public class StandardReceiver extends BroadcastReceiver { public static final String STANDARD_ACTION = "content" ; @Override public void onReceive (Context context, Intent intent) { if (intent != null && intent.getAction().equals(STANDARD_ACTION)){ Log.d("ning" ,"收到一个标准广播" ); } } }
有序广播 标准广播是一种无序广播,无序广播是没有顺序的,都在同一时间发出
一个广播存在多个接收器,这些接收器需要排队收听广播,这意味着该广播是条有序广播。
先收到广播的接收器A,即可让其他接收器继续收听广播也可中断广播不让其他接收器收听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <Button android:id ="@+id/btn_send_standard_order" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="发送有序广播" android:textSize ="17sp" /> </LinearLayout >
发送广播及开关广播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.view.View;import android.widget.Button;import fun.eastwind.myapplication.receiver.OrderReceiverA;import fun.eastwind.myapplication.receiver.OrderReceiverB;import fun.eastwind.myapplication.receiver.StandardReceiver;public class MainActivity3 extends AppCompatActivity implements View .OnClickListener { public static final String STANDARD_ACTION = "content" ; private OrderReceiverA orderReceiverA; private OrderReceiverB orderReceiverB; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main3); findViewById(R.id.btn_send_standard_order).setOnClickListener(this ); } @Override public void onClick (View v) { Intent intent = new Intent (STANDARD_ACTION); sendOrderedBroadcast(intent,null ); } @Override protected void onStart () { super .onStart(); orderReceiverA = new OrderReceiverA (); IntentFilter intentFilter = new IntentFilter (STANDARD_ACTION); intentFilter.setPriority(8 ); registerReceiver(orderReceiverA,intentFilter); orderReceiverB = new OrderReceiverB (); IntentFilter intentFilter2 = new IntentFilter (STANDARD_ACTION); intentFilter2.setPriority(10 ); registerReceiver(orderReceiverB,intentFilter2); } @Override protected void onStop () { super .onStop(); unregisterReceiver(orderReceiverA); unregisterReceiver(orderReceiverB); } }
广播A与广播B
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;import fun.eastwind.myapplication.MainActivity3;public class OrderReceiverA extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { if (intent != null && intent.getAction().equals(MainActivity3.STANDARD_ACTION)){ Log.d("ning" ,"接收器A接收到了广播" ); } } }
广播B的优先级比广播A高,并且它终止了后面的广播继续进行接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;import fun.eastwind.myapplication.MainActivity3;public class OrderReceiverB extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { if (intent != null && intent.getAction().equals(MainActivity3.STANDARD_ACTION)){ Log.d("ning" ,"接收器B接收到了广播" ); abortBroadcast(); } } }
广播的静态注册 在代码中注册接收器,该方法被称为动态注册。
在AndroidManifest.xml中注册接收器,该方式被称作静态注册。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" > <Button android:id ="@+id/btn_send_standard_static" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="发送振动广播" android:textSize ="17sp" /> </LinearLayout >
发送广播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName;import android.content.Intent;import android.os.Bundle;import android.view.View;public class BroadStaticActivity extends AppCompatActivity implements View .OnClickListener { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_broad_static); findViewById(R.id.btn_send_standard_static).setOnClickListener(this ); } @Override public void onClick (View v) { String fullName = "fun.eastwind.myapplication.receiver.MyReceiver" ; Intent content = new Intent ("content" ); ComponentName componentName = new ComponentName (this , fullName); content.setComponent(componentName); sendBroadcast(content); } }
接收广播,并振动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.os.Vibrator;import android.util.Log;public class MyReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { if (intent != null && intent.getAction().equals("content" )){ Log.d("ning" ,"震动" ); Vibrator vb = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); vb.vibrate(500 ); } } }
在AndroidManifest.xml中静态注册
1 2 3 4 5 6 7 <receiver android:name =".receiver.MyReceiver" android:exported ="true" > <intent-filter > <action android:name ="content" /> </intent-filter > </receiver >
系统分钟到达广播 发送广播和停止广播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import fun.eastwind.myapplication.receiver.TimeReceiver;public class SystemMinuteActivity extends AppCompatActivity { private TimeReceiver timeReceiver; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_system_minute); } @Override protected void onStart () { super .onStart(); timeReceiver = new TimeReceiver (); IntentFilter intentFilter = new IntentFilter (Intent.ACTION_TIME_TICK); registerReceiver(timeReceiver,intentFilter); } @Override protected void onStop () { super .onStop(); unregisterReceiver(timeReceiver); } }
接收广播
1 2 3 4 5 6 7 8 9 10 11 12 13 import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;public class TimeReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { if (intent != null ){ Log.d("ning" ,"收到了分钟到达广播" ); } } }
监听系统广播
接收分钟到达广播
接收网络变更广播
android.net.conn.CONNECTIVITY_CHANGE
使用方法与分钟到达广播相同
定时管理器AlarmManager
Android提供了专门的定时管理器AlarmManager,它利用系统闹钟定时发送广播,常见方法:
set:设置一次性定时器。
setAndAllowWhiledle:设置一次性定时器,即使设备处于空闲状态,也会保证执行定时器。
setRepeating:设置重复定时器,但系统不保证按时发送广播。
cancel:取消指定延迟意图的定时器。
广播接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import android.app.AlarmManager;import android.app.PendingIntent;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.os.Build;import android.util.Log;public class AlarmReceiver extends BroadcastReceiver { public static final String ALARM_ACTION = "context" ; private final Context mContext; public AlarmReceiver (Context context) { this .mContext = context; } @Override public void onReceive (Context context, Intent intent) { if (intent != null && intent.getAction().equals(ALARM_ACTION)){ Log.d("ning" ,"收到闹钟" ); sendAlarm(); } } public void sendAlarm () { Intent intent = new Intent (ALARM_ACTION); PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,0 ,intent,PendingIntent.FLAG_IMMUTABLE); AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,1000 ,pendingIntent); }else { alarmManager.set(AlarmManager.RTC_WAKEUP,1000 ,pendingIntent); } } }
广播发送、开启、关闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.view.View;import android.widget.Button;import fun.eastwind.myapplication.receiver.AlarmReceiver;import fun.eastwind.myapplication.receiver.TimeReceiver;public class SystemMinuteActivity extends AppCompatActivity implements View .OnClickListener { private TimeReceiver timeReceiver; private AlarmReceiver alarmReceiver; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_system_minute); Button button = findViewById(R.id.btn_alarm); button.setOnClickListener(this ); } @Override public void onClick (View v) { } @Override protected void onStart () { super .onStart(); alarmReceiver = new AlarmReceiver (getApplicationContext()); IntentFilter intentFilter = new IntentFilter (AlarmReceiver.ALARM_ACTION); registerReceiver(alarmReceiver,intentFilter); } @Override protected void onStop () { super .onStop(); unregisterReceiver(alarmReceiver); } }
OkHttp OkHttp的基础用法 先在Gradle中添加依赖,一般在模块的build.gradle中进行添加
1 2 3 dependencies { implementation 'com.squareup.okhttp3:okhttp:4.10.0' }
完成了 OkHttp 依赖的更新,还需要开启一下安卓的用户网络权限,在 AndroidManifest.xml 文件添加下面这句:
1 <uses-permission android:name ="android.permission.INTERNET" />
get 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void asyncGet (String url, String authorization, Callback callback, String... params) { Request.Builder requestBuilder = new Request .Builder().url(makeUrl(url, params)).get().tag(TAG); requestBuilder.addHeader("Accept" , "*/*" ); requestBuilder.addHeader("Connection" , "Keep-Alive" ); requestBuilder.addHeader("User-Agent" , "Android" ); requestBuilder.addHeader("Referer" , "http://www.fpliu.com" ); if (!TextUtils.isEmpty(authorization)) { requestBuilder.addHeader("Authorization" , authorization); } Request request = requestBuilder.build(); Call call = okHttpClient.newCall(request); call.enqueue(callback); }
post 示例一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public static void asyncPostForm (String url, String authorization, List<KV> params, Callback callback) { FormBody.Builder formBodyBuilder = new FormBody .Builder(); if (params != null ) { for (KV kv : params) { if (kv == null ) { continue ; } String key = kv.getKey(); String value = kv.getValue(); if (TextUtils.isEmpty(key)) { continue ; } formBodyBuilder.add(key, value); } } Request.Builder requestBuilder = new Request .Builder().url(makeUrl(url, params)).post(formBodyBuilder.build()).tag(TAG); requestBuilder.addHeader("Accept" , "*/*" ); requestBuilder.addHeader("Connection" , "Keep-Alive" ); requestBuilder.addHeader("User-Agent" , "Android" ); requestBuilder.addHeader("Referer" , "http://www.fpliu.com" ); if (!TextUtils.isEmpty(authorization)) { requestBuilder.addHeader("Authorization" , authorization); } Request request = requestBuilder.build(); Call call = okHttpClient.newCall(request); call.enqueue(callback); }
示例二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public static void asyncPostForm (String url, String authorization, Callback callback, String... params) { FormBody.Builder formBodyBuilder = new FormBody .Builder(); if (params != null ) { int length = params.length / 2 ; for (int i = 0 ; i < length; i++) { String key = params[i]; String value = params[i + 1 ]; if (TextUtils.isEmpty(key)) { continue ; } formBodyBuilder.add(key, value); } } Request.Builder requestBuilder = new Request .Builder().url(makeUrl(url, params)).post(formBodyBuilder.build()).tag(TAG); requestBuilder.addHeader("Accept" , "*/*" ); requestBuilder.addHeader("Connection" , "Keep-Alive" ); requestBuilder.addHeader("User-Agent" , "Android" ); requestBuilder.addHeader("Referer" , "http://www.fpliu.com" ); if (!TextUtils.isEmpty(authorization)) { requestBuilder.addHeader("Authorization" , authorization); } Request request = requestBuilder.build(); Call call = okHttpClient.newCall(request); call.enqueue(callback); }
JSON提交 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static void asyncPostJson (String url, String authorization, String json, Callback callback) { RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=UTF-8" ), json); Request.Builder requestBuilder = new Request .Builder().url(url).post(requestBody).tag(TAG); requestBuilder.addHeader("Accept" , "*/*" ); requestBuilder.addHeader("Connection" , "Keep-Alive" ); requestBuilder.addHeader("User-Agent" , "Android" ); requestBuilder.addHeader("Referer" , "http://www.fpliu.com" ); if (!TextUtils.isEmpty(authorization)) { requestBuilder.addHeader("Authorization" , authorization); } Request request = requestBuilder.build(); Call call = okHttpClient.newCall(request); call.enqueue(callback); }
文件提交 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public static void asyncPostFile (String url, String authorization, String filePath, Callback callback) { if (TextUtils.isEmpty(filePath) && requestCallBack != null ) { requestCallBack.onFailure(null , new FileNotFoundException ("文件路径不能为空" )); return ; } File file = new File (filePath); RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream" ), file); Request.Builder requestBuilder = new Request .Builder().url(url).post(requestBody).tag(TAG); requestBuilder.addHeader("Accept" , "*/*" ); requestBuilder.addHeader("Connection" , "Keep-Alive" ); requestBuilder.addHeader("User-Agent" , "Android" ); requestBuilder.addHeader("Referer" , "http://www.fpliu.com" ); if (!TextUtils.isEmpty(authorization)) { requestBuilder.addHeader("Authorization" , authorization); } Request request = requestBuilder.build(); Call call = okHttpClient.newCall(request); call.enqueue(callback); }
这里要特别注意,如果想要读取SD
卡里的文件,需要申请android.permission.READ_EXTERNAL_STORAGE
这个权限, 如果您的targetSdkVersion
被设置成了大于等于23
,您还必须要在代码中动态申请这个权限。为了方便, 可以使用RxPermissions 这个框架,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 RxPermissions rxPermissions = new RxPermissions (this );rxPermissions.requestEach(Manifest.permission.READ_EXTERNAL_STORAGE).subscribe(new Observer () { @Override public void onSubscribe (@NonNull Disposable d) { Log.d(TAG, "onSubscribe()" ); } @Override public void onNext (@NonNull Permission permission) { Log.d(TAG, "onNext()" + permission); if (permission.granted) { OKHttpRequest.init(MainActivity.this ); OKHttpRequest.asyncPostFile("http://192.168.1.103/upload" , "" , "/sdcard/xx.txt" , new Callback () { @Override public void onFailure (Call call, IOException e) { Log.e(TAG, "onFailure()" , e); } @Override public void onResponse (Call call, Response response) throws IOException { Log.d(TAG, response.body().string()); } }); } else if (permission.shouldShowRequestPermissionRationale){ Toast.makeText(MainActivity.this , "您曾经选择过不授权,您想再授权,请到设置里进行授权" , Toast.LENGTH_SHORT).show(); } } @Override public void onError (@NonNull Throwable e) { Log.e(TAG, "onError()" , e); } @Override public void onComplete () { Log.d(TAG, "onComplete()" ); } });
Multipart提交 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public static void asyncPostMultipart (String url, String authorization, List<MultipartBody.Part> parts, Callback callback) { MultipartBody.Builder multipartBodyBuilder = new MultipartBody .Builder().setType(MultipartBody.FORM); if (parts != null ) { for (MultipartBody.Part part : parts) { multipartBodyBuilder.addPart(part); } } Request.Builder requestBuilder = new Request .Builder().url(url).post(multipartBodyBuilder.build()).tag(TAG); requestBuilder.addHeader("Accept" , "*/*" ); requestBuilder.addHeader("Connection" , "Keep-Alive" ); requestBuilder.addHeader("User-Agent" , "Android" ); requestBuilder.addHeader("Referer" , "http://www.fpliu.com" ); if (!TextUtils.isEmpty(authorization)) { requestBuilder.addHeader("Authorization" , authorization); } Request request = requestBuilder.build(); Call call = okHttpClient.newCall(request); call.enqueue(callback); }
delete Delete
请求用于删除资源,不应该携带大量信息,所以,一般Delete
请求没有实体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;import java.io.IOException;public class OkHttpDeleteExample { public static void main (String[] args) { OkHttpClient client = new OkHttpClient (); Request request = new Request .Builder() .url("https://api.example.com/resource/1" ) .delete() .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException ("Unexpected code " + response); } System.out.println(response.body().string()); } catch (IOException e) { e.printStackTrace(); } } }
put PUT
请求与POST
请求在协议本身,除了请求方法本身的不同,其他方面没有任何差别。 从Restful
风格上讲:只是语义上不同,POST
用于创建资源,而PUT
用于更新资源。
所以将
修改为:
即可。