一、 Activity
1. Activity 的启动和结束
从当前页面跳到新页面,跳转代码如下:
- startActivity(new Intent(源页面.this, 目标页面.class));
从当前页面回到上一个页面,相当于关闭当前页面,返回代码如下:
- finish(); // 结束当前的活动页面
第一个页面XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/btn_act_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳到下个页面" />
</LinearLayout>
第一个页面Java:
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class ActStartActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "ning";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "ActStartActivity onCreate");
setContentView(R.layout.activity_act_start);
findViewById(R.id.btn_act_next).setOnClickListener(this);
}
@Override
public void onClick(View v) {
startActivity(new Intent(this,ActFinishActivity.class));
}
}
第二个页面XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_back"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="5dp"
android:src="@drawable/ic_back" />
<Button
android:id="@+id/btn_finish"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="完成" />
</LinearLayout>
第二个页面Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class ActFinishActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_finish);
findViewById(R.id.iv_back).setOnClickListener(this);
findViewById(R.id.btn_finish).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.iv_back || v.getId() == R.id.btn_finish) {
// 结束当前的活动页面
finish();
}
}
}
实现效果:点击第一个页面的按钮时,跳转到第二个页面,按返回键,或者点击第二个页面的左上角的箭头图标,或者点击第二个页面的上面的完成按钮,均可关闭第二个页面的、返回第一个页面。
2. Activity 的生命周期
onCreate:创建活动。把页面布局加载进内存,进入了初始状态。
onStart:开始活动。把活动页面显示在屏幕上,进入了就绪状态。
onResume:恢复活动。活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许用户输入文字等等。
onPause:暂停活动。页面进入暂停状态,无法与用户正常交互。
onStop:停止活动。页面将不在屏幕上显示。
onDestroy:销毁活动。回收活动占用的系统资源,把页面从内存中清除。
onRestart:重启活动。重新加载内存中的页面数据。
onNewIntent:重用已有的活动实例。
打开新页面的方法调用顺序为:
- onCreate→onStart→onResume
关闭旧页面的方法调用顺序为:
- onPause→onStop→onDestroy
3. Activity 的启动模式
某App先后打开两个活动,此时活动栈的变动情况如下图所示。
依次结束已打开的两个活动,此时活动栈的变动情况如下图所示。
-
在配置文件中指定启动模式
launchMode属性的取值说明见下表:
-
在代码里面设置启动标志
启动标志的取值说明如下:
- 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:跳转到新页面时,栈中的原有实例都被清空
4. 显式Intent和隐式Intent
Intent是各个组件之间信息沟通的桥梁,它用于Android各组件之间的通信,主要完成下列工作:
- 标明本次通信请求从哪里来、到哪里去、要怎么走。
- 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
- 发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容。
Intent的组成部分:
显式Intent,直接指定来源活动与目标活动,属于精确匹配。它有三种构建方式:
- 在Intent的构造函数中指定。
- 调用意图对象的setClass方法指定。
- 调用意图对象的setComponent方法指定。
// 1.在Intent的构造函数中指定
Intent intent = new Intent(this, ActFinishActivity.class);
// 2.调用意图对象的setClass方法指定
Intent intent = new Intent();
intent.setClass(this,ActFinishActivity.class);
// 3.调用意图对象的setComponent方法指定
ComponentName component = new ComponentName(this, ActFinishActivity.class);
intent.setComponent(component);
startActivity(intent);
隐式Intent,没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配。动作名称既可以通过setAction方法指定,也可以通过构造函数Intent(String action)直接生成意图对象。常见的系统动作如下表:
// 1. 设置意图动作为准备拨号
intent.setAction(Intent.ACTION_DIAL);
startActivity(intent);
// 2. 设置意图动作跳转到自己的activity
intent.setAction("android.intent.action.my");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
跳转到自己的activity时添加:
<android:exported="true"/>
<intent-filter>
<action android:name="android.intent.action.my" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
5. 向下一个Activity发送数据
Intent使用Bundle对象存放待传递的数据信息。
Bundle对象操作各类型数据的读写方法说明见下表:
- 在代码中发送消息包裹,调用意图对象的putExtras方法,即可存入消息包裹。
- 在代码中接收消息包裹,调用意图对象的getExtras方法,即可取出消息包裹。
存入消息Activity的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="今天的天气真不错" />
<Button
android:id="@+id/btn_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="发送以上文字" />
</LinearLayout>
存入消息Activity的Java:
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.dongnaoedu.chapter04.util.DateUtil;
public class ActSendActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tv_send;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_send);
tv_send = findViewById(R.id.tv_send);
findViewById(R.id.btn_send).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this, ActReceiveActivity.class);
// 创建一个新包裹
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_content", tv_send.getText().toString());
intent.putExtras(bundle);
startActivity(intent);
}
}
取出消息Activity的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_receive"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
取出消息Activity的Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class ActReceiveActivity extends AppCompatActivity {
private TextView tv_receive;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_receive);
tv_receive = findViewById(R.id.tv_receive);
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
tv_receive.setText(desc);
}
}
第一个Activity:
点击发送以上文字后:
6. 向上一个Activity返回数据
处理下一个页面的应答数据,详细步骤说明如下:
- 上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作;
- 下一个页面接收并解析请求数据,进行相应处理 ;
- 下一个页面在返回上一个页面时,打包应答数据并调用setResult方法返回数据包裹;
- 上一个页面重写方法onActivityResult,解析获得下一个页面的返回数据。
不过startActivityForResult已经废弃,现在使用registerForActivityResult。
第一个Activity的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_request"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="传送请求数据" />
<TextView
android:id="@+id/tv_response"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
第一个Activity的Java:
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.AppCompatActivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.dongnaoedu.chapter04.util.DateUtil;
public class ActRequestActivity extends AppCompatActivity implements View.OnClickListener {
private static final String mRequest = "你睡了吗?来我家睡吧";
private ActivityResultLauncher<Intent> register;
private TextView tv_response;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_request);
TextView tv_request = findViewById(R.id.tv_request);
tv_request.setText("待发送的消息为:" + mRequest);
tv_response = findViewById(R.id.tv_response);
findViewById(R.id.btn_request).setOnClickListener(this);
register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result != null) {
Intent intent = result.getData();
if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
Bundle bundle = intent.getExtras();
String response_time = bundle.getString("response_time");
String response_content = bundle.getString("response_content");
String desc = String.format("收到返回消息:\n应答时间为%s\n应答内容为%s", response_time, response_content);
// 把返回消息的详情显示在文本视图上
tv_response.setText(desc);
}
}
});
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this, ActResponseActivity.class);
// 创建一个新包裹
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_content", mRequest);
intent.putExtras(bundle);
register.launch(intent);
}
}
第二个Activity的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="返回应答数据" />
<TextView
android:id="@+id/tv_response"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
第二个Activity的Java:
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.dongnaoedu.chapter04.util.DateUtil;
public class ActResponseActivity extends AppCompatActivity implements View.OnClickListener {
private static final String mReponse = "我还没睡,我爸妈不在家。";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_response);
TextView tv_request = findViewById(R.id.tv_request);
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
// 把请求消息的详情显示在文本视图上
tv_request.setText(desc);
findViewById(R.id.btn_response).setOnClickListener(this);
TextView tv_response = findViewById(R.id.tv_response);
tv_response.setText("待返回的消息为:" + mReponse);
}
@Override
public void onClick(View v) {
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("response_time", DateUtil.getNowTime());
bundle.putString("response_content", mReponse);
intent.putExtras(bundle);
// 携带意图返回上一个页面。RESULT_OK表示处理成功
setResult(Activity.RESULT_OK, intent);
// 结束当前的活动页面
finish();
}
}
第一个Activity:
点击传送请求数据按钮后:
7. 利用资源文件配置字符串
res\values\strings.xml 可用来配置字符串形式的参数。
在活动页面的Java代码中,调用getString方法即可根据“R.string.参数名称”获得指定参数的字符串值。
在res\values\strings.xml中:
<resources>
<string name="weather_str">晴天</string>
</resources>
Activity的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_resource"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Activity的Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class ReadStringActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_read_string);
TextView tv_resource = findViewById(R.id.tv_resource);
// 从strings.xml获取名叫weather_str的字符串值
String value = getString(R.string.weather_str);
tv_resource.setText(value);
}
}
效果如下:
8. 利用元数据传递配置信息
元数据是一种描述其他数据的数据,它相当于描述固定活动的参数信息。
在activity节点内部添加meta-data标签,通过属性name指定元数据的名称,通过属性value指定元数据的值。
在Java代码中,获取元数据信息的步骤分为下列三步:
- 调用getPackageManager方法获得当前应用的包管理器;
- 调用包管理器的getActivityInfo方法获得当前活动的信息对象;
- 活动信息对象的metaData是Bundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值。
在AndroidManifest.xml中:
<activity
android:name=".MetaDataActivity"
android:exported="true">
<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="阴天" />
</activity>
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_meta"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Java:
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 MetaDataActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meta_data);
TextView tv_meta = findViewById(R.id.tv_meta);
// 获取应用包管理器
PackageManager pm = getPackageManager();
try {
// 从应用包管理器中获取当前的活动信息
ActivityInfo info = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
// 获取活动附加的元数据信息
Bundle bundle = info.metaData;
String weather = bundle.getString("weather");
tv_meta.setText(weather);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
效果如下:
二、中级控件
1. 图形Drawable
Drawable 类型表达了各种各样的图形,包括图片、色块、画板、背景等。
包含图片在内的图形文件放在res目录的各个drawable目录下,其中drawable目录一般保存描述性的XML文件,而图片文件一般放在具体分辨率的drawable目录下。
各视图的background属性、ImageView 和 ImageButton的src属性、TextView和Button四个方向的drawable系列属性都可以引用图形文件。
Shape图形又称形状图形,它用来描述常见的几何形状,包括矩形、圆角矩形、圆形、椭圆等等。
形状图形的定义文件是以shape标签为根节点的XML描述文件,它支持四种类型的形状:
- rectangle:矩形。默认值
- oval:椭圆。此时corners节点会失效
- line:直线。此时必须设置stroke节点,不然会报错
- ring:圆环
除了根节点shape标签,形状图形还拥有下列规格标签:
-
size(尺寸),它描述了形状图形的宽高尺寸。
-
stroke(描边),它描述了形状图形的描边规格。
-
corners(圆角),它描述了形状图形的圆角大小。
-
solid(填充),它描述了形状图形的填充色彩。
-
padding(间隔),它描述了形状图形与周围边界的间隔。
-
gradient(渐变),它描述了形状图形的颜色渐变。
在res/drawable下定义圆角矩形的XML:
<?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>
在res/drawable下定义椭圆的XML:
<?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>
页面XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/v_content"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="10dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_rect"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="圆角矩形背景" />
<Button
android:id="@+id/btn_oval"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="椭圆背景" />
</LinearLayout>
</LinearLayout>
Java代码:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class DrawableShapeActivity extends AppCompatActivity implements View.OnClickListener {
private View v_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drawable_shape);
v_content = findViewById(R.id.v_content);
findViewById(R.id.btn_rect).setOnClickListener(this);
findViewById(R.id.btn_oval).setOnClickListener(this);
// v_content的背景设置为圆角矩形
v_content.setBackgroundResource(R.drawable.shape_rect_gold);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_rect:
v_content.setBackgroundResource(R.drawable.shape_rect_gold);
break;
case R.id.btn_oval:
v_content.setBackgroundResource(R.drawable.shape_oval_rose);
break;
}
}
}
运行效果:
点击椭圆背景按钮后:
2. 状态列表图形selector
使用selector标签为根节点的XML描述文件,实现如下效果:
res/drawable下的XML(自定义按钮样式):
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/button_normal" />
</selector>
页面的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="5dp">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_nine_selector"
android:text="定制样式的按钮" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class DrawableStateActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drawable_state);
}
}
状态列表图形不仅用于按钮控件,还可用于其他拥有多种状态的控件。
3. 复选框CheckBox
CompoundButton类是抽象的复合按钮,由它派生而来的子类包括:复选框CheckBox、单选按钮RadioButton以及开关按钮Switch。 下图描述了复合按钮的继承关系:
CompoundButton 在 XML 文件中主要使用下面两个属性。
- checked:指定按钮的勾选状态,true表示勾选,false表示未勾选。默认未勾选。
- button:指定左侧勾选图标的图形资源。如果不指定就使用系统的默认图标。
CompoundButton 在 Java 代码中主要使用下列4种方法:
- setChecked:设置按钮的勾选状态。
- setButtonDrawable:设置左侧勾选图标的图形资源。
- setOnCheckedChangeListener:设置勾选状态变化的监听器。
- isChecked:判断按钮是否勾选。
res/drawable下的XML(自定义复选框样式):
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/check_choose" android:state_checked="true" />
<item android:drawable="@drawable/check_unchoose" />
</selector>
页面的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<CheckBox
android:id="@+id/ck_system"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="这是系统的CheckBox" />
<CheckBox
android:id="@+id/ck_custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:button="@drawable/checkbox_selector"
android:checked="true"
android:padding="5dp"
android:text="这个CheckBox换了图标" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.CheckBox;
import android.widget.CompoundButton;
public class CheckBoxActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_check_box);
CheckBox ck_system = findViewById(R.id.ck_system);
CheckBox ck_custom = findViewById(R.id.ck_custom);
ck_system.setOnCheckedChangeListener(this);
ck_custom.setOnCheckedChangeListener(this);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
String desc = String.format("您%s了这个CheckBox", isChecked ? "勾选" : "取消勾选");
buttonView.setText(desc);
}
}
运行效果:
取消勾选后:
4. 开关按钮Switch
Switch 是开关按钮,它在选中与取消选中时可展现的界面元素比复选框丰富。
Switch 控件新添加的XML属性说明如下:
- textOn:设置右侧开启时的文本。
- textOff:设置左侧关闭时的文本。
- track:设置开关轨道的背景。
- thumb:设置开关标识的图标。
系统默认的switch开关:
仿iOS的开关按钮:
- 借助状态列表图形StateListDrawable,分别定义已选中时候的“开”图形,以及未选中时候的“关”图形。
- 然后把CheckBox控件的background属性设置为该状态图形。
res/drawable下的XML(自定义开关按钮样式):
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/switch_on" android:state_checked="true" />
<item android:drawable="@drawable/switch_off" />
</selector>
页面的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_weight="1"
android:padding="5dp"
android:text="Switch开关:" />
<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/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="start" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
public class SwitchIOSActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {
private TextView tv_result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_switch_iosactivity);
CheckBox ck_status = findViewById(R.id.ck_status);
tv_result = findViewById(R.id.tv_result);
ck_status.setOnCheckedChangeListener(this);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
String desc = String.format("仿iOS开关的状态是%s", isChecked ? "开" : "关");
tv_result.setText(desc);
}
}
效果如下:
点击switch开关后:
5. 单选按钮RadioButton
单选按钮要在一组按钮中选择其中一项,并且不能多选,这要求有个容器确定这组按钮的范围,这个容器便是单选组RadioGroup。RadioGroup实质上是个布局,同一组RadioButton都要放在同一个RadioGroup节点下。除了RadioButton,也允许放置其他控件。
判断选中了哪个单选按钮,通常不是监听某个单选按钮,而是监听单选组的选中事件。 下面是RadioGroup常用的3个方法:
- check:选中指定资源编号的单选按钮。
- getCheckedRadioButtonId:获取选中状态单选按钮的资源编号。
- setOnCheckedChangeListener:设置单选按钮勾选变化的监听器。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="请选择您的性别" />
<RadioGroup
android:id="@+id/rg_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_male"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="男" />
<RadioButton
android:id="@+id/rb_female"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="女" />
</RadioGroup>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.RadioGroup;
import android.widget.TextView;
public class RadioHorizontalActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {
private TextView tv_result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_radio_horizontal);
RadioGroup rb_gender = findViewById(R.id.rg_gender);
tv_result = findViewById(R.id.tv_result);
rb_gender.setOnCheckedChangeListener(this);
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId){
case R.id.rb_male:
tv_result.setText("哇哦,你是个帅气的男孩");
break;
case R.id.rb_female:
tv_result.setText("哇哦,你是个漂亮的女孩");
break;
}
}
}
演示效果:
6. 编辑框EditText
EditText 是文本编辑框,用户可在此输入文本等信息。
EditText 的常用属性说明如下:
- inputType:指定输入的文本类型。若同时使用多种文本类型,则可使用竖线“|”把多种文本类型拼接起来。
- maxLength:指定文本允许输入的最大长度。
- hint:指定提示文本的内容。
- textColorHint:指定提示文本的颜色。
inputType的取值说明:
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="下面是登录信息" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"
android:inputType="text" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class EditSimpleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_simple);
}
}
效果如下:
输入文字后:
焦点变更监听器
编辑框点击两次后才会触发点击事件,因为第一次点击只触发焦点变更事件,第二次点击才触发点击事件。
若要判断是否切换编辑框输入,应当监听焦点变更事件,而非监听点击事件。
调用编辑框对象的setOnFocusChangeListener方法,即可在光标切换之时(获得光标和失去光标)触发焦点变更事件。
演示:手机号码未输满11位,就点击密码框,此时校验不通过,一边弹出提示文字,一边把焦点拉回手机框。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<EditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/editext_selector"
android:hint="请输入11位手机号码"
android:inputType="number"
android:maxLength="11" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/editext_selector"
android:hint="请输入6位密码"
android:inputType="numberPassword"
android:maxLength="6" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
public class EditFocusActivity extends AppCompatActivity implements View.OnFocusChangeListener {
private EditText et_phone;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_focus);
et_phone = findViewById(R.id.et_phone);
EditText et_password = findViewById(R.id.et_password);
et_password.setOnFocusChangeListener(this);
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
String phone = et_phone.getText().toString();
// 手机号码不足11位
if (TextUtils.isEmpty(phone) || phone.length() < 11) {
// 手机号码编辑框请求焦点,也就是把光标移回手机号码编辑框
et_phone.requestFocus();
Toast.makeText(this, "请输入11位手机号码", Toast.LENGTH_SHORT).show();
}
}
}
}
效果如下:
文本变化监听器
判断手机号输入满11位后自动关闭软键盘,或者密码输入满6位后自动关闭软键盘,此时要注册文本变化监听器。
达到指定位数便自动关闭键盘的功能,可以再分解为两个独立的功能点:
- 如何关闭软键盘;
- 如何判断已输入的文字达到指定位数
调用编辑框对象的 addTextChangedListener 方法即可注册文本监听器。 文本监听器的接口名称为 TextWatcher,该接口提供了3个监控方法,具体说明如下。
- beforeTextChanged:在文本改变之前触发。
- onTextChanged:在文本改变过程中触发。
- afterTextChanged:在文本改变之后触发。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<EditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:background="@drawable/editext_selector"
android:hint="输入11位时自动隐藏输入法"
android:inputType="text"
android:maxLength="11" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:background="@drawable/editext_selector"
android:hint="输入6位时自动隐藏输入法"
android:inputType="textPassword"
android:maxLength="6" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import com.dongnaoedu.chapter05.util.ViewUtil;
public class EditHideActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_hide);
EditText et_phone = findViewById(R.id.et_phone);
EditText et_password = findViewById(R.id.et_password);
et_phone.addTextChangedListener(new HideTextWatcher(et_phone, 11));
et_password.addTextChangedListener(new HideTextWatcher(et_password, 6));
}
// 定义一个编辑框监听器,在输入文本达到指定长度时自动隐藏输入法
private class HideTextWatcher implements TextWatcher {
// 声明一个编辑框对象
private EditText mView;
// 声明一个最大长度变量
private int mMaxLength;
public HideTextWatcher(EditText v, int maxLength) {
this.mView = v;
this.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 str = s.toString();
// 输入文本达到11位(如手机号码),或者达到6位(如登录密码)时关闭输入法
if (str.length() == mMaxLength) {
// 隐藏输入法软键盘
ViewUtil.hideOneInputMethod(EditHideActivity.this, mView);
}
}
}
}
监听文本位数自动关闭软键盘的演示效果:
7. 提醒对话框AlertDialog
AlertDialog 可以完成常见的交互操作,例如提示、确认、选择等功能。AlertDialog借助建造器 AlertDialog.Builder 才能完成参数设置。
调用建造器的 create 方法生成对话框实例,再调用对话框实例的show方法,在页面上弹出提醒对话框。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<Button
android:id="@+id/btn_alert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="弹出提醒对话框" />
<TextView
android:id="@+id/tv_alert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp" />
</LinearLayout>
Java:
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class AlertDialogActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tv_alert;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alert_dialog);
findViewById(R.id.btn_alert).setOnClickListener(this);
tv_alert = findViewById(R.id.tv_alert);
}
@Override
public void onClick(View v) {
// 创建提醒对话框的建造器
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 设置对话框的标题文本
builder.setTitle("尊敬的用户");
// 设置对话框的内容文本
builder.setMessage("你真的要卸载我吗?");
// 设置对话框的肯定按钮文本及其点击监听器
builder.setPositiveButton("残忍卸载", (dialog, which) -> {
tv_alert.setText("虽然依依不舍,但是只能离开了");
});
// 设置对话框的否定按钮文本及其点击监听器
builder.setNegativeButton("我再想想", (dialog, which) -> {
tv_alert.setText("让我再陪你三百六十五个日夜");
});
// 根据建造器构建提醒对话框对象
AlertDialog dialog = builder.create();
// 显示提醒对话框
dialog.show();
}
}
效果如下:
8. 日期对话框DatePickerDialog
日期选择器 DatePicker 可以让用户选择具体的年月日。 但 DatePicker 并非弹窗模式,而是在当前页面占据一块区域,并且不会自动关闭。
DatePickerDialog 相当于在 AlertDialog 上装载了 DatePicker,日期选择事件则由监听器OnDateSetListener负责响应,在该监听器的onDateSet方法中,开发者获取用户选择的具体日期,再做后续处理。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<Button
android:id="@+id/btn_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="请选择日期" />
<DatePicker
android:id="@+id/dp_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:calendarViewShown="false"
android:datePickerMode="spinner" />
<Button
android:id="@+id/btn_ok"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="确 定" />
<TextView
android:id="@+id/tv_date"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.app.DatePickerDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.DatePicker;
import android.widget.TextView;
import java.util.Calendar;
public class DatePickerActivity extends AppCompatActivity implements View.OnClickListener, DatePickerDialog.OnDateSetListener {
private DatePicker dp_date;
private TextView tv_date;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_date_picker);
findViewById(R.id.btn_ok).setOnClickListener(this);
findViewById(R.id.btn_date).setOnClickListener(this);
tv_date = findViewById(R.id.tv_date);
dp_date = findViewById(R.id.dp_date);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_ok:
String desc = String.format("您选择的日期是%d年%d月%d日", dp_date.getYear(), dp_date.getMonth() + 1, dp_date.getDayOfMonth());
tv_date.setText(desc);
break;
case R.id.btn_date:
// 获取日历的一个实例,里面包含了当前的年月日
/*Calendar calendar = Calendar.getInstance();
calendar.get(Calendar.YEAR);
calendar.get(Calendar.MONTH);
calendar.get(Calendar.DAY_OF_MONTH);*/
DatePickerDialog dialog = new DatePickerDialog(this, this, 2090, 5, 11);
// 显示日期对话框
dialog.show();
break;
}
}
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
String desc = String.format("您选择的日期是%d年%d月%d日", year, month + 1, dayOfMonth);
tv_date.setText(desc);
}
}
运行效果:
点击请选择日期按钮后:
9. 时间对话框TimePickerDialog
时间选择器 TimePicker 可以让用户选择具体的小时和分钟。TimePickerDialog 的用法类似 DatePickerDialog。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<Button
android:id="@+id/btn_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="请选择时间" />
<TimePicker
android:id="@+id/tp_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:timePickerMode="spinner" />
<Button
android:id="@+id/btn_ok"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="确 定" />
<TextView
android:id="@+id/tv_time"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.app.TimePickerDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.TimePicker;
import java.util.Calendar;
public class TimePickerActivity extends AppCompatActivity implements View.OnClickListener, TimePickerDialog.OnTimeSetListener {
private TimePicker tp_time;
private TextView tv_time;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_time_picker);
findViewById(R.id.btn_ok).setOnClickListener(this);
findViewById(R.id.btn_time).setOnClickListener(this);
tp_time = findViewById(R.id.tp_time);
tp_time.setIs24HourView(true);//设置24小时制
tv_time = findViewById(R.id.tv_time);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_ok:
String desc = String.format("您选择的时间是%d时%d分", tp_time.getHour(), tp_time.getMinute());
tv_time.setText(desc);
break;
case R.id.btn_time:
// 获取日历的一个实例,里面包含了当前的时分秒
Calendar calendar = Calendar.getInstance();
// 构建一个时间对话框,该对话框已经集成了时间选择器。
TimePickerDialog dialog = new TimePickerDialog(this,this,
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
true); // true表示24小时制,false表示12小时制
dialog.show();
break;
}
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
String desc = String.format("您选择的时间是%d时%d分", hourOfDay, minute);
tv_time.setText(desc);
}
}
效果如下:
点击请选择时间按钮后: