【Android入门】提高篇:重构BMI应用程序
一、重构程序
-
重构BMI应用程序
我们将 “MVC模式” 应用在 “MainActivity.java” 程序上,把 声明与查找界面组件 和 为特定界面组件添加控制流程 的两段代码,分别整理成两个函数 findViews() 和 setListensers()
MainActivity.java
package com.example.myapp; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import java.text.DecimalFormat; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViews(); setListensers(); } private Button button_calc; private EditText field_height; private EditText field_weight; private TextView view_result; private TextView view_suggest; private void findViews() { button_calc = (Button) findViewById(R.id.submit); field_height = (EditText) findViewById(R.id.height); field_weight = (EditText) findViewById(R.id.weight); view_result = (TextView) findViewById(R.id.result); view_suggest = (TextView) findViewById(R.id.suggest); } //Listen for button clicks private void setListensers() { button_calc.setOnClickListener(calcBMI); } //改成“Button.OnClickListener”是为了让“Button”界面组件和“OnClickListener”界面组件方法之间的关系清晰 private Button.OnClickListener calcBMI = new Button.OnClickListener() { public void onClick(View v) { DecimalFormat nf = new DecimalFormat("0.00"); double height = Double.parseDouble(field_height.getText().toString())/100; double weight = Double.parseDouble(field_weight.getText().toString()); double BMI = weight / (height * height); //Present result view_result.setText(getText(R.string.bmi_result) + nf.format(BMI)); //Give health advice if(BMI > 25) { view_suggest.setText(R.string.advice_heavy); } else if(BMI < 20) { view_suggest.setText(R.string.advice_light); } else { view_suggest.setText(R.string.advice_average); } } }; }
-
添加对话框(Dialog)
本节学习如何显示对话框。在本节中,我们要产生一个应用程序中常见的“关于”页面,我们的“关于”页面将以弹出对话框的方式表现。所需要做的是编写负责处理对话框的“openOptionsDialog”函数,并将之附加在原本应用程序中“calcBMI”这个按钮组件的“OnClickListener”方法上。当我们按下“计算BMI值”按钮时,即弹出对话框。
修改 MainActivity.java
private Button.OnClickListener calcBMI = new Button.OnClickListener() { public void onClick(View v) { ... } else { view_suggest.setText(R.string.advice_average); }
//调用对话框 openOptionsDialog(); } }; private void openOptionsDialog() {
//创建一个"AlertDialog"对话框实体,调用"Builder"方法来预备对应的界面组件 new AlertDialog.Builder(MainActivity.this)
//设置对话框的标题 .setTitle(R.string.about_title)
//设置对话框的内容 .setMessage(R.string.about_msg)
//给对话框添加按钮 .setPositiveButton(R.string.ok_label,
//包含一个没有作用的对话框接口(DialogInterface),表示当我们按下按钮时,不做任何事情直接退出对话框 new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } } ).show(); }
新增 res/layout/string.xml
<resources> ... <string name="about_title">关于 Android BMI</string> <string name="about_msg">Android BMI Calc</string> <string name="ok_label">确认</string> </resources>
-
Toast界面组件
“Toast”界面组件的作业是弹出一个消息框,快速在屏幕上显示一小段信息。
import android.widget.Toast; ...
//重写函数"openOptionsDialog" private void openOptionsDialog() {
//LENGTH_SHORT表示显示时间的长短 Toast.makeText(MainActivity.this, "BMI计算器", Toast.LENGTH_SHORT).show(); }
-
错误处理
前面使用XML说明文件定义界面的时候,在字段的属性中添加了只能输入数字的限制。现在我们将体重(weight)输入的限制移除,允许输入除了整数之外的其他符号(为了可以输入小数点位数的体重)。
<EditText android:id="@+id/weight" android:layout_width="fill_parent" android:layout_height="wrap_content" android:inputType="numberDecimal" //删除,解除了必须输整数的限制 android:singleLine="true" //增加,避免用户因为按到Enter键而多输入几行内容,所以增加了单行输入 android:text=""/>
上述的修改可能会造成,在“体重”字段中输入非整数也非浮点数的值时,整个程序会报错。因此在下面的程序中加入“try...catch”语句处理错误,利用“Toast”组件来通知用户输入错误。
DecimalFormat nf = new DecimalFormat("0.00"); try { double height = Double.parseDouble(field_height.getText().toString()) / 100; double weight = Double.parseDouble(field_weight.getText().toString()); double BMI = weight / (height * height); //Present result view_result.setText(getText(R.string.bmi_result) + nf.format(BMI)); //Give health advice if (BMI > 25) { view_suggest.setText(R.string.advice_heavy); } else if (BMI < 20) { view_suggest.setText(R.string.advice_light); } else { view_suggest.setText(R.string.advice_average); } openOptionsDialog(); } catch (Exception e) { Toast.makeText(MainActivity.this, "打错了吗?只能输入数字喔", Toast.LENGTH_SHORT).show(); }
二、查看线上内容
打开网页
在“openOptionsDialog”对话框函数中,新添加一个“链接到首页”的按钮
private void openOptionsDialog() { new AlertDialog.Builder(this) .setTitle(R.string.about_title) .setMessage(R.string.about_msg) .setPositiveButton(R.string.ok_label, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) //使用“setNegativeButton”方法提供“NegativeButton”按钮 .setNegativeButton("首页", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //为这个按钮添加了链接到指定网站的动作 Uri uri = Uri.parse("http://sites.google.com/site/gasodroid"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } }) .show(); }
通过“startActivity”函数,Android系统根据收到不同的“意图”(Intent)的动作和内容,打开对应的新页面或新程序。Intent及Intent和Activity之间的关系还需要后期系统的学习
后期为了方便字符串的管理,需要提取到strings.xml中,然后发现“Uri.parse()”函数不接受资源标识符类型的输入。我们可以使用“android.content.Context”类中的“getString”函数(或是getText),来取得资源标识符对应的文字。
Uri uri = Uri.parse(getString(R.string.homepage_uri));
另外网址输错也是常有的事,为了避免出错影响程序使用,我们可以使用“try...catch”语句来包住它(下面的异常类型有问题,需要查证)
try { //为这个按钮添加了链接到指定网站的动作 Uri uri = Uri.parse(getString(R.string.homepage_uri)); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } catch (URISyntaxException e) { }
三、添加菜单
我们把“openOptionsDialog”移出“OnClickListener”方法,将运行流程改为按下“Menu”键后,弹出一个菜单栏(Menu Bar)。当我们点击菜单栏中的选项后,才弹出“openOptionsDialog”的对话框
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... }protected static final int MENU_ABOUT = Menu.FIRST; protected static final int MENU_Quit = Menu.FIRST + 1;
/** * 创建菜单 * 此方法用于初始化菜单,其中menu参数就是即将要显示的Menu实例。返回true则显示该menu,false则不显示 */ public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); //增加菜单栏中选项,“.setIcon”方法来添加选项图标 //menu.add(0, 标识符(identifer), 0, 字符串或资源标识符) menu.add(0, MENU_ABOUT, 0, "关于...").setIcon(android.R.drawable.ic_menu_help); menu.add(0, MENU_Quit, 0, "结束"); return true; } /** * 处理选项动作 * 菜单项被点击时调用,也就是菜单项的监听方法 */ public boolean onOptionsItemSelected(MenuItem item) { //“item.getItemId()”函数用来获取所选项对应的标识符(identifer) switch (item.getItemId()) { case MENU_ABOUT: openOptionsDialog(); break; case MENU_Quit: finish(); break; } return super.onOptionsItemSelected(item); } }
四、定义Android列表(Manifest)
AndroidManifest.xml清单文件通常包括如下内容:
- 应用程序的包名,该包名作为该应用的唯一标识。
- 应用包含的组件,如Activity,Service,Broadcastreceiver和ContentProvider.
- 应用程序使用系统所需的权限声明。
- 其他程序访问该程序所需的权限声明。
五、添加新活动(Activity)
本章重点讲Activity的切换。Activity分为独立的Activity和相依赖的Activity,独立的Activity不涉及数据的交换,只是单纯地从一个屏幕跳到下一个屏幕;而相依赖的Activity需要与其他Activity交换数据,相依赖的Activity又可分为单向与双向。
//Activity 切换 Intent intent = new Intent(); intent.setClass(MainActivity.this, Report.class); startActivity(intent);
将定义好的intent传入“startActivity”函数中。“startActivity”函数会将intent传入Android框架,Android框架会根据各应用程序在系统中注册的信息,找到“要跳转的”Activity,并调用它。
六、传送数据到新意图(Intent)
Android使用Intent来完成在屏幕间切换的动作。Intent包含Activity间切换所需的动作、分类、传送数据等信息。Intent可以分为“默认的Intent”和“自定义的Intent”
本章中我们会完成将“BMI应用程序”从一个页面改成两个页面:“输入页面”(原本的Main Activity)与“结果页面”(Report Activity)的应用程序。“输入页面”从界面上取得身高、体重值,通过传送Intent,将值携带到“结果页面”。“结果页面”从Intent中取出其携带的身高、体重值,用这两个参数来产生“BMI”报告结果。
-
使用Intent传递数据
修改 MainActivity.java “Button.OnClickListener”函数
private Button.OnClickListener calcBMI = new Button.OnClickListener() { public void onClick(View v) { //Switch to report page Intent intent = new Intent(); intent.setClass(MainActivity.this, Report.class);
//携带数据 Bundle bundle = new Bundle(); bundle.putString("KEY_HEIGHT", field_height.getText().toString()); bundle.putString("KEY_WEIGHT", field_weight.getText().toString()); intent.putExtras(bundle);
startActivity(intent); } };
相依赖的Activity需要靠Intent对象携带数据传送到新的Activity。附加在Intent上的消息都存储在“bundle”对象实体中,通过“intent.putExtras(bundle)”语句,我们将“bundle”对象实体附加在“intent”对象实体上,随着Intent送出而送出
“Bundle”类其实是一种特别定义的映射(map)类型,类似于键值对
-
使用Intent接收信息
新建“res/values/report.xml”文件
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="report_title">BMI 报告</string> <string name="report_back">前一页</string> </resources>
修改“res/layout/activity_report.xml”文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".Report"> <TextView android:id="@+id/result" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> <TextView android:id="@+id/suggest" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> <Button android:id="@+id/report_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/report_back"/> </LinearLayout>
新建Report.java 完整程序如下:
package com.example.myapp; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.text.DecimalFormat; public class Report extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_report); findViews(); showResults(); setListensers(); } private Button button_back; private TextView view_result; private TextView view_suggest; private void findViews() { button_back = (Button) findViewById(R.id.report_back); view_result = (TextView) findViewById(R.id.result); view_suggest = (TextView) findViewById(R.id.suggest); } private void setListensers() { button_back.setOnClickListener(backMain); } private Button.OnClickListener backMain = new Button.OnClickListener() { @Override public void onClick(View v) { //Close this Activity,显示出之前的Activity Report.this.finish(); } }; private void showResults() { DecimalFormat nf = new DecimalFormat("0.00"); //接收传来的“Intent”对象实体 Bundle bundle = this.getIntent().getExtras(); double height = Double.parseDouble(bundle.getString("KEY_HEIGHT"))/100; double weight = Double.parseDouble(bundle.getString("KEY_WEIGHT")); double BMI = weight / (height * height); view_result.setText(getString(R.string.bmi_result) + nf.format(BMI)); //Give health advice if(BMI>25) { view_suggest.setText(R.string.advice_heavy); } else if(BMI<20) { view_suggest.setText(R.string.advice_light); } else { view_suggest.setText(R.string.advice_average); } } }
Report.java 主要用到 “Activity.getIntent().getExtras()” 来获取传来的 “Intent” 对象实体
七、信息提醒(Notification)
在 Report.java 中新增一个函数,用于消息提醒
public class Report extends Activity {
...
private void showResults() { DecimalFormat nf = new DecimalFormat("0.00"); Bundle bundle = this.getIntent().getExtras(); double height = Double.parseDouble(bundle.getString("KEY_HEIGHT"))/100; double weight = Double.parseDouble(bundle.getString("KEY_WEIGHT")); double BMI = weight / (height * height); view_result.setText(getString(R.string.bmi_result) + nf.format(BMI)); //Give health advice if(BMI>25) { showNotification(BMI); view_suggest.setText(R.string.advice_heavy); } else if(BMI<20) { view_suggest.setText(R.string.advice_light); } else { view_suggest.setText(R.string.advice_average); } } protected void showNotification(double BMI) { PendingIntent pendingIntent = PendingIntent.getActivity(this,0,new Intent(this, MainActivity.class),0); String id ="channel_1";//channel的id String description = "123";//channel的描述信息 int importance = NotificationManager.IMPORTANCE_LOW;//channel的重要性 NotificationChannel channel = new NotificationChannel(id, "123", importance);//生成channel //为channel添加属性 //channel.enableVibration(true); 震动 //channel.enableLights(true);提示灯 NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); manager.createNotificationChannel(channel);//添加channel Notification notification = new Notification.Builder(this,id) //注意这里多了一个参数id,指配置的NotificationChannel的id //你可以自己去试一下 运行一次后 即配置完后 将这行代码以上的代 //码注释掉 将参数id直接改成“channel_1”也可以成功运行 //但改成别的如“channel_2”就不行了 .setCategory(Notification.CATEGORY_MESSAGE) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("This is a content title") .setContentText("This is a content text") .setContentIntent(pendingIntent) .setAutoCancel(true) .build(); manager.notify(1,notification); } }
参考:https://blog.csdn.net/weixin_40604111/article/details/78674563
https://www.jianshu.com/p/a84ddaf530ec
八、优先级
使用情景:当用户第一次输入身高体重值后,程序能帮我们预先记住上次输入过的身高,那么等到下次运行程序时,便只需要输入体重。
打开“MainActivity.java”,在“onCreate”和“onPalse”中添加“Preference”(优先级设置)相关的程序代码。
卡在 onPalse() 函数