讲作业先把昨天卖的关子给说了，怎么才能按返回键实现网页后退。我们需要做一个返回键拦截，然后手动控制网页后退。请看代码：public boolean onKeyDown(int keyCode, KeyEvent event) { //支持网页后退 if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) { webView.goBack(); return true; } return super.onKeyDown(keyCode, event); }我们首先重写了 onKeyDown 方法，在这个方法体里做一下判断，如果按的是返回键同时网页可以后退就调用 webview 的 goBack 方法。不会的同学就直接复制我的这个吧，我当时就是。随着技术的增长积累，就明白了。隐式 Intent有的时候我们并不是明确的要启动某一个活动，比如我要打开系统浏览器，我也不可能把每一个浏览器的活动都写一遍。这时就需要用到隐式 Intent 了。隐式 Intent 是和系统描述我们要做什么，然后系统就会打开对应可以处理的程序。比如说我告诉系统我要打开一个网页，系统就会自动调用浏览器来执行。使用 action 限定隐式 Intent：一个活动要想能被别的活动调用，首先被调用的Activity在AndroidManifest.xml文件中要有一个带有<intent-filter>并且包含<action>的Activity，并且设定它能处理的Intent。将category设为"android.intent.category.DEFAULT"。action的name是一个字符串，可以自定义，例如我在这里设成"webview"：<activity android:name=".webview_activity"> <intent-filter> <action android:name="webview"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity>然后，在MainActivity，才可以通过这个action name找到上面的Activity。下面两种方式分别通过setAction和构造方法方法设置Action，两种方式效果相同：1、setAction方法Intent intent = new Intent(); intent.setAction("webview"); startActivity(intent);2、构造方法直接设置ActionIntent intent = new Intent("webview"); startActivity(intent);通过设置Action字符串，表明自己的意图，即我想干嘛，需要由系统解析，找到能够处理这个 Intent 的 Activity 并启动。比如我想打电话，则可以设置Action为"android.intent.action.DIAL"字符串，表示打电话的意图，系统会找到能处理这个意图的Activity，例如调出拨号面板。 不过需要注意的是：这个Activity其他应用程序也可以调用，只要使用这个Action字符串。这样应用程序之间交互就很容易了，例如手机QQ可以调用QQ空间，可以调用腾讯微博等。 因为如此，为了防止应用程序之间互相影响，一般命名方式是包名+Action名，例如这里命名"webview"就很不合理了，就应该改成"com.intent.test.webview"。 再回过头去看看 AndroidManifest.xml 你会发现不光有 action 还有个 category 。这么说吧，要 Action 和 category 同时匹配上才行。因为「android.intent.category.DEFAULT」是一个默认的 category 所以就不用写了。总结为啥这一节这么匆忙，因为，，，，这东西了解就好，到用的时候再回来看一下就好了。一共二十二讲，我觉得你应该也算是入门了吧，你已经具备了自己探索的能力了。第一季是时候说再见了，我会利用一段的时间闭关去提升自己的能力，以更好的姿态面对大家。不过不用伤心，大家敬请期待第二期：『烽哥带你彪程序』2020/5/24 注：这是我 2018 年高中毕业的假期写的，当时并没有自己搭建博客，近期我给这一系列的文章都放到我自己的博客上。当时写这个系列文章的想法就是扎实下基础，然后能帮到更多的人就更好了，但是大学开学之后就开始了新生活，没有大块的时间去编撰，系列文章也没有再更新过。但是我其实并不想放弃这个系列，那就，有缘再见吧，拜拜ヾ(•ω•`)o
讲作业话说这次的作业是设计一个布局：这大概也是之前学过的了，直接贴一下代码吧：<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="100dp" android:layout_height="100dp" android:id="@+id/icon" android:layout_centerHorizontal="true" android:background="@drawable/ic_magnify" android:gravity="center" android:paddingTop="500dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/icon" android:orientation="horizontal"> <EditText android:id="@+id/edittext" android:layout_width="0dp" android:layout_height="50dp" android:layout_weight="1" android:hint="请输入关键词或网址" /> <ImageView android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/ic_send" /> </LinearLayout>TIPS: EditText 可以通过「android:hint="提示词"」来设置提示词，在用户输入内容时提示词会自动消失；在清空输入框时，提示词又会重写出现最外层的布局是一个相对布局，LOGO 的图片固定大小为 100dp*100dp ，距离顶端 500dp，对齐方式为居中。然后紧接着一个水平的线性布局宽填充父布局，高为适应大小。然后在线性布局里放置一个 EditText，一个 ImageView 。其中 EditText 高度和 ImageView 的高度相同都是 50dp ，因为那个小飞机是正方形，所以 ImageView 的宽也设置为 50dp 这也都不难。重点是，他需要背后的逻辑，我们需要让他输入关键词之后，该搜索的搜索，该跳转的跳转。Intent 活动之间的桥梁随着应用的开发，一个活动肯定解决的不了问题，那么就需要建立多个活动。不过打开程序时都是启动的主活动，如果要启动别的活动就需要使用 Intent 了。显式 Intent我们昨天已经讲过了怎么创建活动。我们依旧还是在 com.intent.test 包上右键~new~Activity~Empty Activity，弹出新建活动的窗口，我们呢这次就把新建的活动叫做 「webview_activity」，记得这次勾选 Generate Layout File 让 AS 为我们自动创建布局文件，然后在「Layout File」中给布局布局文件起一个名字，就叫「activity_webview」吧。点击 Finish 结束：随后就可以看到创建好的布局和 Java 文件：然后我们配置下 MainActivity.java ，为那个小飞机加一个点击事件来跳转到 webview_activity：ImageView search = (ImageView)findViewById(R.id.search); search.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, webview_activity.class); startActivity(intent); } }); Intent 翻译成中文就是意图的意思。Intent 的构造函数需要传入两个参数，第一个是当前活动的上下文，通常传入当前「类名.this」就行。第二个参数需要传入要启动的目标活动，通常传入「目标活动类名.class」即可。这样我们就构造出了我们要启动一个名字叫「webview_activity」的活动，意图很明显。这样的方式就叫做显示 Intent 。我们调用类提供「startactivity();」方法并传入 Intent 对象就可以启动另一个活动。现在我们重新运行程序，点击小飞机就会发现成功的跳转到了另一个活动：现在来完善一下我们的新活动，在布局里放入一个 WebView 就行: <WebView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/webview"/>然后修改 Java 文件：public class webview_activity extends AppCompatActivity { String HOMEPAGE = "https://bing.com"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); WebView webView = (WebView)findViewById(R.id.webview); initWebView(webView); webView.loadUrl(HOMEPAGE); } void initWebView(WebView webView){ webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { //设置在当前窗口打开网页 view.loadUrl(request.toString()); return true; } @Override //拦截唤醒本地app public void onPageStarted(WebView view, String url, Bitmap favicon){ if (url.startsWith("intent") || url.startsWith("INTENT")) view.loadUrl(url); } }); //设置支持JavaScript webView.getSettings().setJavaScriptEnabled(true); } }最后别忘了加入联网权限<uses-permission android:name="android.permission.INTERNET"/>运行的效果如下：传递数据到下一个活动话说这也不行呀，我得把我输入的关键词传递给下一个活动。于是我们就需要用 Intent 把数据传递给下一个活动。修改MainActivity.java 中 ImageView 点击事件的代码：Intent intent = new Intent(MainActivity.this, webview_activity.class); EditText editText = (EditText)findViewById(R.id.edittext); intent.putExtra("InputWord",editText.getText().toString()); startActivity(intent);这里我们利用 Intent.putExtra() 方法来把用户输入的文本暂存到 Intent 里。这里接收两个参数，第一个是「键」，第二个是要传递的数据。键说白了就是你给暂存的数据起一个名字，就好比在论坛中的 ID 一样。因为 Intent 能传递多个数据，要给每一个数据起一个名字。然后我们修改 webview_activity.java 中的逻辑，在 setContentView() 后加入以下的代码，来在第一时间获取 Intent 中的值：Intent intent = getIntent(); String inputText = intent.getStringExtra("InputText");首先可以通过 getIntent 来获取启动了这个活动的 Intent ，然后调用 getStringExtra(键值) 来获取 Intent 中的数据。因为我们刚才存放的是字符串，所以这里用 getStringExtra 。如果存放的是整数，那么可以用 getIntExtra，同样如果是布尔值，那么就是 getBooleanExtra。我们继续完善：if (TextUtils.isEmpty(inputText)){ //验证获取到的用户输入是否为空 //如果用户啥都没输入，就加载主页 webView.loadUrl(HOMEPAGE); } else{ if (inputText.startsWith("http") || inputText.startsWith("HTTP")){ //验证是否是网址 webView.loadUrl(inputText); //是网址就直接加载 } else { String result = "https://www.baidu.com/s?wd=" + inputText; //不是网址就拼接出百度搜索链接 webView.loadUrl(result); //加载拼接出的链接 } }在获取用户输入的内容后，先验证用户的输入是否为空，是空的话就加载主页。如果不为空就判断是否是网址，是网址就直接丢给 webview 去加载，要是不是网址就拼接出百度的搜索链接。逻辑也比较好理清。下面就贴一下完整的代码：/** 文件：webview_activity.java **/ public class webview_activity extends AppCompatActivity { String HOMEPAGE = "https://m.baidu.com"; WebView webView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); Intent intent = getIntent(); String inputText = intent.getStringExtra("InputText"); webView = (WebView)findViewById(R.id.webview); initWebView(webView); if (TextUtils.isEmpty(inputText)){ //如果用户啥活都没输入，就加载主页 webView.loadUrl(HOMEPAGE); } else{ if (inputText.startsWith("http") || inputText.startsWith("HTTP")){ webView.loadUrl(inputText); } else { String result = "https://www.baidu.com/s?wd=" + inputText; webView.loadUrl(result); } } } void initWebView(WebView webView){ webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { //设置在当前窗口打开网页 view.loadUrl(request.toString()); return true; } @Override //拦截唤醒本地app等不是浏览器该做的事 public void onPageStarted(WebView view, String url, Bitmap favicon){ Log.d("webview", "onPageStarted: "+url); if (url.startsWith("intent") || url.startsWith("INTENT")) { view.loadUrl(url); } } }); //设置支持JavaScript webView.getSettings().setJavaScriptEnabled(true); }/** 文件：MainActivity.java **/ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout); ImageView search = (ImageView)findViewById(R.id.search); search.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, webview_activity.class); EditText editText = (EditText)findViewById(R.id.edittext); intent.putExtra("InputText",editText.getText().toString()); startActivity(intent); } }); } }效果图：总结今天我们学了显式 intent，明天我们搞隐式的。不过有一个问题值得考虑，这个浏览器没法后退，这个问题就当作业吧，下课。2020/5/24 注：这是我 2018 年高中毕业的假期写的，当时并没有自己搭建博客，近期我给这一系列的文章都放到我自己的博客上。当时写这个系列文章的想法就是扎实下基础，然后能帮到更多的人就更好了，但是大学开学之后就开始了新生活，没有大块的时间去编撰，系列文章也没有再更新过。但是我其实并不想放弃这个系列，那就，有缘再见吧，拜拜ヾ(•ω•`)o
前言话说上次没作业，这次就直接讲课。我们之前好像一直在用 AS 自动为我们创建的活动，这样糊里糊涂的可不行，今天我们就来探究下 AS 究竟默默的为我们做了什么？创建一个新项目我们在创建新项目时直接就可以运行是因为 AS 帮我们创建好了活动，为了理解原理我们这次关闭自动创建活动，来手动创建一个活动。打开 AS 创建一个新项目，Application name(应用名字) 填 IntentText，Company domain(公司网站)空着，Project location(项目文件存放地址) 选一个能找到的地方，Package name(包名) 填 com.intent.test，然后下一步1526980287343这里仍然默认，直接 Next 下一步：这里注意了，要选「Add No Activity」，不自动添加 Activity。1526981963764然后点击「Finish」结束配置。将项目结构转换成「Project」，app/src/main/java/com.intent.test 应该是空的了：而且 appsrcmainres 下应该也没有 layout 布局文件夹：手动创建布局项目创建好了，我们就先来创建布局吧，在 res 上右键，new~XML~Layout XML File1526991322612打开新建布局界面。第一行时输入布局文件的名字，默认是 layout；第二行是根布局，默认是 LinearLyout。这里就保持默认不变吧，直接点击 finish。然后就会发现这个文件，在里面随便加一个控件，比如 Button：手动创建 Java 文件布局准备好了，我们现在开始准备 Java 文件：在 com.intent.text 上右键 new~Activity~Empty Activity，来创建一个 Java 文件：弹出新建 Android Activity 的对话框，Generate Layout File 的意思是自动为活动创建布局，因为我们刚才已经手动创建了，所以不勾选。Launcher Activity 是把当前活动表示为主活动，由于我们是要探究，所以也不勾选。最后的 Backwards Compatibility 是用 AppCompat 来将项目向后兼容。直接点击 Finish，完成设置然后就能看到创建好的 MianAvtivity：1526993735564默认是这样的：在 Android 中，活动必须继承(extends) 自 Activity 或 AppCompatActivity，并且重写 onCreate() 方法，才能具有活动的特性，也才是一个活动。TIPS：重写的意思是一个类继承自父类后，就具有了父类的所有方法和特性。如果子类继承自父类以后，想要修改父类的方法，只需要新建一个完全一样的方法（访问控制符，方法名，形参列表都相同），就可以把父类的方法覆盖掉。为了防止手抖打错了，需要在重写的方法前加「@Override」，如果没有重写成功（像返回值类型错了之类的），编译器会报错，来警告开发者这里 AS 已经帮我们完成了这一步，现在我们要修改现有的代码来引入布局。在 onCreate 方法中加入一行代码：setContentView(R.layout.layout);其中 R 代表引用资源，第一个layout 代表 layout 文件夹，最后一个 layout 代表布局的 XML 文件的文件名。这样就完成了布局的。TIPS：用 AIDE 的小伙伴注意了，AIDE 不支持直接创建活动，你只能手动完成。而且你在新建 Java 文件时会发现他说空白的！没有自动完成任何东西。你只能从已经创建好的项目里复制一份过来。配置 AndroidManifest.xml还记得之前讲的，任何活动都需要在 AndroidManifest.xml 中注册吗？现在就打开 AndroidManifest.xml 来看看：<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.intent.test"> <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=".MainActivity"></activity> </application> </manifest>看到那个<activity>标签了吧，就是他。「android:name」用于指定是哪一个活动，这里的「.MainActivity」其实是「com.intent.test.MainActivity」的缩写，因为在最外层的「package」中已经定义了，所以这里就可以缩写。但是在某些特殊的情况下还是需要使用全称的，比如在使用一些别人的库的时候。当然现在我们这个程序是还不能运行的，因为我们还没有标识主活动和启动活动：加入如下代码：<intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter>这些 AS 全程都是有代码补全的，但是 AIDE 的伙伴，，，你从已经创建好的源码里复制吧，没办法，我也是那么过来的。最后的效果如下：<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.intent.test"> <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=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>现在我们来运行一下，效果如图：我们就成功的手动创建一个活动了。总结今天的学习主要是为了明天的「Intent」的讲解打基础，那么今天的作业来了：新建一个活动，做一个搜索的界面，仅仅是界面即可。如下图所示：在公众号后台回复「21素材」获取图标素材。祝大家学习进步！！！！2020/5/24 注：这是我 2018 年高中毕业的假期写的，当时并没有自己搭建博客，近期我给这一系列的文章都放到我自己的博客上。当时写这个系列文章的想法就是扎实下基础，然后能帮到更多的人就更好了，但是大学开学之后就开始了新生活，没有大块的时间去编撰，系列文章也没有再更新过。但是我其实并不想放弃这个系列，那就，有缘再见吧，拜拜ヾ(•ω•`)o
讲作业先来回顾下上节课的题目「为什么 ImageView 会有背景和前景？有什么应用？ 」答案让我们在实践中去体会吧。先在「drawable」里放好我们的素材：然后设计布局<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageview" android:padding="60dp" android:background="@drawable/ic_backgroind"/>其中 padding 属性控制的是控件内容与空间边缘的距离效果如下：还没猜到要干什么？没事，接着看，我们添加 Java 代码：public class MainActivity extends Activity { int MODE = 0; int UNLOVE = 0; int LOVE = 1; ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView)findViewById(R.id.imageview); imageView.setImageResource(R.drawable.ic_heart_outline); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (MODE == UNLOVE){ MODE = LOVE; imageView.setImageResource(R.drawable.ic_heart); } else { MODE = UNLOVE; imageView.setImageResource(R.drawable.ic_heart_outline); } } }); } }我先是定义了三个整型的变量，其中 MODE 描述的是当前的状态，LOVE 代表实心，UNLOVE 代表空心。然后定义了一个 ImageVIew 用于保存按钮的实例。随后在 「setContentView」之后直接通过「findViewById」取得按钮的实例，然后通过设置前景来让空心显示出来。然后为这个 ImageView 绑定了一个点击监听器，在点击之后先通过 MODE 判断当前的状态，然后根据状态来切换前景图案。效果图如下：不用我说，大家已经知道前景和背景的用处了。对话框 AlertDialog「AlertDialog」就是一个小框框，在窗口最顶层，能屏蔽掉其他控件的交互的 Window 。一般都是用来提示或询问重要内容的。下面的代码演示了一个最简单的对话框：new AlertDialog.Builder(MainActivity.this) //构造一个 Builder .setTitle("我是标题") //设置标题 .setMessage("我是正文") //设置正文 .create() //创建对话框 .show(); //显示对话框也可以这么写，至于你怎么写就看你的喜好了：AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this); dialog.setTitle("我是标题"); dialog.setMessage("我是正文"); dialog.create().show();先通过「AlertDialog.Builder」来创建出一个 AlertDialog 实例，这个过程需要传入当前类的实例，通常就是「类名.this」。然后调用他的各种方法来进行配置，最后调用「dialog.create().show();」来将对话框创建显示到屏幕上。效果图如下：加入按钮但是，，，貌似别人家的对话框有按钮呀，观察并运行如下代码：AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this); dialog.setTitle("我是标题"); dialog.setMessage("我是正文"); dialog.setPositiveButton("肯定按钮", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "点击了肯定按钮", Toast.LENGTH_SHORT).show(); } }); dialog.setNegativeButton("否定按钮", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "你点击了否定按钮", Toast.LENGTH_SHORT).show(); } }); dialog.setNeutralButton("中性按钮", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "你点击了中性按钮", Toast.LENGTH_SHORT).show(); } }); dialog.create().show();Dialog 允许我们最多添加三个按钮，根据位置来分分别是「否定按钮」，「中性按钮」，「肯定按钮」。他们的区别主要就是位置：方法MD 位置HOLO 位置解释setNegativeButton中间左边否定按钮setNeutralButton最左边中间中性按钮setPositiveButton最右边右边肯定按钮其中「Negative」是消极的意思，「Neutral」是中性的意思，「Positive」是积极的意思，所以一般「setPositiveButton」都被定义为『确定』或『是』，「setNegativeButton」都被定义为『取消』或『否』。这三个设置按钮的方法都需要传入两个参数，第一个是按钮的文字，第二个是一个点击事件的监听器。在对话框里通常就是new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //点击按钮时执行的代码 }实现的效果：设置不可取消不知道你有没有发现，我们的对话框点一下空白处就会消失。但是别人家的对话框有的就没法取消呀。这时你需要：dialog.setCancelable(false);「Cancel」是取消的意思，这里传入「false」就是不可取消：进度对话框「AlertDialog」还有一个兄弟：「ProgressDialog」。「Progress」是进度的意思，字面上就翻译成「进度对话框」吧。典型代码如下：ProgressDialog progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setTitle("请稍侯"); progressDialog.setMessage("正在拼命处理中...."); progressDialog.setCancelable(false); progressDialog.show();相信有了上面的基础，看这个已经没什么难度了：质感设计的对话框看了上面的截图你是不是想喷我？明明一个 Android P 的设备，对话框却是 Android 4.x 时代的样子。是呀，现在就让对话框符合质感设计：将项目targetSdkVersion设为22或者以上加入对应的support依赖支持 「compile 'com.android.support:appcompat-v7:22.1.0' 」把最顶部的「import android.app.AlertDialog;」替换成「import android.support.v7.app.AlertDialog;」修改 AndroidManifest.xml 中 Activity 的主题为 Appcompat 下的一种，如「android:theme="@style/Theme.AppCompat.Light"」享受吧效果如下：这里提供一下我的 build.gradle，方便不知道在哪里添加的小白：apply plugin: 'com.android.application' android { compileSdkVersion 27 defaultConfig { applicationId "com.example.gtf35.test" minSdkVersion 19 targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support.constraint:constraint-layout:1.1.0' compile 'com.android.support:appcompat-v7:27.1.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }tips：「com.android.support:appcompat-v7:」后面的数字必须和「targetSdkVersion」后面的数字对应。如果不对应的话，AS 会给出修改建议，修改就好。提供一下我的 AndroidManifest.xml ，方便不知道在哪里添加的小白：<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.gtf35.test"> <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.AppCompat.Light"> <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>微技巧--修改按钮的文字颜色有个前提，必须是质感设计的 Dialog，也就是必须导入了「com.android.support:appcompat」包，而且在 Java 文件顶部 import 的是「android.support.v7.app.AlertDialog」 。首先我们需要监听 dialog 的创建：mDialog.create().setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(DialogInterface dialog) { //在对话框创建时执行的代码 } });AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("提示："); builder.setMessage("是否删除该文件？"); builder.setPositiveButton("确定", null); builder.setNegativeButton("取消",null); builder.setNeutralButton("稍后询问",null); final AlertDialog alertDialog = builder.create(); //创建对话框，创建之后才可以监听 alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { //绑定对话框创建监听器 @Override public void onShow(DialogInterface dialog) { alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED); //确定 alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.BLUE); //取消 alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(Color.BLACK); //中性 } }); alertDialog.show(); //显示对话框在没有调用 show() 之前AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 为 null。AlertDialog 的逻辑是 show()方法 中初始化了 AlertDialog 上面的控件，然后展示出来，所以在 show()方法 执行之后才能得到 button。那么我们就监听他的创建，一创建就拿到 Button 然后改颜色。由于这个过程非常快，用户感受不到。 总结今天一不小心又写到凌晨三点，居然不觉得困，真神奇。今天我们学会了对话框的使用，还 get 到了一个小技能。今天就不留什么作业了，大家自己动手创建一个对话框练练手就好。新的一周，燥起来。2020/5/24 注：这是我 2018 年高中毕业的假期写的，当时并没有自己搭建博客，近期我给这一系列的文章都放到我自己的博客上。当时写这个系列文章的想法就是扎实下基础，然后能帮到更多的人就更好了，但是大学开学之后就开始了新生活，没有大块的时间去编撰，系列文章也没有再更新过。但是我其实并不想放弃这个系列，那就，有缘再见吧，拜拜ヾ(•ω•`)o
先来回顾一下上节课的题目「做一个简单的遥控器的界面」，如图：整体的思路就是先弄中间的十字，再弄下面的按钮。中间的十字可以先把「OK」固定的中间，然后四周的根据他来定位：源文件丢失了，请移步这个链接查看，抱歉点击这里跳转
0x0 前言一个炫丽的界面往往都是由很多的小控件组成的。队伍整齐划一，往往是因为他们按队列站。一个程序的界面想做好，就要依赖布局。布局描述了控件在屏幕中的位置和定位方式。布局有很多种，我们今天先来介绍相对简单的线性布局。1x0 线性布局（LinearLayout）顾名思义，「线性布局」可以把其中包含的视图按线性排列。换句话说，「线性布局」可以将其他视图在单个列中水平排列，也可以垂直排列在单个行中。 之前我们都是把控件放在他里面，因为这种布局通常情况下控件之间不会重叠。现在来看一下他的继承结构：可以看到他是一种 ViewGroup（混眼熟），以下代码展示了如何在布局XML文件中使用线性布局：<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> </LinearLayout>1x1 orientation这里有一个新属性「orientation」，可以通过他指定是否在行或列中显示子视图 ：属性值解释orientationhorizontal水平排列 vertical竖直排列我们回顾一下细节，我们以前的项目结构就是「vertical」，也就是竖直排列。新建一个项目，放三个「Button」，代码如下：<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> </LinearLayout>这里通过「android:orientation="vertical"」指定了根布局竖直方向排列，里面有三个「Button」，长和宽都是「wrap_content」，效果图如下：现在我们试试改成水平方向：「android:orientation="horizontal "」：果真和我们预想的一样，但是要注意几点：如果不指定「orientation」的值，默认就是水平如果「LinearLayout」的排列方式是水平的话，内部的控件的宽就绝对不能是「match_parent」，因为这样那个控件在充满屏幕的同时就会把别的控件挤出屏幕，别的控件就没有可放置的位置了。如果「LinearLayout」的排列方式是竖直的话，内部的控件的高就绝对不能是「match_parent」，道理同上。1x2 布局中的对齐方式我们再来看一个新属性「android:layout_gravity」，看起来有没有一点眼熟？没错。我们之前学过「android:gravity」这个属性，也应用过许多次了，他可以指定文字在控件中的对齐方式。我们来打量打量这个新属性「layout_gravity」比之前的多了一个「layout」。「layout」是布局的意思，联想起来就是控件在布局中的对齐方式。说白了，「gravity」是控件内部的元素(像文字啥的)相对于控件的对齐方式； 而「layout_gravity」是「LinearLayout」内部的控件相对于「LinearLayout」的对齐方式。「layout_gravity」和「gravity」的可选值几乎一样，而且水平和竖直都能用。需要注意的是当「LinearLayout」的排列方式。需要注意的是当「LinearLayout」的排列方式是「horizontal」时只有垂直方向的对齐方式才会生效，因为此时水平方向上的长度是不固定的，每添加一个控件，水平方向的长度都会改变，因而无法指定水平方向上的对齐方式。同理，当「LinearLayout」的排列方式是「vertical」时只有水平方向的对齐方式才会生效。继续修改上面的代码：<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> </LinearLayout>这里「LinearLayout」的排列方向是「horizontal」，也就是说只能指定在垂直方向上的排列方式：将「Button」的「layout_gravity」分别指定为「top」，「center_vertical」，「bottom」，效果图如下：1x3 按比例调整大小有的时候我们主要几个控件按比例填充屏幕，比如，聊天app的输入框。下面我来介绍一个新属性：「layout_weight」（权重）。总的来说就是屏幕的剩余空间按比例分配 。注意的是：只有在Linearlayout中，该属性才有效 。<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:text="在此处输入内容...."/> </LinearLayout>先来张效果图吧：如果要在水平方向使用「layout_weight」（权重）来确定控件的宽，就要先把控件的宽设置为「0dp」，这是一种标准写法，因为这时控件的宽度不应该由「layout_width」来决定【文末会作为进阶内容给大家分析】。其中「dp」是 Android 中一种描述控件大小px ： 其实就是像素单位，比如我们通常说的手机分辨列表1920*1980都是px的单位 sp ： 同dp相似，还会根据用户的字体大小偏好来缩放 dp ： 虚拟像素，在不同的像素密度的设备上会自动适配 dip： 同dp然后我们再把 EditText 的「android:layout_weight」设置为 3 ，把 Button 的设置为 1。意思就是把屏幕水平方向上按 3：1 分配，其中 EditText 占 3 份，Button 占 1 份。再举一个例子，如果 EditText 的「android:layout_weight」属性的值相等，那么就会平分屏幕的水平方向。1x4 一个固定，另一个按比例调整大小有的时候，我们可能还需要一个控件的尺寸固定，然后用别的控件来填充剩余空间。换句话说就是一个空间假设是 3 dp ，不管设备屏幕多大，他都是 3 dp ，至于剩的空间怎么办，那是别的控件的事，他们去适应，反正我就 3 dp。继续修改布局如下：<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="在此处输入内容...."/> </LinearLayout>这里把「Button」的「layout_width」指定为「wrap_content」，「Button」的宽度就由他自己决定。此时「EditText」依旧是宽 0dp ，weight 为1。也可以理解为 Button 独立，EditText 的宽度按照剩余空间的百分比计算，一方面按百分比算会填充剩余的空间，另一方面只有 EditText 这一个控件设置了 weight ，没人跟他抢，所以他的权重自然就是100%。综上的结果就是 Button 适应自己的大小，而 EditText 会占满屏幕剩余的空间。这是效果图： 2x0 总结今天我们学习了最常用的一个布局「LinearLayout」，那么作业来了，做一个带地址栏的浏览器：提示：布局是可以嵌套的。2x1 进阶还记得我说如果要在水平方向使用「layout_weight」（权重）来确定控件的宽，就要先把控件的宽设置为「0dp」吧。这是因为实际上控件最终的宽度 = 「layout_width」的宽度 + (剩余空间 * 占比)。假设一个水平的 LinearLayout 里有两个控件，宽度都为 match_parent 的情况下，原有宽度为L，两个的View的宽度都为L，那么剩余宽度为L-（L+L） = -L, 左边的View占比四分之三，左边的总宽度是L+(-L)*1/4 = (3/4)L。TIPS: Google官方推荐，当使用weight属性时，将width设为0dp即可。这样weight就可以理解为占比了。不要给自己挖坑找麻烦，切记。我希望你永远也不要像上面一样去计算。代码可读性很重要。2020/5/24 注：这是我 2018 年高中毕业的假期写的，当时并没有自己搭建博客，近期我给这一系列的文章都放到我自己的博客上。当时写这个系列文章的想法就是扎实下基础，然后能帮到更多的人就更好了，但是大学开学之后就开始了新生活，没有大块的时间去编撰，系列文章也没有再更新过。但是我其实并不想放弃这个系列，那就，有缘再见吧，拜拜ヾ(•ω•`)o
（第十五讲）异常捕获---让程序更加健壮
0 公告已开设交流群，回复「群」获取加群方式逐步减少 AIDE 的演示，因为 AS 和 AIDE 的代码一样，只是操作方式不一样。AIDE 的基本操作都已经演示过了。Android Studio 以后均简称为 AS ，且不再重复1 注意为了暂时保持 AS 和 AIDE 代码的统一性，在 AS 新建项目时请不要勾选 「Backwards Compatibility (AppCompat)」。15259406165292 讲作业昨天的作业是做一个简易的加法计算器，先来考虑需求，应该是允许输入两个数点击一个按钮，出现结果。新建一个项目：「计算器」包名就是「edittext.test.jishuan」，这样我们在设计布局的时候就加入两个 EditText 来让用户输入，然后安放一个 Button 来确定，最后在一个 TextView 来显示结果。在细节上来说，EditText 应该只允许输入数字：<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/edittext_first" android:text="第一个加数" android:gravity="center" android:inputType="number"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="+" android:gravity="center"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/edittext_second" android:gravity="center" android:text="第二个加数" android:inputType="number"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textview_result" android:gravity="center" android:text="结果"/> </LinearLayout>虽然有点长但是都是我们熟悉的属性，效果如下：布局代码操作演示：现在来想一想逻辑，应该是用户点击按钮之后先判断一下两个输入框是否为空，然后获取两个输入框里的数值，再相加，最后显示到 TextView 上：public class MainActivity extends Activity implements View.OnClickListener{ //接口方式实现 onClick() Button btn; //计算按钮 EditText et1, et2; //两个输入框 TextView tv; //显示结果的 TextView @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); //初始化控件 et1.setOnClickListener(this); //绑定第一个输入框的点击监听器 et2.setOnClickListener(this); //绑定第二个输入框的点击监听器 btn.setOnClickListener(this); //绑定按钮的点击监听器 } @Override public void onClick(View view) { switch (view.getId()){ case R.id.edittext_first: //点击第一个输入框时清空原有的数据 et1.setText(""); break; case R.id.edittext_second: //点击第二个输入框时清空原有的数据 et2.setText(""); break; case R.id.button_run: int result; try{ if(checkEmpty()) throw new Exception("输入的数据无效"); int first = Integer.parseInt(et1.getText().toString()); int second = Integer.parseInt(et2.getText().toString()); result = first + second; Log.d("结果", Integer.toString(result)); tv.setText(Integer.toString(result)); } catch (NumberFormatException e){ Toast.makeText(MainActivity.this,"请填写每一个输入框", Toast.LENGTH_SHORT).show(); } catch (Exception e){ Toast.makeText(MainActivity.this,e.getMessage(), Toast.LENGTH_SHORT).show(); } break; default: break; } } void initView(){ et1 = (EditText)findViewById(R.id.edittext_first); et2 = (EditText)findViewById(R.id.edittext_second); btn = (Button)findViewById(R.id.button_run); tv = (TextView)findViewById(R.id.textview_result); } boolean checkEmpty(){ //判断两个输入框是否为空 if (TextUtils.isEmpty(et1.getText())) return true; if (TextUtils.isEmpty(et2.getText())) return true; return false; } }运行效果如下：都是以前的知识堆积起来的，但是有两个新鲜的东西。判断一个字符串是否为空用 「TextUtils.isEmpty()」，比如判断字符串 a 是否为空，就这么写「TextUtils.isEmpty(a)」，如果为空就会返回 true，非空就会返回 false。另一个便是 try 块了。3 捕获异常异常捕获就是字面意思，把异常抓住。在之前，如果一段代码出错了，程序将崩溃。但是如果我们能预知或是觉得这一段代码可能会出错，那么我们就可以把错误抓住，也就是「异常捕获」。我们使用 try...catch 捕获异常，以下是异常捕获的典型代码：try{ //可能会出错的代码 } catch (Exception e){ //出现问题时的处理方法 } finally { //最后执行的收尾工作 }如果在执行 try 块里的程序代码时出现异常，系统会自动生成一个异常对象，该异常对象被提交给了 Java 运行时环境。这个过程被称作抛出『throw』异常。当 Java 运行时环境收到异常对象时时，会寻找该异常对象的 catch 块，如果找到合适的 catch 块，则把该异常对象交给该 catch 块执行，这个过程叫做捕获『catch』异常；如果 Java 运行时环境找不到捕获异常的 catch 块，运行时环境将会终止，程序也会退出。但是需要注意的是，不管代码是否在 try 块中，只要执行该代码块时出现了异常，系统总是会生成一个异常对象，如果系统没有找到 catch 块，程序直接「crash」退出。异常处理中只有 try 块是必须的，也就是说，没有 try 块，就不能有后面的 catch 块和 finally 块；catch 块和 finally 块都是可选的，但 catch 和 finally 至少出现一个，也可以同时出现；可以有多个 catch 块，多个 catch 块必须在 try 块后面 finally 块前边，也就是说 finally 块必须位于所有的 catch 块之后。常见的异常如下：NullPointerException(空指针异常)NumberFormatException(字符串转换为数字异常)IndexOutOfBoundsException(数组下标越界异常)ArithmeticException(数学运算异常)Exception(所有异常)比如我们想要捕获「ArithmeticException(数学运算异常)」，则可以这么写：try{ //可能会出现数学运算异常的代码 } catch (ArithmeticException e){ //出现问题时的处理方法 }tips：「数学运算异常」通常就是一个数除以零造成的；也可以捕获多种异常：try{ //可能会出现多种异常的代码 } catch (ArithmeticException e){ //出现数学运算异常时的处理办法 } catch (NumberFormatException e){ //出现字符串转化异常时的处理办法 } catch (Exception e){ //其余所有的错误的处理办法 }tips：由于「Exception」是异常的父类，所以他可以代表所有错误我们继续看向今天的程序，try{ if(checkEmpty()) throw new Exception("输入的数据无效"); int first = Integer.parseInt(et1.getText().toString()); int second = Integer.parseInt(et2.getText().toString()); result = first + second; Log.d("结果", Integer.toString(result)); tv.setText(Integer.toString(result)); } catch (NumberFormatException e){ Toast.makeText(MainActivity.this, "请填写每一个输入框", Toast.LENGTH_SHORT).show(); } catch (Exception e){ Toast.makeText(MainActivity.this,e.getMessage(), Toast.LENGTH_SHORT).show(); } }由于用户可能输入中文，有可能出现运算错误，所以要进行异常捕获，大体来看可以这么简化一下：try{ //进行计算的代码 } catch (ArithmeticException e){ //出现数学运算异常时提示用户检查输入的数据 } catch (Exception e){ //其余出现所有的错误直接提示用户 //可通过 e.getMessage() 来获取异常信息 }开发中可以调用「getMessage」方法来获取具体的异常信息。4 抛出异常在程序中出现了我们可以判断的错误时我们也可以手动制造一个异常来让当前代码块结束运行。譬如在今天的程序中： if(checkEmpty()) throw new Exception("输入的数据无效");当发现输入框时空的，程序当然不能继续计算，所以我手动抛出了一个异常，内容是「输入的数据无效」。抛出异常的语法如下：throw new 异常类型("异常内容");tips：当 if 语句只有一行时，{}大括号可以省略。现在来整理一下，计算的过程，先判断输入框是否为空，空的话抛出异常；然后先「getText」获取输入框的内的字符串，然后为了计算，调用「Integer.parseInt(int)」来把字符串转化为整数，但是这一步的输入如果有非数字内容的话会抛出 「NumberFormatException」异常，要进行适当的异常捕获，防止崩溃；这样就可以获取到了两个输入框中的数字，将他俩相加，然后调用「Integer.toString(int)」来把结果的数字转化为字符串。这里贴一个刚踩的坑，textview.setText()不能传 如int ，如果传入 int 的话编译可以通过，但是运行报错。为什么呢？还记得可以通过 R.String.xxx 来获取字符串吧（其实估计你忘了，忘了就直接跳过吧），这个其实返回的是一个 int 。这时如果我们传入 int 系统就会把他当作一个 resourse 资源。当然这不是一个资源值，所以会崩溃。所以在 setText 一个数字时一定要先将他转化为字符串。5 总结今天本来是做浏览器的来着，但是我发现这个作业躲不过异常捕获，因为我们虽然限制只能输入数字，但是我们默认的提示词用户如果不更改就点击计算就会导致程序崩溃。我转念一想，这是一个非常好的介绍异常捕获的机会，人不是神，写的代码总归会有崩溃的时候，做好异常处理，主动出击，可以使程序更加健壮。今天的作业就是改进一下昨天的计算器，让他「百毒不侵」。2020/5/24 注：这是我 2018 年高中毕业的假期写的，当时并没有自己搭建博客，近期我给这一系列的文章都放到我自己的博客上。当时写这个系列文章的想法就是扎实下基础，然后能帮到更多的人就更好了，但是大学开学之后就开始了新生活，没有大块的时间去编撰，系列文章也没有再更新过。但是我其实并不想放弃这个系列，那就，有缘再见吧，拜拜ヾ(•ω•`)o
0 讲作业我昨天漏讲了加网络权限（视频里的没问题），唉，人真是老了。你想呀，一个「小淘宝」肯定是要网络权限的呀，现在我们就把昨天图文教程缺的补上，给「小淘宝」添加网络权限。打开 「AndroidManifest.xml 」，加入「uses-permission」节点：<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.github.uitest"> <uses-permission android:name="android.permission.INTERNET"/> <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=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>动图演示：至于把「小淘宝」改成「QQ空间」，就是把网址改成 QQ 空间的网址「https://i.qq.com」即可。TIPS：WebView 只可以打开以「http://」或「https://」开头的网址，比如「www.baidu.com」必须写成「https://www.baidu.com」。动图演示：1 EditText 输入框1x1 XML 属性Edit 是编辑的意思，Text 是文本的意思。这玩意看起来很眼熟有没有，呃....好像并没有。EditText 是 TextView 的子类，用于输入和修改文本的一个控件。定义 EditText 时，必须指定TextView_inputType属性。例如，对于纯文本输入将inputType设置为“text”，以下是典型布局代码：<EditText android:id="@+id/plain_text_input" android:layout_height="wrap_content" android:layout_width="match_parent" android:inputType="text"/>inputType 输入类型这里有个新面孔，就是上面提到的「inputType」属性。「input」是输入的意思，「Type」是类型的意思，合起来字面意思就是输入类型。来看个例子：输入密码时输入框是看不到文字的，只有一堆点点；再譬如某些情况下只能输入数字，这都是「inputType」的功劳。「inputType」可配置显示的键盘类型，可接受的字符类型以及编辑文本的外观。例如，如果您想请求用户输入密码，则可以将inputType设置为「textPassword」，这样输入的字符就显示为小点点。说白了，你没啥特殊要求 「inputType」的值就写「text」，要是输入普通的密码就写「textPassword」，要是输入只有数字的密码就写「numericPassword」。Google 的 API 上要求是必须定义「inputType」，但是实际开发时不定义也不会有问题，就相当于「text」。但还是建议按照 API 文档上说的定义一下比较好。imeOptions 输入法编辑器选项还有一个值得关注的属性：在浏览器地址栏输入文本时，输入法右下角是搜索：在发微信时，输入法右下角是回车：输入法右下角的样式就是「imeOptions」属性控制的。「ime」英文全称「Input Method Editors」，「Input Method」是输入法的意思，「Editors」是编辑器的意思，「Options」是选项的意思，「imeOptions」合起来就是「输入法编辑器选项」的意思。这个属性的值的键盘右下角的字的对应关系是：值输入法Java代码actionDone完成EditorInfo.IME_ACTION_DONEactionGo前进EditorInfo.IME_ACTION_GOactionNext下一项EditorInfo.IME_ACTION_NEXTactionNone无EditorInfo.IME_ACTION_NONEactionPrevious上一项EditorInfo.IME_ACTION_PREVIOUSactionSearch搜索EditorInfo.IME_ACTION_SEARCHactionUnspecified未指定EditorInfo.IME_ACTION_UNSPECIFIEDactionSend发送EditorInfo.IME_ACTION_SENDTIPS: 使用android:imeOptions属性的时候，一定要对EditText设置 android:inputType 或者 设置 android:singleline=”true”。我们新建一个项目叫 「EdItTextTest」 包名写 「edittext.test」，其余默认，添加三个带有不同属性的 EditText ，要注意的是用 AndroidStudio 的朋友需要先在布局里嵌套一个 ，详情见演示：<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:text="普通的 EditText" android:imeOptions="actionNone" android:inputType="text"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:imeOptions="actionNext" android:inputType="text" android:text="输入法是下一个的EditText" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:imeOptions="actionGo" android:inputType="textPassword" android:text="输入形式是密码的EditText"/> </LinearLayout>运行后就这效果：演示视频，这次我特意把文字调大了：【此处e.mp4】1x2 Java 代码只有布局那就是个空壳，这里介绍几种常见的 EditText 的用法。还是在案例中学习，我们这次设计一个程序，界面上有一个输入框「EditText」，一个文本「TextView」，最后实现在输入框「EditText」输入文字，同步显示在文本里。那么来想一想布局，就上面放一个 TextView 下面放一个 EditText：<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="50sp" android:text="在下方输入文字，这里将会显示相同的内容" android:id="@+id/textview"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/edittext" android:imeOptions="actionGo" android:text="请输入文字，上方将会出现相同的文字"/> </LinearLayout>这里定义了一个 TextView ，文字大小为 「50sp」 ，文字为「在下方输入文字，这里将会显示相同的内容」，id为「textview」，都是我们比较熟悉的属性。接着又定义了一个 EditText ，文字为「请输入文字，上方将会出现相同的文字」，id 为「edittext」。然后想一想要实现什么样的逻辑，首先因为我们的 EditText 里有文字，还不少，用户想输入自己的还得一个个把我们写的删掉，所以我们需要在点击 EditText 时就清空里面的文字，另外由于上面的 TextView 应该是和下面的 EditText 里的文字同步的，所以 TextView 里的文字也要一并清空。然后需要在 EditText 里的文字改变时获取到里面输入的内容，然后把文字显示到 TextView 上。这时我们需要一个新的监听器「TextWatcher」，「Text」是文字的意思，「watcher」是观察者的意思，合起来就是「文字观察者」，很形象是不是？对于 EditText ，他的语法如下： editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { //在文字改变之前执行 } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { //在文字改变时执行 } @Override public void afterTextChanged(Editable editable) { //在文字改变之后执行 } });看起来有点小复杂，我们先要调用 EditText 对象的 「addTextChangedListener」方法，这里需要传入一个「TextWatcher」对象。然后我们在「TextWatcher」里重写「beforeTextChanged」，「onTextChanged」，「afterTextChanged」三个方法，然后在里面分别实现我们需要的逻辑。我们这里需要在文字改变时也改变 TextView 里的文字。系统会在文字改变时调用「onTextChanged」方法，并且传入参数，其中改变的文字就藏在第一个 「CharSequence」 对象里，我们调用「CharSequence」的「toString」方法就可以获取到改变后的文字。然后调用 TextView 的 「setText」方法，把文字显示到 TextView 上。依照这个思路，修改 MainActivity.java :public class MainActivity extends AppCompatActivity { EditText editText; TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = (EditText) findViewById(R.id.edittext); textView = (TextView) findViewById(R.id.textview); editText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { textView.setText(""); editText.setText(""); } }); editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { textView.setText(charSequence.toString()); } @Override public void afterTextChanged(Editable editable) { } }); } }老例子，双开发工具演示视频：【此处两个视频】3 总结今天我们又学会了一个新的重要的控件——EditText ，收获满满，作业又来了，设计一个程序，做一个简易的计算器，实现两数相加，提示：两个输入框，一个按钮，一个文本。好啦，祝每个人都能实现梦想。2020/5/24 注：这是我 2018 年高中毕业的假期写的，当时并没有自己搭建博客，近期我给这一系列的文章都放到我自己的博客上。当时写这个系列文章的想法就是扎实下基础，然后能帮到更多的人就更好了，但是大学开学之后就开始了新生活，没有大块的时间去编撰，系列文章也没有再更新过。但是我其实并不想放弃这个系列，那就，有缘再见吧，拜拜ヾ(•ω•`)o
0x0 讲作业话说昨天的视频太苦了，页面里的小视频就是不清晰，还得外链。这一期干脆上传 B 站得了，高清还没广告。你可以在公众号回复 “13” 来获取可以点击的超链接，或者是在图文内长按网址复制粘贴到浏览器观看（微信限制，必须手动复制）。在 main_activity.xml 加入一个新按钮，都是旧知识，哪里不懂的就再看一遍昨天的。没事。忘记了太正常了。源文件丢失了，请移步这个链接查看，抱歉点击这里跳转
0x0 前言昨天我们介绍了 TextView 的一些属性，今天会介绍 Button 的一些属性和如何绑定监听器。今天一些过于基本的内容将不会提供动图，代替的是在文末有一个全操作的视频，供大家系统性的学习。1x0 Button 按钮我们打开上一节的 UIText 项目中的 activity_main.xml (AIDE 为 main.xml)，删掉里面的 TextView ，加入一个 Button 。最后效果如下：<?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="horizontal"> </LinearLayout>这些属性都是昨天我们介绍过的，属性值说明layout_widthmatch_parent将控件的宽指定为填充父布局layout_heightwrap_content将控件的高指定为适应控件大小idbutton将控件的 ID 指定为 buttontext按钮将控件的文字指定为“按钮”运行效果图如下，操作过程见尾部视频第一节1x1 点击事件但是我们这个按钮中看不中用，点击了什么反应都没有，那是因为还没有给他添加逻辑。如果需要要让按钮在点击时作出反应，我们就需要为 Button 注册一个 onClick（点击）事件监听器。打开 MainActivity.java 加入代码，如下所示：public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button mButton = (Button)findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //在此处添加被点击时的逻辑 } }); } }在第 8 行，我们先是定义了一个 Button 类型的变量 mButton 。然后为了他赋一个初始值：我们的 Button，然后 mButton 就代表了我们这个代码了。但是怎么获取我们的 Button 呢？我们先通过 findViewById(R.id.xxx) 来获取我们的 Button 实例，其中xxx就是在布局里定义的 ID 。findViewById 返回的是个 View 对象，我们需要通过 (Button) 向下转型成一个 Button 对象。这个过程连起来就是第 8 行。这时我们的 mButton 就可以代表我们的按钮实例了。然后通过调用 mButton 的 .setonClickListener() 来为Button 注册一个点击监听器。在 .setonClickListener() 内需要传入一个 View.OnClickListener(){} 对象。然后在 View.OnClickListener(){} 里重写 onClick() 方法，在 onClick() 方法里编辑自己的逻辑。是不是听懵了？？？没事，我当初也懵了很久，一直不明白为什么要这样。那时我查遍教程，由于我特别差的 Java 基础，根本看不透，看不懂，和你们现在一样。我的做法就是硬记下来，在需要用这段时就照猫画虎写下来。等我经验多了，基础知识多了，我不但背下来了，我还理解了。我知道这么多你背不下来，我也背不下来。我建议你去看看文末的视频，里面演示了如何使用代码补全功能，只要大体记住结构和关键词，就可与补全代码。但是我要求你没补全一次就默默背一次，这样才能早日修成正果。1x2 使用 Toast接下来我们先小试牛刀，添加一个逻辑，就是弹出一个 Toast 。所谓的 Toast 就是在屏幕下方的一个小气泡，如下图所示Toast 的用法比较简单，通过静态方法 makeText() 创建出一个 Toast 对象，然后调用 show() 方法来把 Toast 显示出来。其中 makeText() 需要传入 3 个参数：参数说明ContextToast 的上下文，由于活动本身就是一个 Context 对象，这里直接传入 MainActivity.this 即可TextToast 显示的文字内容Toast 显示时长通常选择两个内置的常量：Toast.LENGTH_SHORT （短）和 Toast.LENGTH_LONG（长）于是我们就在 onClick()里写一个 Toast :public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button mButton = (Button)findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //在此处添加被点击时的逻辑 Toast.makeText(MainActivity.this, "点击了 Toast", Toast.LENGTH_SHORT).show(); } }); } }小TIPS：在 Android Studio 中快速插入一个Toast可以输入 Toast （注意大小写）之后按方向键选择列表里第二个，然后按回车，第一个参数通常会智能的帮我们生成，然后我们只需要写入我们要显示的文字就好。运行后点击的效果图：1x3 通过接口注册点击事件还有一种绑定点击事件的方法就是使用接口，接口的概念我并没有介绍，我觉得我也没那个本事也没必要仔细讲接口的概念，我就告诉你怎么做吧。看代码：public class MainActivity extends AppCompatActivity implements View.OnClickListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button mButton = (Button)findViewById(R.id.button); mButton.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()){ case R.id.button: Toast.makeText(MainActivity.this, "点击了 Button", Toast.LENGTH_SHORT).show(); break; default: break; } } }就是在第一行的 AppCompatActivity 后面加上 implements 代表实现一个接口，然后接着写 View.OnClickListener 来代表实现一个 onClick 的接口。接着借鉴刚才的 setOnClickListener() ，只不过这里传入活动的实例 this 就可以了。最后在 onCreate 外重写 onClick() 方法，然后参照上面代码的格式写就可以了。如果实在不明白可以看我的视频，这里也有一些自动补全的技巧，在重写 onClick() 方法时只要输入 onClick 然后按方向键加回车确定就能自动补全所有的。实在还不会就用第一种吧，伴随着知识继续积累，慢慢那会懂的。2x0 结合上节的内容综合利用还记得上节课的 TextView 吧，我们综合利用一下，每点击一次按钮 TextView 里的数字就加 1 。我们应该定义两个控件，一个是 Button 一个是 TextView ，然后注册 Button 的点击事件，在事件里获取 TextView 的值，然后把TextView 的值加 1 后，在修改 TextView 上的文字。思路大体就是这样，我们在之前的基础上接着修改 activity_main.xml （AIDE 为 main.xml）加入一个 TextView ，让宽度充满父布局，高度适应控件大小：<?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="horizontal"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textview" android:text="这是 TextView"/> </LinearLayout>这些属性我相信你已经非常熟悉了，就不多赘述了，我们打开 MainActivity.java 修改逻辑，讲解都写在注释里了：public class MainActivity extends AppCompatActivity implements View.OnClickListener{ Button mButton; //定义成全局变量，保存 Button 对象，便于全局调用 TextView mTextView; //定义成全局变量，保存 TextView 对象，便于全局调用 int textInTextView; //定义成全局变量，保存 TextView 中的值，便于调用 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview);//绑定控件，获取实例 mTextView.setText("0"); //初始化 TextView 中的值为 0 mButton = (Button)findViewById(R.id.button); //绑定控件，获取实例 mButton.setOnClickListener(this); //注册点击事件 } @Override public void onClick(View view) { switch (view.getId()){ case R.id.button: textInTextView = Integer.parseInt(mTextView.getText().toString()); //我们要的时 int 值，因为我们要做数学运算 //而通过 getText() 获取的值是 byte 类型的， //需要调用 .toString 转换成 String 型 //然后通过 Integer.getInteger() 方法将获取到的值转换成整型。 textInTextView++; //自加 mTextView.setText(Integer.toString(textInTextView)); //将结果显示到 TextView 上 //由于 setText() 需要传入一个字符串， //所以要调用 Integer.toString 方法把数字转换成字符串，再传入 break; default: break; } } }Tips: 调用 TextVeiw 的 setText() 可以设置 TextView 的文字，同理可以用 getText() ，获取到 TextView 的文字。下面是效果图：3x0 作业给上面的程序田间一个按钮用来把数字归零数字居中2020/5/24 注：这是我 2018 年高中毕业的假期写的，当时并没有自己搭建博客，近期我给这一系列的文章都放到我自己的博客上。当时写这个系列文章的想法就是扎实下基础，然后能帮到更多的人就更好了，但是大学开学之后就开始了新生活，没有大块的时间去编撰，系列文章也没有再更新过。但是我其实并不想放弃这个系列，那就，有缘再见吧，拜拜ヾ(•ω•`)o