Android Studio && Android 开发笔记
一名沙雕大学生的Android学习笔记
第一章: 活动Activity
活动
Acitivity
是最吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户交互
创建活动布局 勾选
Generate Layout File
表示会自动为此 活动 创建一个对应的布局文件, 勾选Launcher Acitivity
表示会自动为此活动设置为当前项目的主要活动
活动的基本用法
创建和加载布局
创建布局文件时选择了
LinearLayout
作为根元素,因此布局文件中已经有一个LinearLayout
元素了,现在我们对这个布局稍做编辑,添加一个按钮:
1 |
|
android:id
:是给当前元素定义一个唯一标识符,@+id/id_name
用于在XML
中定义一个id
,@id
则是需要在XML
中引用一个id
.
android:layout_width
:指定了当前元素的宽度,这里使用了match_parent
表示让当前元素和父元素一样宽此时父元素则是整个布局页面.
android:layout_height
:指定了当前元素的高度,这里使用了wrap_content
表示当前元素的高度只要刚好包含里面的内容就行.
android:text
:指定了元素的中显示的文字内容.
在AndroidManifest
文件中注册
活动注册声明要放在
<application>
标签内,通过<activity>
标签来对活动进行注册的
1 |
|
<activity>
标签中我们使用android:name
来指定具体注册那一个活动,由于最外层的的<manifest>
标签中已经通过package
属性指定了程序的包名是io.pure.firsttestacitivity
因此注册活动时就可以省略前一部分,直接使用.FirstActivity
.
<intent-filter>
标签里添加<android:name="android.intent.action.MAIN"/> && <category android:name="android.intent.category.LAUNCHER"/>
用来声明程序主活动.
在活动中使用Toast
Toast
是Android
系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间
1 |
|
通过静态方法
makeText()
创建出一个Toast
对象,然后调用show()
将Toast
显示出来,makeText()
方法需要传入3个参数, 第一个参数是Context
也就是Toast
要求的上下文,第二个参数是Toast
显示的文本内容:文本内容太也可以用通过app/src/res/values/string.xml
文件内定义,使用getResouces().getString(R.string.value)
获取到,第三个参数是Toast
显示的时长,有两个内置常量可以选择Toast.LENGTH_SHORT>默认显示2秒 && Toast.LENGTH_LONG>默认显示3.5秒
.
在活动中使用Menu
首先在
res
目录下面创建一个menu
文件夹,接着在此文件夹下再新建一个名叫main
菜单文件,右击menu > New > Menu resouces fil > main.xml
.
1 |
|
这里我们创建了两个菜单项,其中
<item>
标签就是用来创建具体某一个菜单项,然后通过android:id
给这个<item>
指定一个唯一的标识符,通过android:title
给<item>
指定一个名称.
接着我们重新回到
FirstAcitivity
中来重写onCreateOptionsMenu()
方法,重写方法可以使用Ctrl + O
快捷键
1 | //重写 onCreateOptionsMenu() 方法用于调用菜单资源文件 显示菜单 |
通过
getMenuInflater()
方法能够得到MenuInflater
对象,再调用它的Inflater()
方法就可以给当前活动创建才当了,Inflater()
方法接收两个参数,第一个参数用于指定我们通过那一个资源文件来创建菜单,这里传入R.menu.main
,第二个参数用于指定我们的菜单项将添加到哪一个Menu
对象当中,这里直接使用onCreateOptionsMenu()
方法传入的menu
参数,然后给这个方法返回true
,true
用来表示允许创建的菜单显示出来,如果返回了false
,创建的菜单将无法显示.
定义显示菜单响应事件, 在``FirstActivity
中重写
onOptionsItemSelected()`方法:
1 | //重写 onOptionsItemSelected() 方法定义菜单响应事件 |
在
onOptionsItemSelected()
方法中,通过调用item.getItemId()
来判断我们点击的是哪一个菜单项,然后给每个菜单项加入子的逻辑处理,这里我们弹出一个刚学的的Toast
.
销毁一个活动
通过点击
Back
键销毁当前活动,不过如果你不想通过按键的方式,而是希望使用代码的方式来销毁活动,Acitivity
类提供了一个finish()
方法,在活动中调用此方法就可以销毁活动.
1 | button1.setOnClickListener(new View.OnClickListener() { |
再次运行程序,这是点击一下按钮, 当前活动就被成功销毁了,效果同按下
Back
键.
使用Intent
在活动之间穿梭
使用显式Intent
创建活动,右击
app > New > Acitivity > Empty Acitivity
我们为此活动命名SecondAcitivity
并且勾选Generate Layout File
为此活动添加布局文件,但不要勾选Launcher Acitivity
.
1 |
|
为生成的
activity_second.xml
布局文件替换成以上代码,同时还手动定义了一个按钮按钮显示为Button 2
, 查看AndroidManifest.xml
文件中是否注册SecondAcitivity
活动,如果Android Studio
没有自动完成,我们手动添加<activity>
标签写入<android:name />
属性.
由于
SecondAcitivity
不是主活动,因此不需要配置<intent-filter>
标签。
Intent
是Android
程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据,Intent
一般可被用于启动活动、启动服务以及发送广播等场景.
Intent
大致可以分为两种:显式Intent
和隐式Intent
,现在学习显式Intent
如何使用.
Intent
有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?>cls)
这个构造函数接收两个参数,其中第一个参数是Context
要求提供一个启动活动的上下文,第二个参数Class
则是指定想要启动的目标活动,通过这个构造函数就可以构建出Intent
的“意图”,Acitivity
类中提供了一个startAcitivity()
方法用于启动活动,它接收一个Intent
参数,这里我们将构建好的Intent
传入startAcitivity()
方法就可以启动目标活动了.
1 | //为 button1 注册监听器 |
使用隐式Intent
相比于显式
Intent
,隐式Intent
含蓄了许多,他不明确指出我们想要启动哪一个活动,而是制定了一系列更为抽象的action && category
等信息然后交由系统去分析这个Intent
并帮我们找出合适的活动去启动,合适的活动:简单来说就是可以响应我们这个隐式
Intent
的活动,那么目前我们的SecondAcitivity
可以响应什么养的隐式Intent
呢?
通过在
<activity> > <intent-filter>
标签内配置的内容,可以指定当前活动能够相应的action && categroy
打开AndroidManifest.xml
添加如下代码:
1 | <activity android:name=".SecondActivity" |
在
<action>
标签中我们指明了当前活动可以响应io.pure.firsttestactivity.ACTION_START
这个action
,而<category>
标签中包含了一些附加信息,更加精确的指明了当前的活动能够响应的Intent
中还可能带有的category
,只有<action> && <category>
中的内容同时能够匹配上Intent
中指定的action && <category>
时,这个活动才能响应Intent
,修改FirstTestActivity
中的按钮点击事件:
1 | //为 button1 注册监听器 |
可以看到,我们使用了
Intent
的另一个构造函数,直接而将action
的字符串传了进去,可以看见现在Android Studio
中的Intent
参数提示为action
,表明我们想要启动能够响应io.pure.firsttestacitivity.ACTION_START
这个action
活动,前面说了需要<action> && <category>
同时匹配才可以响应,为什么这里没有看见指定category
,这是因为我们在AndroidManifest.xml
中<category>
标签中的android.intent.category.DEFAULT
是一种默认的category
在调用startActivity()
方法时会自动将这个category
添加到Intent
中.现在重新运行程序发现你使用隐式
Intent
已经成功了,在<action> && <category>
中的内容已经生效了.
每个
Intent
中只能指定一个action
但却能指定多个category
这里我们指定了一个自定义的category
值为io.pure.fifsttestacitivity.MY_CATEGORY
,重新运行程序你会发现,程序崩溃了!Android Studio
中使用Alt + 6
打开
logcat
查看错误日志,
1 | 2020-12-27 20:08:52.801 25783-25783/io.pure.firsttestacitivity E/AndroidRuntime: FATAL EXCEPTION: main |
错误信息提醒我们没有发现处理意图的活动即是没有任何一个活动可以响应我们的
Intent
,这是因为我们刚刚在Intent
中新增了一个category
, 而AndroidManifest.xml > SecondActivity > <intent-filter>
标签中并没有声明可以响应这个category
所以就出现了没有任何活动可以响应该Intent
的情况,
1 | <activity android:name=".SecondActivity" |
我们向
<intent-filter>
中在添加一个category
的声明,要保留默认声明哦!
更多隐式Intent
的用法
使用隐式
Intent
我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使多个应用程序之间的功能共享成为了可能,比如说你的程序中要站是一个网页这是你没必要自己去实现一个浏览器,而是只需要调用系统的浏览器来打开这个网页就行了,修改FirstTestAcitivity
中的按钮点击事件代码如下:
1 | button1.setOnClickListener(new View.OnClickListener() { |
首先指定了
Intent
的action
是Intent.ACTION_VIEW
,这是一个Android
系统内置的动作,其常量值为android.intent.action.VIEW
,然后通过Url.parse()
这个方法将一个网址字符串解析成一个Uri
对象,再调用setData()
方法将这个Uri
对象传递进去,重新运行程序, 点击按钮就会可以看见打开了系统浏览器,方法其实并不复杂,他接收一个
Uri
对象,主要用于指定当前Intent
正在操作的数据,而这些数据同城都是以字符串的形式传入到Uri.parse()
方法中解析产生的.
于此对应我们还可以在
<intent-filter>
中在配置一个<data>
标签,用于更精确的指定当前活动能够响应什么类型的数据,<data>
标签中主要可以配置一下内容:
android:scheme
> 用于指定数据的协议部分,如上面的http
部分.android:host
> 用于指定数据的主机名部分,如上面的baidu.com
部分.android:port
> 用于指定数据的端口部分,一般紧随在主机名之后.android:path
> 用于指定主机名和端口之后的部分,如一段网址跟在域名之后的内容地址.android:mimeType
> 用于指定可以处理的数据类型,允许使用通配符的方式进行指定.
只有
<data>
标签中指定的内容和Intent
中携带的Data
完全一致时,当前活动才能够响应该Intent
,不过一般的<data>
标签中都不会指定太多内容,如上面例子中 其实只需要指定android:scheme="http"
就可以响应所有的http
协议的Intent
了.
除了指定
http
协议外,我们还可以指定很多其他协议,比如geo && tel
等,下面的代码展示了如何在我们的程序中调用系统拨号界面.
1 | button1.setOnClickListener(new View.OnClickListener() { |
向下一个活动传递数据
Intent
中提供了一系列putExtra()
方法的重载,可以把我们想要的传递的数据暂存在Intent
中,启动了另一个活动后,只需要把这些数据再从Intent
中去取出就可以.
1 | button1.setOnClickListener(new View.OnClickListener() { |
这里还是使用显示
Intent
的方式启动SecondAcitivity
,并通过putExtra()
方法传递了一个字符串,putExtra()
方法这里接收了两个参数,第一个参数是key
,第二个是value
,然后我们在SecondAcitivity
中将传递的数据取出并打印出来:
1 |
|
首先可以通过
getIntent()
方法获取到用于启动SecondAcitivity
的Intent
然后调用getStringExtra()
方法传入对应的key
就可以得到相应的数据了,这里由于我们传递的是字符串所以使用的getStringExtra()
方法来获取传递的数据,如果传递的整形数据则使用getIntExtra()
方法,如果传递的是布尔类型数据,则使用getBooleanExtra()
方法,以此类推.
重新运行程序,打开
logcat
选择到Debug
可以看见我们从FirstAcitivity
中传递过来并打印的数据.
返回数据给上一个活动
返回上一个活动只需要按一下
Back
键就可以,并没有一个用于启动活动Intent
来传递数据,通过查阅文档你会发现,Acitivity
中还有一个startAcitivityForResult()
方法也适用于启动活动的但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动,
startAcitivityForResult()
方法接收两个参数,第一个参数还是Intent
,第二个参数是请求码requestCode
,用于在之后回调中判断数据的来源,修改FirstAcitivity
中按钮的点击事件:
1 | button1.setOnClickListener(new View.OnClickListener() { |
这里我们使用了
startAcitivityForResult()
方法启动SecondAcitivity
请求码只要是一个唯一值就可以了,这里传入了1
,接下来我们在SecondAcitivity
中给按钮注册事件:
1 | btn2 = (Button) findViewById(R.id.button_2); |
可以看见我们还是构建了一个
Intent
不过这个Intent
仅仅是用于传递数据而已,他并没有指定意图,然后调用了setResult()
方法用于向上一个活动返回数据的,setResult()
方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK && RESULT_CANCELED
这两个值,第二个参数则是把带有数据的Intent
传递回去,然后调用了finish()
方法来销毁当前的活动
由于我们使用
startAcitivityForResult()
方法来启动SecondAcitivity
,在SecondAcitivity
被销毁之后会回调上一个活动的onAcitivityResult()
方法因此我们需要在FirstAcitivity
中重写这个方法来获取返回的数据,
1 |
|
onAcitivityResult()
带有三个参数,第一个参数是requestCode
,第二个参数是resultCode
,即是我们在启动活动传入的请求码,第二个参数即是我们在返回数据时传入的处理结果,第三个参数是data
即是携带返回数据的Intent
由于在一个活动中有可能调用startAcitivityForResult()
方法来启动很多不同的活动每一个活动返回的数据都会回调到onAcitivityResult()
方法中因此我们首先要做的就是通过检查requestCode
来判断数据来源,确定数据是SecondAcitivity
返回的我们在通过resultCode
的值判断处理结果是否成功,最后从data
中取值并打印出来.
重新运行程序,在
FirstAcitivity
界面点击按钮会打开SecondAcitivity
然后在SecondAcitivity
界面中点击按钮会回到FirstAcitivity
,这时查看logcat
的打印信息.
如果用户在
SecondAcitivity
中并不是通过点击按钮而是通过按下Back
返回到FirstAcitivity
这样数据不就没法返回了吗,我们可以通过在SecondAcitivity
中重写onBackPressed()
方法来解决问题:
1 |
|
当用户按下
Back
键,就回去执行onBackPerssed()
方法中的代码.
活动的生命周期
活动的生命周期对任何
Android
开发者来说都非常重要,只有深入理解了活动的生命周期才可以写出更加连贯流畅地程序,
返回栈
堆栈都已经很熟悉了,经过前面活动的学习,也发现了这一点,
Android
中的活动是可以层叠的,我们每启动一个新的活动,就会覆盖在原活动之上,然后点击Back
键会销毁最上面的活动,下面的一个活动就会重新显示出来,
Android
是使用任务Task
来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈Back Stack
,栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,他会在返回栈中入栈,并处于栈顶的位置,而每当我们按下Back
键或调用finish()
方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置,系统总会显示处于栈顶的活动给用户,
活动状态
每个活动在其生命周期中最多可能会有
4
种状态.
运行状态
当一个活动位于返回栈的栈顶时,这时活动就处于运行状态,系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用于体验.
暂停状态
当一个活动不在处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态,处于暂停状态的活动是完全存活的,系统也不愿意去回收这种活动,因为太还是可见的活动,回收可见的东西都会在用户体验方面有不好的影响,只有在内存极低的情况下,系统才会考虑回收这种活动.
停止状态
当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态,系统仍然会给这种活动保存相应的状态和成员变量,但是这并不是完全可靠,如果其他地方需要内存,处于停止状态的活动就可能会被系统回收.
销毁活动
当一个活动从返回栈中移除后就变成了销毁状态,系统会最倾向于回收这种状态的活动,从而去保证手机内存充足.
活动的生存期
Acitivity
类中定义了7
回调方法,覆盖了活动生命周期的每一个环节:
onCreate()
: 这个方法你已经看到很多次了,每个活动中我们都重写了这个活动,他会在活动第一次被创建的时候调用,你应该在这个方法中完成活动的初始化操作,比如加载布局资源,绑定事件等.onStart()
:这个方法在活动有不可见变为可见的时候调用.onResume()
:这个方法在活动准备好和用户进行交互的时候调用,此时的活动一定位于返回栈的栈顶,并且处于运行状态.onPause()
:这个方法在系统准备去启动给或者恢复另一个活动的时候调用,我们通常会再这个方法中将一些消耗CPU
的资源释放掉,以及保存一些关键数据,但这个方法执行速度一定要快,不然会影响到新的栈顶活动的使用.onStop()
:这个方法再活动完全不不可见的时候调用,他和onPause()
方法的主要区别在于如果启动的新活动是一个对话框式的活动,那么onPause()
方法会得到执行,而onStop()
方法并不会执行.onDestory()
:这个方法在活动被销毁之前调用,之后活动状态将变为销毁状态.onRestart()
:这个方法再活动由停止状态变为运行状态之前调用,也就是活动被重新启动了
以上7个方法中除了
onRestart()
方法,其他方法都是两两相对的,从而又可以将活动分为3种生存期.
完整生存期:活动在
onCreate()
方法和onDestory()
方法之间所经历的,就是完整生存期,一般情况下,一个活动会在onCreate()
方法种完成各种初始化操作,而在onDestory()
方法种完成释放内存的操作.可见生存期:活动在
onStart()
方法和onStop()
方法之间所经历的,就是可见生存期,在可见生存期内,活动对于用户总是可见的,即使有可能无法和用户进行交互4
第二章 UI开发点滴
TextView
我们在布局文件中添加
TextView
控件,修改布局文件如下
1 |
|
android:id
:给控件定义一个唯一标识符
match_parent
:表示让当前控价大小和父布局大小一样
fill_parent
:同match_parent
但是现在官方更推荐用match_parent
wrap_content
:表示让当前控件的大小能够刚好包含住里面的内容
最常用也最难用的控件ListView
ListView
的简单用法
首先创建一个
ListViewTest
项目,修改布局文件种根元素为LinearLayout
手动添加ListView
控件如下设置宽高都为
match_parent
这样就占满了整个布局空间
1 |
|
接下来修改
MainAcitivity
中的代码我们使用了
ArrayAdapter
,它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入,ArrayAdapter
有多个构造函数的重载,可以根据情况来选择最合适的,ArrayAdapter
中依次传入上下文,ListView
子项布局的id
以及要适配的数据,这里我们使用了android.R.layout.simple_list_view_1
作为ListView
子项布局的id
,这是一个Android
内置的布局文件,里面只有一个TextView
可用于简单的显示一段文本,最后还需要调用
ListView
的setAdapter()
方法将构建好的适配器对象传递进去,这样ListView
和数据之间就建立起了关联
1 | public class MainActivity extends AppCompatActivity { |
定制ListView
的界面
定义一个实体类,作为
ListView
适配器的适配类型,新建类Fruit
如下
Fruit
类中只有两个字段,name
表示水果的名字,imageId
表示水果对应图片的资源id
1 | public class Fruit { |
新建
fruit_item.xml
文件,为ListView
的子项绑定自定义布局在这个布局中我们定义了一个
ImageView & TextView
用于显示水果的图片和水果的名称,
1 |
|
创建一个自定义的适配器,这个适配器继承自
ArrayAdapter
,并将泛型指定Fruit
类,新建类FruitAdapter
1 | public class FruitAdapter extends ArrayAdapter<Fruit> { |
重写
getView()
方法,通过getItem()
得到当前项的Fruit
实例,然后使用LayoutInflater
为子项加载我们传入的布局,这里LayoutInflater
的inflater()
方法接收3
参数,这里就不多去阐述说明参数意义
接下来修改
MainAcitivity
中的代码为
1 | public class MainActivity extends AppCompatActivity { |
initFruits()
初始化水果数据,在Fruit
类中将水果的图片id
和水果名字传入,我们还使用了for
循环将所有水果数据添加了4
遍这是因为如果只添加一遍的话,这点数据量不足以充满整个屏幕,
Android Studio && Android 开发笔记
https://purekits.github.io/2021/02/25/Android Studio && Android 开发笔记/