选择按钮
复选框 CheckBox
继承关系
CompoundButton
CompoundButton 在 XML 文件中主要使用下面两个属性。
checked:指定按钮的勾选状态,true表示勾选,false表示未勾选。默认未勾选
button:指定左侧勾选图标的图形资源。如果不指定就使用系统的默认图标
CompoundButton 在 Java 代码中主要使用下列4种方法。
setChecked:设置按钮的勾选状态。
setButtonDrawable:设置左侧勾选图标的图形资源。
setOnCheckedChangeListener:设置勾选状态变化的监听器。
isChecked:判断按钮是否勾
设置股癣状态的监听器,并且设置内容显示
public class CheckBoxActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_check_box); CheckBox system_checkbox = findViewById(R.id.system_checkbox); CheckBox custom_checkbox = findViewById(R.id.custom_checkbox); system_checkbox.setOnCheckedChangeListener(this); custom_checkbox.setOnCheckedChangeListener(this); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { String desc = String.format("你%s了这个CheckBox", isChecked ? "勾选" : "取消勾选"); buttonView.setText(desc); } }
开关按钮 switch
Switch 控件属性
extOn:设置右侧开启时的文本。
textOff:设置左侧关闭时的文本。
track:设置开关轨道的背景。
thumb:设置开关标识的图标
单选按钮 RedioButton
RadioGroup 实质上是个布局,同一组RadioButton都要放在同一个 RadioGroup 节点下
RadioGroup的常用方法
check:选中指定资源编号的单选按钮。
getCheckedRadioButtonId:获取选中状态单选按钮的资源编号。
setOnCheckedChangeListener:设置单选按钮勾选变化的监听器
<?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/select_gender" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dp"> <RadioButton android:id="@+id/sex_male" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="男" /> <RadioButton android:id="@+id/sex_female" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="女" /> </RadioGroup> <TextView android:id="@+id/show_sex" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" /> </LinearLayout>
public class RadioHorizaontalActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener { private TextView show_sex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_radio_horizaontal); RadioGroup select_gender = findViewById(R.id.select_gender); show_sex = findViewById(R.id.show_sex); select_gender.setOnCheckedChangeListener(this); } @Override public void onCheckedChanged(RadioGroup group, int checkedId) { switch (checkedId){ case R.id.sex_male: show_sex.setText("你是一个man"); break; case R.id.sex_female: show_sex.setText("你是一个women"); break; } } }
文本输入
编辑框 EditText
文本编辑框
EditText 一些方法
inputType:指定输入的文本类型。若同时使用多种文本类型,则可使用竖线“|”把多种文本类 型拼接起来。
l maxLength:指定文本允许输入的最大长度。
hint:指定提示文本的内容。
textColorHint:指定提示文本的颜色。
监听焦点变更事件
编辑框点击两次后才会触发点击事件,因为第一次点击只触发焦点变更事件,第二次点击 才触发点击事件
若要判断是否切换编辑框输入,应当监听焦点变更事件,而非监听点击事件。
调用编辑框对象的setOnFocusChangeListener方法,即可在光标切换之时(获得光标和 失去光标)触发焦点变更事件
监听输入手机号码
必须要满11 位,不够就不通过校验,一遍弹出提示文件,一边把焦点拉回手机框
<?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/phone_num" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:background="@drawable/edittext_slelect" android:hint="请输入手机号码" android:inputType="number" android:maxLength="11" /> <EditText android:id="@+id/input_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:background="@drawable/edittext_slelect" android:hint="请输入6位密码" android:inputType="numberPassword" android:textColor="@color/green" android:maxLength="6"/> <Button android:id="@+id/btn_login" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="登录" /> </LinearLayout>
public class EditFocusActivity extends AppCompatActivity implements View.OnFocusChangeListener { private TextView input_phone; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit_focus); input_phone = findViewById(R.id.phone_num); EditText password = findViewById(R.id.input_password); password.setOnFocusChangeListener(this); } @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { String phone = input_phone.getText().toString(); //提示手机号码不足11位 if (TextUtils.isEmpty(phone) || phone.length() < 11){ input_phone.requestFocus(); Toast.makeText(this, "请输入11位手机号码", Toast.LENGTH_SHORT).show(); } } } }
文本变化监听器
文本位数自动关闭键盘
调用编辑框对象的 addTextChangedListener 方法即可注册文本监听器。
文本监听器的接口名称为 TextWatcher,该接口提供了3个监控方法,如下。
beforeTextChanged:在文本改变之前触发。
onTextChanged:在文本改变过程中触发。
afterTextChanged:在文本改变之后触发
public class EditHideActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit_hind); EditText input_phone1 = findViewById(R.id.phone_num1); EditText password1 = findViewById(R.id.input_password1); input_phone1.addTextChangedListener(new HideTextWather(input_phone1, 11)); password1.addTextChangedListener(new HideTextWather(password1, 6)); } //编辑框监听器,输入文本达到要求自动隐藏输入法 private class HideTextWather implements TextWatcher { //编辑框对象 private EditText mView; //最大长度变量 private int mMaxLenght; public HideTextWather(EditText mView, int mMaxLenght) { this.mView = mView; this.mMaxLenght = mMaxLenght; } @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() == mMaxLenght){ //就隐藏输入法键盘 ViewUtil.hideOneInputMethod(EditHideActivity.this,mView); } } } }
工具类:
public class ViewUtil { public static void hideOneInputMethod(Activity act, View v) { // 从系统服务中获取输入法管理器 InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); // 关闭屏幕上的输入法软键盘 imm.hideSoftInputFromWindow(v.getWindowToken(), 0); } }
对话框
提醒对话框 AlertDialog
AlertDialog 可以完成常见的交互操作:提示、确定等。
- AlertDialog借 助建造器 AlertDialog.Builder 才能完成参数设置
- 调用建造器的 create 方法生成对话框实例,再调用对话框实例的show方法,在页面上弹 出提醒对话框
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.setNegativeButton("拜拜,卸载你", ((dialog, which) -> { tv_alert.setText("我走了,呜呜呜~~~"); })); //设置对话框否定按钮文本及其监听器 builder.setPositiveButton("再给你一次机会", ((dialog, which) -> { tv_alert.setText("我会变好的~~~"); })); // 根据构建起对话框提醒对话框对象 AlertDialog alertDialog = builder.create(); //显示提醒对话框 alertDialog.show(); } }
日期对话框 DatePickerDialog
日期选择器 DatePicker 可以让用户选择具体的年月日。
DatePicker 并非弹窗模式,而是在当前页面占据一块区域,并且不会自动关闭。
DatePickerDialog 相当于在 AlertDialog 上装载了 DatePicker,日期选择事件则由监听器OnDateSetListener负责响应,在该监听器的onDateSet方法中,开发者获取用户选择 的具体日期,再做后续处理
public class DatePickerActivity extends AppCompatActivity implements View.OnClickListener, DatePickerDialog.OnDateSetListener { private DatePicker date; private TextView tv_date_text; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_date_picker); findViewById(R.id.tv_btn_date).setOnClickListener(this); findViewById(R.id.tv_btn_date_alert).setOnClickListener(this); tv_date_text = findViewById(R.id.tv_date_text); date = findViewById(R.id.date_picker1); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.tv_btn_date: String desc = String.format("您选择的日期是 %d 年 %d 月 %d 日", date.getYear(), date.getMonth() + 1, date.getDayOfMonth()); tv_date_text.setText(desc); break; case R.id.tv_btn_date_alert: //获取日历的当前时间 Calendar ca = Calendar.getInstance(); DatePickerDialog dialog = new DatePickerDialog(this, this, ca.get(Calendar.YEAR), ca.get(Calendar.MONTH), ca.get(Calendar.DAY_OF_MONTH)); //显示日期对话框 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_text.setText(desc); } }
时间对话框 TimePickerDialog
- 时间选择器 TimePicker 可以让用户选择具体的小时和分钟
- TimePickerDialog 的用法类似 DatePickerDialog
public class TimePickerActivity extends AppCompatActivity implements View.OnClickListener, DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener { private TimePicker time; private TextView tv_time_text; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_time_picker); findViewById(R.id.tv_btn_time).setOnClickListener(this); findViewById(R.id.tv_btn_time_alert).setOnClickListener(this); tv_time_text = findViewById(R.id.tv_time_text); time = findViewById(R.id.time_picker1); //设置24 小时 time.setIs24HourView(true); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.tv_btn_time: String desc = String.format("您选择的时间是 %d 时 %d 分 ", time.getHour(), time.getMinute()); tv_time_text.setText(desc); break; case R.id.tv_btn_time_alert: //获取日历的当前时间 Calendar ca = Calendar.getInstance(); TimePickerDialog dialog = new TimePickerDialog(this, android.R.style.Theme_Holo_Light_DarkActionBar,this, ca.get(Calendar.HOUR_OF_DAY), ca.get(Calendar.MINUTE), true); //显示日期对话框 dialog.show(); break; } } @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { String desc = String.format("您选择的时间是 %d 时 %d 分 ", hourOfDay, minute); tv_time_text.setText(desc); } @Override public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { } }
实战项目:找回密码
登录方式
用户名与密码组合登录
手机号与验证码组合登录
区别
密码输入框和验证码输入框的左侧标题以及输入框内部的提示语各不相同
如果是密码登录,则需要支持找回密码;如果是验证码登录,则需要支持向用户手机发送 验证码;
密码登录可以提供记住密码功能,而验证码的数值每次都不一样,无需也没法记住验证码
找回密码
如果用户忘记密码,则需跳到单独的找回密码页面,在该页面输入和确认新密码,并校验 找回密码的合法性(通过短信验证码检查)
界面设计
单选按钮 RadioButton:用来区分是密码登录还是验证码登录。
文本视图 TextView:输入框左侧要显示此处应该输入什么信息。
编辑框 EditText:用来输入手机号码、密码和验证码。
复选框 CheckBox:用于判断是否记住密码。
按钮 Button:包含“登录” 、 “忘记密码”和“获取验证码”三个按钮。
线性布局 LinearLayout:界面从上往下排列,用到了垂直方向的线性布局。
相对布局 RelativeLayout:忘记密码的按钮与密码输入框是叠加的,且“忘记密码”与上级视图右对齐。
单选组 RadioGroup:放置密码登录和验证码登录这两个单选按钮。
提醒对话框 AlertDialog:通过提醒对话框向用户反馈结果
整个登录模块由登录页面和找回密码页面组成,因此这两个页面之间需要进行数据交互。
从登录页面跳到找回密码页面,要携带唯一标识的手机号码作为请求参数;
从找回密码页面回到登录页面,要将修改之后的新密码作为应答参数传回去
数据存储
共享参数SharedPreferences
- Android的轻量级存储工具,采用 Key-Value 的键值对方式的存储结构
- 共享参数的存储介质是符合XML规范的配置文件,保存位置:/data/data/应用包名/s hared_prefs/文件名.xml
使用场景
简单且孤立的数据。若是复杂且相互间有关的数据,则要保存在数据库中
文本形式的数据。若是二进制数据,则要保存在文件中
需要持久化存储的数据。在App退出后再次启动时,之前保存的数据仍然有效
实际开发中,共享参数经常存储的数据有App的个性化配置信息、用户使用App的行为信息、临时需要保存的片段信息等
实现记住密码
- 声明一个共享参数对象,并在onCreate函数中调用 getSharedPreferences 方法获取共享参数的实例
- 登录成功时,如果用户勾选了“记住密码” ,就使用共享参数保存手机号码与密码。
- 再次打开登录页面时,App从共享参数中读取手机号码与密码,并展示在界面上
package com.example.datastorage; import androidx.appcompat.app.AppCompatActivity; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; import android.view.contentcapture.ContentCaptureSession; import android.widget.CheckBox; import android.widget.EditText; public class ShareWriteActivity extends AppCompatActivity implements View.OnClickListener { private EditText et_name; private EditText et_age; private EditText et_height; private EditText et_weight; private CheckBox ck_married; private SharedPreferences preferences; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_share_write); et_name = findViewById(R.id.et_name); et_age = findViewById(R.id.et_age); et_height = findViewById(R.id.et_height); et_weight = findViewById(R.id.et_weight); ck_married = findViewById(R.id.ck_married); findViewById(R.id.btn_save).setOnClickListener(this); preferences = getSharedPreferences("config", Context.MODE_PRIVATE); reload(); } private void reload() { String name = preferences.getString("name", null); if (name != null) { et_name.setText(name); } int age = preferences.getInt("age", 0); if (age != 0) { et_age.setText(String.valueOf(age)); } float height = preferences.getFloat("height", 0f); if (height != 0f) { et_height.setText(String.valueOf(height)); } float weight = preferences.getFloat("weight", 0f); if (weight != 0f) { et_weight.setText(String.valueOf(weight)); } boolean married = preferences.getBoolean("married", false); ck_married.setChecked(married); } @Override public void onClick(View v) { String name = et_name.getText().toString(); String age = et_age.getText().toString(); String height = et_height.getText().toString(); String weight = et_weight.getText().toString(); //编辑器 SharedPreferences.Editor editor = preferences.edit(); editor.putString("name", name); editor.putInt("age", Integer.parseInt(age)); editor.putFloat("height", Float.parseFloat(height)); editor.putFloat("weight", Float.parseFloat(weight)); editor.putBoolean("married", ck_married.isChecked()); editor.commit(); } }
<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="40dp" android:orientation="horizontal"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:text="姓名:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_name" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginTop="3dp" android:layout_marginBottom="3dp" android:layout_weight="1" android:background="@drawable/edittext_select" android:hint="请输入姓名" android:inputType="text" android:maxLength="12" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="40dp" android:orientation="horizontal"> <TextView android:id="@+id/tv_age" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:text="年龄:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_age" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginTop="3dp" android:layout_marginBottom="3dp" android:layout_weight="1" android:background="@drawable/edittext_select" android:hint="请输入年龄" android:inputType="number" android:maxLength="2" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="40dp" android:orientation="horizontal"> <TextView android:id="@+id/tv_height" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:text="身高:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_height" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginTop="3dp" android:layout_marginBottom="3dp" android:layout_weight="1" android:background="@drawable/edittext_select" android:hint="请输入身高" android:inputType="number" android:maxLength="3" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="40dp" android:orientation="horizontal"> <TextView android:id="@+id/tv_weight" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:text="体重:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_weight" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginTop="3dp" android:layout_marginBottom="3dp" android:layout_weight="1" android:background="@drawable/edittext_select" android:hint="请输入体重" android:inputType="numberDecimal" android:maxLength="5" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <CheckBox android:id="@+id/ck_married" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="false" android:gravity="center" android:text="已婚" android:textColor="@color/black" android:textSize="17sp" /> <Button android:id="@+id/btn_save" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="保存到共享参数" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout>
edittext_select
<?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/shape_edit_focus"/> <item android:state_focused="false" android:drawable="@drawable/shape_edit_nofocus"/> </selector>
数据库SQLite
与MySQL一样的操作
SQLiteDatabase
管理类,用于数据库层面的操作。
openDatabase:打开指定路径的数据库。
isOpen:判断数据库是否已打开。
close:关闭数据库。
getVersion:获取数据库的版本号。
setVersion:设置数据库的版本号。
事务类,用于事务层面的操作。
beginTransaction:开始事务。
setTransactionSuccessful:设置事务的成功标志。
endTransaction:结束事务。
数据处理类,用于数据表层面的操作。
execSQL:执行拼接好的SQL控制语句。
delete:删除符合条件的记录。
update:更新符合条件的记录。
insert:插入一条记录。
query:执行查询操作,返回结果集的游标。
rawQuery:执行拼接好的SQL查询语句,返回结果集的游标
SQLiteOpenHelper
新建一个继承自 SQLiteOpenHelper 的数据库操作类,提示重写 onCreate 和 onUpgrade 两个方 法。
- 封装保证数据库安全的必要方法。
- 提供对表记录进行增加、删除、修改、查询的操作方法
游标Curious
调用SQLiteDatabase的query和rawQuery方法时,返回的都是Cursor对象,因此获取 查询结果要根据游标的指示一条一条遍历结果集合
游标控制类方法,用于指定游标的状态。
close:关闭游标。
isClosed:判断游标是否关闭。
isFirst:判断游标是否在开头。
isLast:判断游标是否在末尾
游标移动类方法,把游标移动到指定位置。
moveToFirst:移动游标到开头。
moveToLast:移动游标到末尾。
moveToNext:移动游标到下一条记录。
moveToPrevious:移动游标到上一条记录。
move:往后移动游标若干条记录。
moveToPosition:移动游标到指定位置的记录。
获取记录类方法,可获取记录的数量、类型以及取值。
getCount:获取结果记录的数量。
getInt:获取指定字段的整型值。
getLong:获取指定字段的长整型值。
getFloat:获取指定字段的浮点数值。
getString:获取指定字段的字符串值。
getType:获取指定字段的字段类型
优化记住密码功能
利用共享参数实现记住密码,只能记住一个用户的登录信息,并且手机号码跟密码不存在 从属关系,如果换个手机号码登录,前一个用户的登录信息就被覆盖了。
真正的记住密码功能是先输入手机号码,然后根据手机号匹配保存的密码,一个密码对应 一个手机号码,从而实现具体手机号码的密码记忆功能。
运用SQLite技术分条存储不同用户的登录信息,并提供根据手机号码查找登录信息的方 法,这样可以同时记住多个手机号码的密码
SQLite记住密码:
声明一个用户数据库的帮助器对象,然后在活动页面的onResume方法中打开数据库连接,在on Pasue方法中关闭数据库连接。
登录成功时,如果用户勾选了“记住密码” ,就使用数据库保存手机号码与密码在内的登录信息。
再次打开登录页面,用户输入手机号完毕后点击密码输入框时,App到数据库中根据手机号查找 登录记录,并将记录结果中的密码填入密码框。
存储卡的文件操作
私有存储空间与公有存储空间
外部存储空间:
- 公共空间
- 私有空间
存储卡上读写图片文件
Android 的位图工具是Bitmap,App读写Bitmap可以使用性能更好的BufferedOutputS tream和BufferedInputStream
Android还提供了BitmapFactory工具用于读取各种来源的图片,相关方法如下:
decodeResource:该方法可从资源文件中读取图片信息。
decodeFile:该方法可将指定路径的图片读取到Bitmap对象。
decodeStream:该方法从输入流中读取位图数据
应用组件Application
Application生命周期:https://blog.csdn.net/qq_56098191/article/details/127308723
- 有且只有一个Aplication对象
Application操作全局变量
适合在Application中保存的全局变量主要有下面3类数据:
- 会频繁读取的信息,如用户名、手机号等。
- 不方便由意图传递的数据,例如位图对象、非字符串类型的集合对象等。
- 容易因频繁分配内存而导致内存泄漏的对象,如Handler对象等
实现
用自定义Application的静态成员变量实现全局变量的功能
写一个继承自Application的类MainApplication。该类要采用单例模式,内部声明自身类的一个 静态成员对象,然后提供该静态对象的获取方法getInstance。
在Activity中调用MainApplication的getInstance方法,获得MainApplication的一个静态对象, 通过该对象访问MainApplication的公共变量和公共方法。
不要忘了在AndroidManifest.xml中注册新定义的Application类名,即在application节点中增 加android:name属性,值为“.MainApplication”
Room简化数据库操作
使用数据库帮助器编码的时候,开发者每次都得手工实现以下代码逻辑:
重写数据库帮助器的onCreate方法,添加该表的建表语句;
在插入记录之时,必须将数据实例的属性值逐一赋给该表的各字段;
在查询记录之时,必须遍历结果集游标,把各字段值逐一赋给数据实例;
每次读写操作之前,都要先开启数据库连接;读写操作之后,又要关闭数据库连接
Room框架的导入
在使用Room之前,要先修改模块的 build.gradle 文件,往 dependencies 节点添加下面两行配置,表示导入指定版本的Room库:
implementation ‘androidx.room:room-runtime:2.3.0’
annotationProcessor ‘androidx.room:room-compiler:2.’
Room框架编码步骤
以录入书籍信息为例,使用Room框架的编码过程分为下列五步:
编写书籍信息表对应的实体类,该类添加“@Entity”注解。
编写书籍信息表对应的持久化类,该类添加“@Dao”注解。
编写书籍信息表对应的数据库类,该类从RoomDatabase派生而来,并添加“@Database”注 解。
在自定义的Application类中声明书籍数据库的唯一实例。
在操作书籍信息表的地方获取数据表的持久化对象
实战项目:购物车
功能需求
购物车存放着用户准备购买的商品,一开始是空的,随着商品被加入购物车,购物车中就会显示已添加的商品列表。
除了购物车页面,其它页面(如商场频道页面、商品详情页面),都可能在右上角或者右 下角找到购物车图标。购物车图标上会显示已添加的商品数量,且商品数量是实时更新的。
购物车页面、商场频道页面、商品详情页面多处都会显示商品的小图或者大图
界面设计
线性布局LinearLayout:购物车界面从上往下排列,垂直方向的线性布局
网格布局GridLayout:商场页面的陈列橱柜,允许分行分列展示商品
相对布局RelativeLayout:页面右上角的购物车图标,图标右上角又有数字标记,按照指定方位排列控件
其他常见控件尚有文本视图TextView、图像视图ImageView,按钮控件Button等
存储技术
数据库SQLite:最直观的是数据库,购物车里的商品列表一定放在SQLite中,增删改查
全局内存:购物车图标右上角的数字表示购物车中的商品数量,该数值建议保存在全局内存中,这样不必每次都到数据库中执行count操作。
存储卡文件:App把下载的商品图片保存在存储卡中,这样下次就能直接从存储卡获取商品图片,加快浏览速度。
共享参数SharedPreferences:是否首次访问网络图片,这个标志位推荐放在共享参数中, 需要持久化存储,并且只有一个参数信息
内容共享
应用之间共享数据
ContentProvider
ContentProvider 为App内部数据提供统一的外部接口,让不同的应用之间得以共享数据
案例
Client App 讲用户输入内容,通过ContenetProvider跨进程通信传递给Server App
共享参数使用场景
ContentProvider只是服务端App存取数据的抽象类,需要在其基础上实现一个完整的内容提供器,并重写下列方法。
onCreate:创建数据库并获得数据库连接。
insert:插入数据。
delete:删除数据。
update:更新数据。
query:查询数据,并返回结果集的游标。
getType:获取内容提供器支持的数据类型
————————————————
Uri
Uri(通用资源标识符 Universal Resource Identifer),代表数据操作的地址,每一个Cont entProvider 都会有唯一的地址。ContentProvider 使用的Uri 语法结构如下:
content://authority/data_path/id
「content://」 是通用前缀,表示该Uri用于ContentProvider定位资源。
「authority」 是授权者名称,用来确定具体由哪一个ContentProvider提供资源。因此一般authority都由 类的小写全称组成,以保证唯一性。
「data_path」 是数据路径,用来确定请求的是哪个数据集。
「id」 是数据编号,用来请求单条数据。如果是多条这个字段忽略
ContentResovler访问数据
ContentProvider只实现服务端App的数据封装,如果客户端App想访问对方的内部 数据,就要通过内容解析器ContentResolver访问
内容组件获取通讯信息
动态申请权限
防止某些App滥用权限,允许App在运行时动态检查是否拥有某些权限,没有则会弹出权限访问窗口获取权限
步骤
检查App是否开启了指定权限
调用ContextCompat的checkSelfPermission方法。
请求系统弹窗,以便用户选择是否开启权限
调用ActivityCompat的requestPermissions方法,即可命令系统自动弹出权限申请窗口。
判断用户的权限选择结果
重写活动页面的权限请求回调方法onRequestPermissionsResult,在该方法内部处理用户的权限选择结果。
ContentResoler读写联系人
- 因为一个联系人可能有多个电话号码,还可能有多个邮箱,所以系统通讯录将其设计为三 张表,分别是联系人基本信息表、联系号码表、联系邮箱表,于是每添加一位联系人,就 要调用至少三次insert方法。
- 联系人读取代码也分成三个步骤,先查出联系人的基本信息,再依次查询联系人号 码和联系人邮箱
ContentObserver监听信息
内容观察器ContentObserver给目标内容注册一个观察器,目标内容的数据一旦发生变 化,观察器规定好的动作马上触发,从而执行开发者预先定义的代码
应用之间共享文件
使用相册图片发送彩信
FileProvider发送彩信
继承与ContentProvider,对第三方应用暴露文件,授予文件读写权限
FileProvider的配置步骤
- 在res/xml下创建file_paths.xml。
- 给AndroidManifest.xml的application节点添加provider标签。
- 在Java代码中根据字符串路径构建Uri对象
高级控件
下拉列表
下拉框Spinner
- Spinner 用于从一串列表中选择某项,功能类似于单选按钮的组合
Adapter 适配器
继承结构
- 适配器负责从数据集合中取出对应的数据显示到条目布局上
数组适配器ArrayAdapter
最简单的适配器,只展示一行文字
数组适配器分成下列步骤
编写列表项的XML文件,内部布局只有一个TextView标签
调用ArrayAdapter的构造方法,填入待展现的字符串数组,以及列表项的XML文件(R.layout.item_select)
调用下拉框控件的setAdapter方法,传入第二步得到的适配器实例
简单适配器SimpleAdaper
- SimpleAdapter 允许在列表项中同时展示文本与图片
集合当中的数据与条目布局的对应关系
列表类视图
基本适配器BaseAdapter
- BaseAdapter 是一种适应性更强的基本适配器
条目与数据集合对应关系
复用convertView
当列表的Item从上方滚出屏幕视角之外时
列表视图ListView
ListView 允许在页面上分行展示数据列表,例如新闻列表、商品列表等,方便用户浏览 与操作
ListView 新增属性
修改分隔线样式要在XML文件中同时设置 divider 与 dividerHeight 两个属性
取消按压列表项之时默认的水波背景,可在XML文件中设置也可在Java代码中设置
网格视图 GridView
- 网格视图用于分行分列显示表格信息
新增属性方法
GridView拉伸效果
GridView 拉伸模式取值
翻页类视图
翻译视图 ViewPaper
翻页视图允许页面在水平方向左右滑动
翻页视图3个常用方法
setAdapter:设置页面项的适配器。适配器用的是PagerAdapter及其子类。
setCurrentItem:设置当前页码,也就是要显示哪个页面。
addOnPageChangeListener:添加翻页视图的页面变更监听器。该监听器需实现接
OnPageChangeListener下的3个方法,
onPageScrollStateChanged:在页面滑动状态变化时触发。
onPageScrolled:在页面滑动过程中触发。
onPageSelected:在选中页面时,即滑动结束后触发。
翻页标签栏 PaperTabStrip
- 翻页标签栏能够在翻页视图上方显示页面标题,点击页面标题即可切换到对应页面
简单的启动引导页
当用户安装一个新应用时,首次启动大多出现欢迎页面,这个引导页要往右翻好几页,才会进入应用主页,这种启动引导页就是通过翻页视图实现的
引导页由两部分组成,一部分是背景图;另一部分是页面下 方的一排圆点,其中高亮的圆点表示当前位于第几页
除了背景图与一排圆点之外,最后一页往往有个按钮,它便是进入应用主页的入口
适配器
启动引导页的适配器
- 根据页面项的XML文件构造每页的视图。
- 让当前页码的圆点高亮显示。
- 如果翻到了最后一页,就显示中间的入口按钮
页面xml布局
- 引导页的背景图(采用ImageView)
- 底部的一排圆点(采用RadioGroup)
- 最后一页的入口按钮(采用Button)
碎片Fragment
- 传统的Activity并不能很好的处理大屏问题,所以急需一个碎片化的东西能够划区域的展 示内容,且有属于自己的独立可操作空间,所以就出现了Fragment
碎片的静态注册
- 静态注册在布局文件中直接指定Fragment,而动态注册直到在代码中才动态添加Fragment
碎片的动态注册(改进启动引导页)
与之前的启动引导页比较,改进后的启动引导页采用Fragment搭配ViewPager
一旦发生页面切换,相邻页面就被加载,非相邻页面就被回收。只有用户正在浏览与将要 浏览的Fragment才会加载,避免所有页面项一起加载造成资源浪费
引入碎片之后有以下两个好处
加快启动速度
低耦合
实战项目:记账本
好用的记账本必须具备两项基本功能,一项是记录新账单,另一项是查看账单列表。其中账单的记录操 作要求用户输入账单的明细要素,包括账单的发生时间、账单的收支类型(收入还是支出)、账单的交 易金额、账单的事由描述等,据此勾勒简易的账单添加界面如图8-30所示。账单列表页通常分月展示, 每页显示单个月份的账单数据,还要支持在不同月份之间切换。每月的账单数据按照时间从上往下排 列,每行的账单明细则需依次展示账单日期、事由描述、交易金额等信息,然后列表末尾展示当月的账 单合计情况(总共收入多少、总共支出多少)。根据这些要求描绘的账单列表界面原型如图8-31所示。 账单的填写功能对应数据库记录的添加操作,账单的展示功能对应数据库记录的查询操作,数据库记录 还有修改和删除操作,分别对应账单的编辑功能和删除功能。账单的编辑页面原型如图8-32所示,至于 删除操作则由如图8-33所示的提示窗控制,点击“是”按钮表示确定删除,点击“否”按钮表示取消删除。
账单填写页面、账单列表页面
账单编辑页面
伤处账单提示窗
界面设计
翻页视图ViewPager:每页一个月份,一年12个月,支持左右滑动,用到了ViewPager。
翻页标签栏PagerTabStrip:每个账单页上方的月份标题来自PagerTabStrip。
碎片适配器FragmentPagerAdapter:把12个月份的Fragment组装到ViewPager中,用到了碎片 适配器。
碎片Fragment:12个月份对应12个账单页,每页都是一个碎片Fragment。
列表视图ListView:每月的账单明细从上往下排列,采用了ListView。
基本适配器BaseAdapter:每行的账单项依次展示账单日期、事由描述、交易金额等信息,需要列 表视图搭档基本适配器。
提醒对话框AlertDialog:删除账单项的提示窗用到了AlertDialog。
日期选择对话框DatePickerDialog:填写账单信息时,要通过DatePickerDialog选择账单日期。
账单列表页面的控件嵌套关系
关键代码
- 实现日期下拉框
填写账单时间的时候,输入界面默认展示当天日期,用户若想修改账单时间,就要点击日期文本,此时 界面弹出日期选择对话框,待用户选完具体日期,再回到主界面展示选定日期的文本。这种实现方式类 似于下拉框控件Spinner,可是点击Spinner会弹出文本列表对话框,而非日期选择对话框。尽管 Android未提供现成的日期下拉框,但是结合文本视图与日期选择对话框,也能实现类似Spinner的日期 下拉框效果
编辑与删除账单项
需求描述提到既要支持账单的编辑功能,又要支持账单的删除功能,因为账单明细位于列表视图当中, 且列表视图允许同时设置列表项的点击监听器和长按监听器,所以可考虑将列表项的点击监听器映射到 账单的编辑功能,将列表项的长按监听器映射到账单的删除功能,也就是点击账单项时跳到账单的编辑 页面,长按账单项时弹出删除账单的提醒对话框。
合并账单的添加与编辑功能
账单编辑页面仍然跳到了BillAddActivity,然而该页面原本用作账单填写,若想让它同时 支持账单编辑功能,则需从意图包裹取出名为xuhao的字段,得到上个页面传来的序号数值,通过判断 该字段是否为-1,再分别对应处理
————————————————
广播
Android 中的广播: 发送一条广播,可以被不同的广播接收者所接收,广播接收者收到广播之后,再进行逻辑 处理
应用广播
广播与活动的区别
(1)活动只能一对一通信;而广播可以一对多,一人发送广播,多人接收处理。
(2)对于发送方来说,广播不需要考虑接收方有没有在工作,接收方在工作就接收广播,不在工作就丢 弃广播。
(3)对于接收方来说,因为可能会收到各式各样的广播,所以接收方要自行过滤符合条件的广播,之后 再解包处理。
广播的3个方法
- sendBroadcast:发送广播。
- registerReceiver:注册广播的接收器,可在onStart或onResume方法中注册接收器。
- unregisterReceiver:注销广播的接收器,可在onStop或onPause方法中注销接收器
广播的收发过程
- 发送标准广播
- 定义广播接收器
- 开关广播接收器
收发标准广播
- 先创建意图对象,再调用sendBroadcast方法发送广播
广播发出来之后,还得有设备去接收广播,也就是需要广播接收器。接收器主要规定两个事情,一个是 接收什么样的广播,另一个是收到广播以后要做什么。由于接收器的处理逻辑大同小异,因此Android 提供了抽象之后的接收器基类BroadcastReceiver,开发者自定义的接收器都从BroadcastReceiver派生 而来。新定义的接收器需要重写onReceive方法,方法内部先判断当前广播是否符合待接收的广播名 称,校验通过再开展后续的业务逻辑
通过意图过滤器挑选动作名称一致的广播
收发有序广播
1)一个广播存在多个接收器,这些接收器需要排队收听广播,这意味着该广播是条有序广播。
(2)先收到广播的接收器A,既可以让其他接收器继续收听广播,也可以中断广播不让其他接收器收听。
- 优先级,级别越大,数字越大
- 优先级大截断了优先级小的广播
收发静态广播
- 在代码中注册接收器,该方式被称作动态注册
- 在AndroidManifest.xml中注册接收器,该方式被称作静态注册