android基础01 - Activity、控件布局、Fragment
四大组件
- Activity
- Service
- BroadcastReceiver
- ContentProvider
使用资源,代码中使用 R.string.app_name
,XML中使用 @string/app_name
。
Activity
一个Activity注意3部分:
- 后端文件
src/main/java/com/example/app/XXXActivity.kt
在重写的 onCreate 方法中setContentView(R.layout.activity_main)
使用资源R.layout.activity_app
R.string.app_name
R.资源分类.资源名
省略 findViewById:https://blog.csdn.net/guolin_blog/article/details/113089706 - 前端页面
src/res/layout/activity_app.xml
使用资源 使用已有id:@id/button1
定义一个新id:@+id/button2
- 在 AndroidManifest.xml 中配置
一般创建Activity时自动添加
Intent 实现跳转
不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动Activity、启动Service以及发送广播等场景
- 显式
// 使用ViewBinding简化了 findViewById var inflate = ActivityMainBinding.inflate(layoutInflater) inflate.button01.setOnClickListener { val intent = Intent(this, SecondActivity::class.java) // 显示Intent,跳到别的Activity startActivity(intent) }
通过返回键销毁新Activity返回到旧的
- 隐式
不明确指出想要启动哪一个Activity,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的Activity去启动。
在 AndroidManifest.xml 中配置 intent-filter 执行处理的 action 和 category <activity android:name=".SecondActivity" android:exported="false"> <meta-data android:name="android.app.lib_name" android:value="" /> <intent-filter> <action android:name="com.example.activitytest.ACTION_START"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="com.example.activitytest.MY_CATEGORY"/> </intent-filter> </activity>
inflate.button01.setOnClickListener { // val intent = Intent(this, SecondActivity::class.java) // 显式 val intent = Intent("com.example.activitytest.ACTION_START") // 隐式 intent.addCategory("com.example.activitytest.MY_CATEGORY") // 指定category,和action一起工作,可省略 startActivity(intent) }
- 通过data隐式响应
<activity android:name=".ThirdActivity"> <intent-filter tools:ignore="AppLinkUrlError"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="https" /> android:host android:port android:path android:mimeType </intent-filter> </activity>
button1.setOnClickListener { val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse("https://www.baidu.com") startActivity(intent) } // 打电话 val intent = Intent(Intent.ACTION_DIAL) intent.data = Uri.parse("tel:10086")
- 向Activity传数据
putExtra() getStringExtra() getIntExtra()
button1.setOnClickListener { val data = "Hello SecondActivity" val intent = Intent(this, SecondActivity::class.java) intent.putExtra("extra_data", data) startActivity(intent) } // 其他 Activity class SecondActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.second_layout) val extraData = intent.getStringExtra("extra_data") Log.d("SecondActivity", "extra data is $extraData") } }
- 返回数据给上个activity
button1.setOnClickListener { val intent = Intent(this, SecondActivity::class.java) startActivityForResult(intent, 1) // 第二个请求吗用于判断跳转来源 } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { // 接受结果 super.onActivityResult(requestCode, resultCode, data) when (requestCode) { 1 -> if (resultCode == RESULT_OK) { val returnedData = data?.getStringExtra("data_return") Log.d("FirstActivity", "returned data is $returnedData") } } } // 跳转到的Activity class SecondActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.second_layout) // 手动返回 button2.setOnClickListener { val intent = Intent() intent.putExtra("data_return", "Hello FirstActivity") setResult(RESULT_OK, intent) // 第一个参数还可返回 RESULT_CANCELED, finish() } } // 通过返回键返回 override fun onBackPressed() { val intent = Intent() intent.putExtra("data_return", "Hello FirstActivity") setResult(RESULT_OK, intent) finish() } }
运行状态
- 完整生存期。Activity在onCreate()方法和onDestroy()方法之间所经历的就是完整生存期。一般情况下,一个Activity会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作。
- 可见生存期。Activity在onStart()方法和onStop()方法之间所经历的就是可见生存期。在可见生存期内,Activity对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的Activity不会占用过多内存。
- 前台生存期。Activity在onResume()方法和onPause()方法之间所经历的就是前台生存期。在前台生存期内,Activity总是处于运行状态,此时的Activity是可以和用户进行交互的,我们平时看到和接触最多的就是这个状态下的Activity。
后台的Activity被销毁后,返回页面时会调用onCreate再构建一次,此时会丢失临时数据
// 此处的Bundle会作为onCreate方法的参数传入
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val tempData = "Something you just typed"
outState.putString("data_key", tempData)
}
启动模式
在AndroidManifest.xml中通过给标签指定 android:launchMode属性来选择启动模式
- standard
默认,启动后在栈顶创建,再启动时不管是否栈中存在,一定再创建一个 - singleTop
处于栈顶时不再创建,否则创建新的
<activity android:name=".FirstActivity" android:launchMode="singleTop" android:label="This is FirstActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
- singleTask
创建时,如果没有新的则创建;否则将栈中其上的全部销毁,使其到栈顶
<activity android:name=".FirstActivity" android:launchMode="singleTask" android:label="This is FirstActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
- singleInstance
启用一个新的返回栈来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。
程序的某个Activity允许其他程序调用,这种模式下,会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用同一个返回栈,也就解决了共享Activity实例的问题。
<activity android:name=".SecondActivity" android:launchMode="singleInstance"> <intent-filter> <action android:name="com.example.activitytest.ACTION_START" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="com.example.activitytest.MY_CATEGORY" /> </intent-filter> </activity>
开发技巧
定位当前 Activity
创建一个BaseActivity 用于继承
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("BaseActivity", javaClass.simpleName)
}
}
随时退出程序
要用一个专门的集合对所有的Activity进行管理,实现一个按键退出所有Activity
// 单例列表
object ActivityCollector {
private val activities = ArrayList<Activity>()
fun addActivity(activity: Activity) {
activities.add(activity)
}
fun removeActivity(activity: Activity) {
activities.remove(activity)
}
fun finishAll() {
for (activity in activities) {
// 注意在销毁Activity之前,我们需要先调用activity.isFinishing来判断Activity是否正在销毁中,因为Activity还可能通过按下Back键等方式被销毁
if (!activity.isFinishing) {
activity.finish()
}
}
activities.clear()
// 杀死当前进程
android.os.Process.killProcess(android.os.Process.myPid())
}
}
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("BaseActivity", javaClass.simpleName)
ActivityCollector.addActivity(this)
}
override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
}
// 一个按键退出所有
button3.setOnClickListener {
ActivityCollector.finishAll()
}
启动 Activity 最佳写法
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("param1", "data1")
intent.putExtra("param2", "data2")
startActivity(intent)
上面正确,但是容易出现问题。理不清关系
class SecondActivity : BaseActivity() {
// ...
// 所有定义在companion object中的方法都可以使用类似于Java静态方法的形式调用
companion object {
fun actionStart(context: Context, data1: String, data2: String) {
val intent = Intent(context, SecondActivity::class.java)
intent.putExtra("param1", data1)
intent.putExtra("param2", data2)
context.startActivity(intent)
}
}
}
// 它的使用
button1.setOnClickListener {
SecondActivity.actionStart(this, "data1", "data2")
}
UI 开发
控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:textColor="#00ff00"
android:textSize="24sp"
android:text="This is TextView"/>
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false" //取消大写
android:text="Button" />
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here" // 提示文本
android:maxLines="2" // 内容过长时选择换行
/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/img_1" // 将图片资源放在 src/drawable 目录下
/>
// 默认是不停转的圆圈
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" // 每个控件都有这个属性,默认visible可见,invisible透明,gone隐藏
// style="?android:attr/progressBarStyleHorizontal" // 指定为水平进度条
/>
</LinearLayout>
class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.button -> {
val inputText = editText.text.toString()
Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show()
imageView.setImageResource(R.drawable.img_2)
}
R.id.button1 -> {
if (progressBar.visibility == View.VISIBLE) {
progressBar.visibility = View.GONE
} else {
progressBar.visibility = View.VISIBLE
}
}
R.id.水平进度条 -> {
progressBar.progress = progressBar.progress + 10
}
R.id.弹出对话框 -> {
AlertDialog.Builder(this).apply {
setTitle("This is Dialog")
setMessage("Something important.")
setCancelable(false)
setPositiveButton("OK") { dialog, which ->
//...
}
setNegativeButton("Cancel") { dialog, which ->
//...
}
show()
}
}
}
}
}
布局
- 线性布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" // horizontal android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top" // 相对位置,horizontal布局只能指定垂直方向,vertical只能指定水平方向 android:layout_weight="1" // 通过指定权重进行相对布局,宽度或高度应为0dp。 // 某行中按钮控件指定宽度,输入框指定权重,可实现按钮宽度注定,输入框占据剩余宽度 android:text="Button 1" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="Button 2" /> </LinearLayout>
- 相对布局
相对父布局
// 通过相对定位,让控件出现在任何位置 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> // 相对父布局定位,让Button 1和父布局的左上角对齐,Button 2和父布局的右上角对齐, // Button 3居中显示,Button 4和父布局的左下角对齐,Button 5和父布局的右下角对齐 <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="Button 1" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:text="Button 2" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Button 3" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:text="Button 4" /> <Button android:id="@+id/button5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:text="Button 5" /> </RelativeLayout>
相对控件
// 控件之间相对 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Button 3" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/button3" android:layout_toLeftOf="@id/button3" android:text="Button 1" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/button3" android:layout_toRightOf="@id/button3" android:text="Button 2" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/button3" android:layout_toLeftOf="@id/button3" android:text="Button 4" /> <Button android:id="@+id/button5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/button3" android:layout_toRightOf="@id/button3" android:text="Button 5" /> </RelativeLayout>
- 帧布局
所有的控件都会默认摆放在布局的左上角帧布局
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left" android:text="This is TextView"/> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="Button"/> </FrameLayout>
自定义控件(页面代码复用)
1. 创建布局
所有控件直接或间接继承自View,所有布局直接或间接继承自ViewGroup。ViewGroup是个特殊容器,能包含多个View或多个ViewGroup。
创建一种新的布局,在页面顶端加上一个标题框和后退按钮:
在layout目录下新建一个title.xml布局
// 一个普通页面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bg">
<Button
android:id="@+id/titleBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/back_bg"
android:text="Back"
android:textColor="#fff" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="Title Text"
android:textColor="#fff"
android:textSize="24sp" />
<Button
android:id="@+id/titleEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/edit_bg"
android:text="Edit"
android:textColor="#fff" />
</LinearLayout>
在页面中调用:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<include layout="@layout/title" />
</LinearLayout>
在页面的后端代码中的onCreate中用supportActionBar?.hide()
隐藏自带的标题栏
2. 创建控件
为自定义控件添加事件
class TitleLayout(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
init {
LayoutInflater.from(context).inflate(R.layout.title, this)
titleBack.setOnClickListener {
val activity = context as Activity
activity.finish()
}
titleEdit.setOnClickListener {
Toast.makeText(context, "You clicked Edit button", Toast.LENGTH_SHORT).show()
}
}
}
在activity_main.xml 中使用控件,这时不用编写额外代码,按键也会监听事件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.uicustomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
ListView
最简单使用
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
class MainActivity : AppCompatActivity() {
private val data = listOf("Apple", "Banana", "Orange", "Watermelon",
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
"Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
"Pineapple", "Strawberry", "Cherry", "Mango")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = ArrayAdapter<String>(this,android.R.layout.simple_list_item_1/*内置布局文件,只有一个TextView*/,data)
listView.adapter = adapter
}
}
RecyclerView
升级版ListView,定义在AndroidX库中,在项目的build.gradle中添加RecyclerView库的依赖所有版本都可以使用。
略
Fragment
与Activity类似,主要用于适配平板。
- 简单使用
先定义两个layout的xml文件
// 左.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Button" /> </LinearLayout> // 右.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:background="#00ff00" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:textSize="24sp" android:text="This is right fragment" /> </LinearLayout>
新建Fragment类
class LeftFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.left_fragment, container, false) } } class RightFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.right_fragment, container, false) } }
layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" > <fragment android:id="@+id/leftFrag" android:name="com.example.fragmenttest.LeftFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> <fragment android:id="@+id/rightFrag" android:name="com.example.fragmenttest.RightFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout>
- 动态添加Fragment
another_right_fragment.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:background="#ffff00" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:textSize="24sp" android:text="This is another right fragment" /> </LinearLayout>
再创建一个新的Fragment对象
class AnotherRightFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.another_right_fragment, container, false) } }
将 layout_main 中的右侧fragment 对象更换为 FragmentLayout 布局,它将控件默认放在左上角,十分适合。
注意要手动添加到返回栈class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button.setOnClickListener { replaceFragment(AnotherRightFragment()) } replaceFragment(RightFragment()) // 调用方法时创建待添加的 Fragment 实例 } private fun replaceFragment(fragment: Fragment) { // 获取事务并开启 val fragmentManager = supportFragmentManager val transaction = fragmentManager.beginTransaction() // 向容器内添加或替换Fragment,一般用replace() 实现,传入容器id和待添加的Fragment实例 transaction.replace(R.id.rightLayout, fragment) // 添加到程序返回栈 transaction.addToBackStack(null) transaction.commit() } }
- Fragment 与 Activity 之间的通信
// Activity 获取 Fragment val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment // Fragment 获取 Activity // 通过调用getActivity()方法来得到和当前Fragment相关联的Activity实例 // Fragment 与 Fragment 通信 // Fragment先获得一个Activity,再通过它获得其他的Fragment
动态加载布局的技巧
限定符
平板上使用双页模式,手机使用单页模式,自动切换。
在 src/main/res/layout/activity_main.xml 中创建单页页面
在 src/main/res/layout_large/activity_main.xml 中创建双页页面
常见限定符:
最小宽度限定符
创建 layout-sw600dp 文件夹,当分辨率大于等于600dp时使用这个页面。