[Android基础] 学习记录1

文章目录

一. Android总览

1. 系统架构

在这里插入图片描述
应用层:所有安装在手机上的应用程序
应用框架层:提供了构建应用程序时可能用到的各种API
系统运行库层:通过一些C/C++库为Android系统提供了主要的特性支持。如SQLite,OpenGL|ES
Linux内核层:Android系统是基于Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理等。

2. 开发环境

在这里插入图片描述
Gradle项目:可以先下载到本地,后边建立项目快一点
Gradle配置教学
Gradle理论知识

3. 在红米手机上运行

连接usb-设置-我的设备-全部参数-连点6次版本号-返回首页,更多设置-开发者选项

4. 项目资源详解

4.1 整体结构

4.2 res文件

在这里插入图片描述
drawable 放图片
mipmap 放应用图标
values 字符串、样式、颜色
layout 布局
xml 一些其他配置规则,如数据提取的规则等
引用规则
在这里插入图片描述

4.3 build.gradle文件

4.4 AndroidMainfest.xml文件

  • 属性:android:exported
`android:exported="true"` 是 Android 应用开发中的一个属性,用于定义应用的组件是否可以被其他应用访问。

在 Android 应用的 `AndroidManifest.xml` 文件中,应用的组件(如 `Activity`、`Service`、`BroadcastReceiver` 
和 `ContentProvider`)可以设置 `android:exported` 属性。这个属性的值可以是 `true` 或 `false`,其含义如下:
- `android:exported="true"`: 组件可以被其他应用访问和启动。这意味着其他应用可以通过意图(Intent)启动该组件。
- `android:exported="false"`: 组件不能被其他应用访问。只有本应用内部的组件可以启动它。

这个属性在 Android 12(API 级别 31)及更高版本中变得更为重要,系统要求所有具有意图过滤器的组件必须显式声明 
`android:exported` 属性,以确保应用的安全性。如果没有声明这个属性,应用在安装时会发生错误。

二. Activity(四大组件)

1. 创建一个Activity并实现一些简单功能

选择No Activity方式创建一个空的Project
切换成project模式,到主目录下创建一个kt文件
写一个主类 + res里面的布局文件 + AndroidMainfest.xml注册
实现功能包含:

  • 创建
  • Toast显示文本提示
  • menu菜单栏 onCreateOptionsMenu函数中调用menuInflater.inflate方法
  • 打印当前activity的名称
package com.example.myactivity

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class FirstActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.mylayout)
        //给按钮设置点击事件
        val button1:Button = findViewById(R.id.button1) 
        button1.setOnClickListener{
            Toast.makeText(this,"This is my first attempt",Toast.LENGTH_SHORT).show()
        }
      //销毁当前activity
	   val button2:Button = findViewById(R.id.button2)
	   button2.setOnClickListener{
	           finish()
	       }
    }
	//menu菜单选择栏
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
//inflate 方法将指定的菜单资源文件 R.menu.main 填充到传入的 menu 对象中。
//这样,菜单项就会被创建并显示在选项菜单中
        menuInflater.inflate(R.menu.main,menu)
        return true//true表示会显示菜单
    }
	//响应事件
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId){
            R.id.add_item -> Toast.makeText(this,"Add",Toast.LENGTH_SHORT).show()
            R.id.remove_item -> Toast.makeText(this,"remove",Toast.LENGTH_SHORT).show()
        }
        return true
    }
    //当前是哪一个activity
    Log.d("BaseActivity", javaClass.simpleName)


}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name" #标题栏的内容
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyActivity"
        tools:targetApi="31">

        <activity android:name=".FirstActivity"
            android:label="FirstActivity"
            android:exported="true">
            <intent-filter> 
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

menu菜单栏需要在res目录下建立menu文件夹

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/add_item"
        android:title="Add"/>
    <item
        android:id="@+id/remove_item"
        android:title="Remove"/>
</menu>

2. 显式和隐式启动

场景描述:在一个Activity中启动另外一个Activity
准备:再创建一个activity

2.1 显式启动 (常用)

写法一:

      val intent = Intent(this,SecondActivity::class.java)
      intent.putExtra("param1", "data1")
	  intent.putExtra("param2", "data2")
      startActivity(intent)

存在问题 如果不知道第二个activity的代码,我怎么传数据?
写法二:
修改第二个Activity的代码,新增如下:

    companion object {
        @JvmStatic
        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)
        }
    }

在第一个activity中添加如下代码:

SecondActivity.actionStart(this,"data1","data2")

2.2 隐式启动

(1) 标准用法
修改第二个activity的xml文件,指定类型;让intent去响应该类型

#为方便演示 添加了两个类型
<activity android:name=".SecondActivity" >
<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>
button1.setOnClickListener {
val intent = Intent("com.example.activitytest.ACTION_START")
intent.addCategory("com.example.activitytest.MY_CATEGORY") //1个intent可以响应多个类型
startActivity(intent)
}

(2)其他用法
启动浏览器

button1.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www.baidu.com")
startActivity(intent)
}

打电话

button1.setOnClickListener {
val intent = Intent(Intent.ACTION_DIAL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)

自己也可以指定activity能够响应的类型
在这里插入图片描述

3. 继承实现关系

在这里插入图片描述
参考文献

  • Context
  • ContextWrapper
  • ContextThemWrapper
  • Activity
  • ComponentActivity:提供对Lifecycle、ViewModel、LiveData等架构组件的支持
  • FragmentActivity:提供对于fragment的支持
  • AppCompatActivity:提供了对于ActionBar和Material Design主题和样式的支持

4. 生命周期

4.1 返回栈

Android是使用任务(task)来管理Activity的,一个任务就是一组存放在栈里的Activity的集合,这个栈也被称作返回栈(back stack)。
在默认情况下,每当我们启动了一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法去销毁一个Activity时,处于栈顶的Activity就会出栈,前一个入栈的Activity就会重新处于栈顶的位置。系统总是会显示处于栈顶的Activity给用户。
在这里插入图片描述

4.2 状态

运行状态:处于返回栈栈顶。系统最不愿意回收
期间触发的函数及顺序为: onCreate() ->onStart() -> onResume()。

暂停状态:部分可见,但不处于栈顶。如对话框形式的Activity只会占用屏幕中间的部分区域。系统不愿意回收。
运行状态到暂停状态所触发的函数及顺序为:onResume() -> onPuased()。
暂停状态恢复至运行状态所触发的函数及顺序为:onPuased() -> onResume()。

停止状态:不处于栈顶,完全不可见。系统仍然会为这种Activity保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的Activity有可能会被系统回收。
暂停状态到停止状态所触发的函数及顺序为:onPuased() -> onStop()。
停止状态恢复至运行状态所触发的函数及顺序为:onStop() -> onRestart() -> onStart() -> onResume()。

销毁状态:从返回栈中移除后。系统最倾向于回收这种Activity。

4.3 生命周期

在这里插入图片描述
完整生存期:create初始化 和 destroy释放内存
可见生存期:strart 加载对用户可见的资源和 stop释放资源。
前台生存期:resume和pause。
onPause () : 准备去启动或者恢复另外一个Activity的时候调用。通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法一定要快,不然会影响到新的栈顶Activity的使用。
onStop (): 这个方法在Activity完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新Activity是一个对话框式的Activity,那么onPause()方法会得到执行,而onStop()方法并不会执行

4.4 onSaveInstanceState() 方法 (TODO)

5. 启动模式

5.1 standard

每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置对于使用standard模式的
Activity,系统不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建一个该Activity
在这里插入图片描述

5.2 singleTop

当Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该Activity,则认为可以直接
使用它,不会再创建新的Activity实例。
在这里插入图片描述

5.3 singleTask

让某个Activity在整个应用程序的上下文中只存在一个实例
当Activity的启动模式指定为singleTask,每次启动该Activity时,系统首先会在返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把在这个Activity之上的所有其他Activity统统出栈,如果没有发现就会创建一个新的Activity实例。
在这里插入图片描述

5.4 singleInstance

singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)
在这里插入图片描述

三. UI布局

1. 常见控件

1.1 控件常用属性

  • 可控性
    所有的Android控件都具有这个属性,可以通过android:visibility进行指定,可选值有3种:visible、invisible和gone。
    visible表示控件是可见的,这个值是默认值,不指定android:visibility时,控件都是可见的。
    invisible表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解成控件变成透明状态了。
    gone则表示控件不仅不可见,而且不再占用任何屏幕空间

1.2 具体控件

  • TextView
    固定文本框
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#00ff00"
android:textSize="24sp"
android:text="This is TextView"/>
  • Button
    按钮
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false" %默认会把所有字母变成大写;加上改行可取消
android:text="Button" />

实现点击事件的两种方式:

//方式一:
button.setOnClickListener {
// 在此处添加逻辑
}
//方式二:继承View.OnClickListner
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 -> {
		// 在此处添加逻辑
	}
}
  • EditText
    可输入文本框
<EditText
	android:id="@+id/editText"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:hint="Type something here" %提示语,可实现移动到就不见的效果
	android:maxLines="2" %最大行数,超过就向上滚东
/>
  • ImageView
    用于显示图片资源
<ImageView
	android:id="@+id/imageView"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:src="@drawable/img_1"
/>
//与button联动实现点击图片切换到另外一张图片的功能
        val imageView:ImageView = findViewById(R.id.imageView)
        button2.setOnClickListener{
            imageView.setImageResource(R.drawable.img_2)
        }
  • ProgressBar
    进度条
<ProgressBar
	android:id="@+id/progressBar"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	style="?android:attr/progressBarStyleHorizontal" %默认是圆形的进度条,可设置成水平的
	android:max="100"%进度条的长度
/>
//改变进度条的可见性
       button2.setOnClickListener{
           imageView.setImageResource(R.drawable.img_2)
           if(progressBar.visibility== View.VISIBLE){
               progressBar.visibility = View.GONE
           }else{
               progressBar.visibility = View.VISIBLE
           }
       }
 // 加载进度条
        val button3:Button = findViewById(R.id.button3)
        button3.setOnClickListener {
            progressBar.progress = progressBar.progress + 20
            if(progressBar.progress==100){
                progressBar.visibility = View.GONE
            }
        }
  • AlterDialog
    可以在当前界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽其他控件的交互能力
         R.id.button4->{
             AlertDialog.Builder(this).apply {
                 setTitle("This is Dialog")
                 setMessage("Something important.")
                 setCancelable(false)
                 setPositiveButton("OK") { dialog, which ->
                 }
                 setNegativeButton("Cancel") { dialog, which ->
                 }
                 show()
             }
         }

2. 常见布局

2.1 LinearLayout 线性布局

  • android:orientation=“vertical”
    在这里插入图片描述
  • android:orientation=“horizontal”
    在这里插入图片描述
  • 其他属性
    android:layout_gravity用于指定控件在布局中的对齐方式
    android:layout_weight。这个属性允许我们使用比例的方式来指定控件的大小,它在手机屏幕的适配性方面可以起到非常重要的作用
    系统会先把LinearLayout下所有控件指定的layout_weight值相加,得到一个总值,然后每个控件所占大小的比例就是用该控件的layout_weight值除以刚才算出的总值
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal"
	android:layout_width="match_parent"
	android:layout_height="match_parent">
	<EditText
	android:id="@+id/input_message"
	android:layout_width="0dp"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:hint="Type something"
	/>
	<Button
	android:id="@+id/send"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="Send"
	/>
</LinearLayout>

在这里插入图片描述

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal"
	android:layout_width="match_parent"
	android:layout_height="match_parent">
	<EditText
	android:id="@+id/input_message"
	android:layout_width="0dp"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:hint="Type something"
	/>
	<Button
	android:id="@+id/send"
	android:layout_width="0dp"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:text="Send"
	/>
	</LinearLayout>

在这里插入图片描述

2.2 RelativeLayout 相对布局

  • 相对于父布局
    android:layout_alignParentLeft
    android:layout_alignParentTop
    android:layout_alignParentRight
    android:layout_alignParentBottom
    android:layout_centerInParent
  • 相对于其他控件
    android:layout_below表示让一个控件位于另一个控件的下方
    android:layout_toLeftOf表示让一个控件位于另一个控件的左侧
    android:layout_toRightOf表示让一个控件位于另一个控件的右侧
    android:layout_alignLeft表示让一个控件的左边缘和另一个控件的左边缘对齐
    android:layout_alignRight表示让一个控件的右边缘和另一个控件的右边缘对齐
    android:layout_alignTop
    android:layout_alignBottom

2.3 FrameLayout

FrameLayout又称作帧布局,它相比于前面两种布局就简单太多了,因此它的应用场景少了很多。这种布局没有丰富的定位方式,所有的控件都会默认摆放在布局的左上角
使用layout_gravity属性来指定控件在布局中的对齐方式

3. ListView

由于手机屏幕空间比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助ListView来实现。ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动出屏幕。

  • Demo演示:
    功能1,显示水果图片和名称,并能进行滚动查看;
    功能2,点击水果能够弹出提示。
    在这里插入图片描述
  • 代码实现
    核心思路,包含了三个元素:listView,数据模型,Adapter,即MVC架构。
    建一个ListView,填充对应的数据模型(新建一个水果类和XML布局文件),然后通过适配器Adapter建立二者的关系。
    滚动翻页功能:重写Adapter里面的getView方法(关键点,需要通过缓存等来进行优化性能)。
    点击功能:setOnItemClickListener方法。
    核心代码 如何写适配器
//水果类
class Fruit(val name:String,val imageId:Int)
//适配器:核心代码
//getView 实现滚动
//优化点1:convertView用于缓存,优化点:不用反复加载布局
//优化点:内部类ViewHolder缓存,无需反复加载控件实例
class FruitAdapter(activity:Activity,val resourceId:Int,data:List<Fruit>)//第二个变量是对应的布局文件!
    :ArrayAdapter<Fruit>(activity, resourceId,data) {
    var tagId:Int = 0 //打印信息的标记,非必要
    inner class ViewHolder (val fruitImage:ImageView,val fruitName:TextView)//内部类用于缓存图片和名称
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val view:View
        val viewHolder:ViewHolder
        if(convertView==null){ //convertView用于缓存,优化点:不用反复加载布局
            view = LayoutInflater.from(context).inflate(resourceId,parent,false)//用于加载视图
            val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
            val fruitName:TextView = view.findViewById(R.id.fruitName)
            viewHolder = ViewHolder(fruitImage,fruitName)//缓存,无需反复加载控件实例
            view.tag = viewHolder //tag
            Log.d("位置" + position, "创建新convertView,设置tagId:" + tagId)//打印信息 验证缓存
            tagId ++
        } else{
            view = convertView
            viewHolder = view.tag as ViewHolder
            Log.d("位置" + position, convertView.getTag().toString() + " 复用convertView");//打印信息 验证缓存

        }
        val fruit =  getItem(position)
        if(fruit!=null){
            viewHolder.fruitImage.setImageResource(fruit.imageId)
            viewHolder.fruitName.text = fruit.name
        }
        return view
    }
}
//只要看listview演示那一段即可
class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_second)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        Log.d("BaseActivity",javaClass.simpleName)
        //listView演示
        initFruits()
        val adapter = FruitAdapter(this,R.layout.fruit_item,fruitList)
        val listView:ListView = findViewById(R.id.listView)
        listView.adapter = adapter
        listView.setOnItemClickListener { _, _, position, _ ->
            val fruit = fruitList[position]
            Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
        } //点击事件 
    }
    //通过第一个Activity启动的 可以不管。
    companion object {
        @JvmStatic
        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)
        }
    }
    //初始化水果
    private val fruitList = ArrayList<Fruit>()
    private fun initFruits() {
        repeat(2) {
            fruitList.add(Fruit("Apple", R.drawable.img_1))
            fruitList.add(Fruit("Banana", R.drawable.img_2))
            fruitList.add(Fruit("Orange", R.drawable.img_1))
            fruitList.add(Fruit("Watermelon", R.drawable.img_1))
            fruitList.add(Fruit("Pear", R.drawable.img_1))
            fruitList.add(Fruit("Grape", R.drawable.img_1))
            fruitList.add(Fruit("Pineapple", R.drawable.img_1))
            fruitList.add(Fruit("Strawberry", R.drawable.img_1))
            fruitList.add(Fruit("Cherry", R.drawable.img_1))
            fruitList.add(Fruit("Mango", R.drawable.img_1))
        }
    }
}

- 核心方法解析
public View getView(int position, View convertView , ViewGroup parent){…} 他是在滚动的时候被嗲用的
在这里插入图片描述
view的tag属性
在这里插入图片描述
listView.setOnItemClickListener
在这里插入图片描述

4. RecylerView

核心部分:子项布局 + 适配器

  • 引入依赖
    implementation ("androidx.recyclerview:recyclerview:1.3.0")
  • Fruit类,Fruit_item布局文件同上一节

  • 写Adapter

class PlusFruitAdapter(val fruitList:List<Fruit>):
        RecyclerView.Adapter<PlusFruitAdapter.ViewHolder>(){

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
        val fruitName: TextView = view.findViewById(R.id.fruitName)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.fruitImage.setImageResource(fruit.imageId)
        holder.fruitName.text = fruit.name
    }
    override fun getItemCount() = fruitList.size
    viewHolder.itemView.setOnClickListener{
       Toast.makeText(parent.context, "you clicked view ",
           Toast.LENGTH_SHORT).show()
   }
   viewHolder.fruitImage.setOnClickListener {
       Toast.makeText(
           parent.context, "you clicked image",
           Toast.LENGTH_SHORT
       ).show()
   }
}

主程序

class MainActivity : ComponentActivity() {
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)

        initFruits()
        val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
        val layoutManager = LinearLayoutManager(this)
        layoutManager.orientation = LinearLayoutManager.HORIZONTAL//实现水平滚动
        recyclerView.layoutManager = layoutManager
        val adapter = PlusFruitAdapter(fruitList)
        recyclerView.adapter = adapter

    }
    private val fruitList = ArrayList<Fruit>()
    private fun initFruits() {
        repeat(2) {
            fruitList.add(Fruit("Apple", R.drawable.img_1))
            fruitList.add(Fruit("Banana", R.drawable.img_2))
            fruitList.add(Fruit("Orange", R.drawable.img_1))
            fruitList.add(Fruit("Watermelon", R.drawable.img_1))
            fruitList.add(Fruit("Pear", R.drawable.img_1))
            fruitList.add(Fruit("Grape", R.drawable.img_1))
            fruitList.add(Fruit("Pineapple", R.drawable.img_1))
            fruitList.add(Fruit("Strawberry", R.drawable.img_1))
            fruitList.add(Fruit("Cherry", R.drawable.img_1))
            fruitList.add(Fruit("Mango", R.drawable.img_1))
        }
    }
}

5. 自定义布局和控件

5.1 引入布局:解决重复布局的问题

在layout目录下新建一个title.xml布局
在需要引入布局的XML文件中,加入 <include layout="@layout/title" />

5.2 自定义控件

应用场景:标题栏中的返回按钮,其实不管是在哪一个Activity中,这个按钮的功能都是相同的,即销毁当前Activity

  1. 新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件
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()
	}
}
}

代码解析:
LayoutInflater.from(context): 这行代码获取一个LayoutInflater对象,该对象用于从XML文件中加载视图。context参数可以是Activity、Fragment或任何实现Context接口的对象。
.inflate(R.layout.title, this): 这行代码使用LayoutInflater对象将R.layout.title对应的XML布局文件加载并实例化为一个视图对象。this参数指定了将加载的视图添加到哪个父视图中

  1. 在布局文件中添加这个自定义控件
<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>

四. Fragment

1. 基本用法

Fragment是一种可以嵌入在Activity当中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间

1.1 简单使用

  • 写xml文件,布局
  • 新建Fragment类,继承Fragment,导入AndroidX的包
class LeftFragment : Fragment() {
	override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
	savedInstanceState: Bundle?): View? {
		return inflater.inflate(R.layout.left_fragment, container, false)
	}
}

方法解析:

`ViewGroup` 是 Android 中的一个重要概念,它是 `View` 类的子类,用于管理和布局其他 `View` 对象。
它是一个容器类,可以包含多个 `View` 对象。
在上面的代码中,`container` 参数就是一个 `ViewGroup` 对象。它代表了当前 `Fragment` 要被放置的父容器。
通常情况下,这个父容器是 `Activity` 的根视图或者是另一个 `ViewGroup`。
`inflater.inflate(R.layout.left_fragment, container, false)` 这行代码的作用是:
 - 使用 `LayoutInflater` 将 `R.layout.left_fragment` 这个布局文件解析成一个 `View` 对象。
 - 将这个 `View` 对象添加到 `container` 这个 `ViewGroup` 中。
 - `false` 表示不要自动将新创建的 `View` 添加到 `container` 中,而是由开发者手动控制添加的时机。
总之, `ViewGroup` 在这里的作用是提供一个容器,用于承载 `Fragment` 的布局视图。
开发者可以根据需求选择合适的 `ViewGroup` 作为 `Fragment` 的父容器。
  • 修改主视图activity_main文件
<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" #包名导入Fragment
	android:layout_width="0dp"
	android:layout_height="match_parent"
	android:layout_weight="1" />
</LinearLayout>

1.2 动态添加Fragment

功能描述:一个Activity分成两个Fragment,左Fragment里面放了一个Button,点击之后,第二个Fragment会切换成另外一个,且第二个Fragment具有回退功能。

class MainActivity : AppCompatActivity() {
...//核心代码
	private fun replaceFragment(fragment: Fragment) {
		val fragmentManager = supportFragmentManager
		val transaction = fragmentManager.beginTransaction()
		transaction.replace(R.id.rightLayout, fragment)
		transaction.addToBackStack(null) //实现回退功能
		transaction.commit()
	}
	private fun replaceFragment(fragment: Fragment) {
		val fragmentManager = supportFragmentManager
		val transaction = fragmentManager.beginTransaction()
		transaction.replace(R.id.rightLayout, fragment)
		transaction.commit()
	}
}

引入FragmentManager进行管理。

1.3 Fragment与Activity之间的通信

  • Activity中调用Fragment
// 方法一:
val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment
// 方法二:引入Kotlin Extension插件后
val fragment = leftFrag as LeftFragment
  • Fragment中调用Activity
if (activity != null) {
	val mainActivity = activity as MainActivity
}
  • Fragment与Fragment
    首先在一个Fragment中可以得到与它相关联的Activity,然后再通过这个Activity去获取另外一个Fragment的实例,这样就实现了不同Fragment之间的通信功能。

2. 生命周期

2.1 状态

运行:当一个Fragment所关联的Activity正处于运行状态时,该Fragment也处于运行状态;
暂停:当一个Activity进入暂停状态时(由于另一个未占满屏幕的Activity被添加到了栈顶),与它相关联的Fragment就会进入暂停状态;
停止当一个Activity进入停止状态时,与它相关联的Fragment就会进入停止状态,或者通过调FragmentTransaction的remove()、replace()方法将Fragment从Activity中移除,但在事务提交之前调用了addToBackStack()方法,这时的Fragment也会进入停止状态。总的来说,进入停止状态的Fragment对用户来说是完全不可见的,有可能会被系统回收。 (注意第二种情况)
销毁:Fragment总是依附于Activity而存在,因此当Activity被销毁时,与它相关联的Fragment就会进入销毁状态。或者通过调用FragmentTransaction的remove()、replace()方法将Fragment从Activity中移除,但在事务提交之前并没有调用addToBackStack()方法,这时的Fragment也会进入销毁状态。

2.2 生命周期

在这里插入图片描述
onAttach(): 当activity与fragment建立关联时调用
onCreateView(): 当为Fragment创建视图(加载布局)调用
onActivityCreated(): 确保与Fragment相关联的Activity已经创建完毕时调用
onDestroyView(): 当与Fragment关联的视图被移除时调用
onDetach():当Activity与Frament解除关联时调用

3. 实现简易版新闻APP

代码链接
核心技术:RecyleView、Fragment、限定符布局
难点:如何根据单页还是双页给RecyleView的子项设置不同的点击事件
在这里插入图片描述
News 新闻数据类:标题、内容;
NewsContentFragment:单双页模式下的新闻内容,核心方法refresh能够根据传入的新闻标题和内容刷新页面;
NewsTitleFragment:使用RecyleView实现新闻标题,单页模式下点击子项跳转到NewsContent,双页模式下,点击子项则刷新NewsContentFragment中的内容;
NewsContent:里面包含了NewsContentFragment(复用了Fragment),通过actionStart让另外一个activity启动,并根据传入的标题和内容,去调用NewsContentFragment.refresh方法

五. 广播 (四大组件)

1. 基本知识

1.1 分类

在这里插入图片描述
在这里插入图片描述

1.2 注册

- 动态注册
写一个类继承BroadCastReceiver类,重写Receiver方法;intentFilter().addAction添加广播类型;通过reisterReceiver注册;当关闭Activity的时候记得取消注册。
该类型的注册必须程序启动才能用,想要程序未启动时接收广播采用静态注册

class MainActivity : AppCompatActivity() {
	lateinit var timeChangeReceiver: TimeChangeReceiver
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		val intentFilter = IntentFilter()
		intentFilter.addAction("android.intent.action.TIME_TICK")
		timeChangeReceiver = TimeChangeReceiver()
		registerReceiver(timeChangeReceiver, intentFilter)
	}
	override fun onDestroy() {
		super.onDestroy()
		unregisterReceiver(timeChangeReceiver)
	}
	inner class TimeChangeReceiver : BroadcastReceiver() {
		override fun onReceive(context: Context, intent: Intent) {
			Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show()
		}
	}
}
  • 静态注册
    在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。
class BootCompleteReceiver : BroadcastReceiver() {
	override fun onReceive(context: Context, intent: Intent) {
		Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show()
	}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.example.broadcasttest">
	<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> # 开启权限
	<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">
		...
		<receiver
		android:name=".BootCompleteReceiver"  # 广播接收者
		android:enabled="true"
		android:exported="true">
		</receiver>
	</application>
</manifest>

1.3 发送自定义广播

  • 写好广播接收者:AndroidManifest.xml中对BroadcastReceiver修改 + BroadcastReceiver
class MyBroadcastReceiver : BroadcastReceiver() {
	override fun onReceive(context: Context, intent: Intent) {
		Toast.makeText(context, "received in MyBroadcastReceiver",
		Toast.LENGTH_SHORT).show()
	}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
...
	<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">
	...
		<receiver
		android:name=".MyBroadcastReceiver"
		android:enabled="true"
		android:exported="true">
			<intent-filter>
			<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
			</intent-filter>
		</receiver>
	</application>
</manifest>
  • 广播发送者
class MainActivity : AppCompatActivity() {
	...
	override fun onCreate(savedInstanceState: Bundle?) {
			super.onCreate(savedInstanceState)
			setContentView(R.layout.activity_main)
			button.setOnClickListener {
			// 核心代码:intent发送广播
				val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
//调用Intent的setPackage()方法,并传入当前应用程序的包名。
//packageName是getPackageName()的语法糖写法,用于获取当前应用程序的包名
				intent.setPackage(packageName)
				sendBroadcast(intent)  // 标准广播
		//		sendOrderedBroadcast(intent, null)  // 有序广播
			}
	...
	}
	...
}

2. 简单Demo:实现强制下线功能

工程代码
项目结构:
在这里插入图片描述
ActivityCollector:管理所有的类
BaseActivty:所有类的父类,重写 onResume 方法处理广播消息
LoginActivity:处理登录逻辑
MainActivity:一个按钮处理下线逻辑
过程中遇到的问题:

  1. EditText的getText()方法 必须在onClick方法里才能用
      login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String account = accountEdit.getText().toString();
                String password = passwordEdit.getText().toString();
                // 如果账号是admin且密码是123456,就认为登录成功
                if (account.equals("admin") && password.equals("123456")) {
                    Intent intent = new Intent(LoginActivity.this, MainActivity.
                            class);
                    startActivity(intent);
                    finish();
                } else {
                    Toast.makeText(LoginActivity.this, "帐号或密码错误,请重新输入", Toast.LENGTH_SHORT).show();
                }
            }
        });
  1. AlterDialog的用法
   class ForceOfflineReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(final Context context, Intent intent) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("下线警告");
            builder.setMessage("你即将离线,请重新登录");
                    builder.setCancelable(false);
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCollector.finishAll(); // 销毁所有活动
                    Intent intent = new Intent(context, LoginActivity.class);
                    context.startActivity(intent); // 重新启动LoginActivity
                }
            });
            builder.show();
        }
    }

六. Service(四大组件)

1. 引入

Service是Android中实现程序后台运行的解决方案,它非常适合执行那些不需要和用户交互而且还要求长期运行的任务。Service的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service仍然能够保持正常运行。
实际上Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在Service的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况

2. 基本用法

  • 创建
    继承Service() -> 重写抽象方法onBind
    onCreate()方法会在Service创建的时候调用,onStartCommand()方法会在每次Service启动的时候调用,onDestroy()方法会在Service销毁的时候调用。
  • 启动和停止
class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		startServiceBtn.setOnClickListener {
			val intent = Intent(this, MyService::class.java)
			startService(intent) // 启动Service
		}
		stopServiceBtn.setOnClickListener {
			val intent = Intent(this, MyService::class.java)
			stopService(intent) // 停止Service
		}
	}
}
  • Activity和Service通信
    比如在MyService里提供一个下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度
class MyService : Service() {
	private val mBinder = DownloadBinder()  //创建一个专门的Binder对象来对下载功能进行管理
	class DownloadBinder : Binder() {
		fun startDownload() {
		Log.d("MyService", "startDownload executed")
		}
		fun getProgress(): Int {
		Log.d("MyService", "getProgress executed")
		return 0
	}
	}
	override fun onBind(intent: Intent): IBinder {
		return mBinder
	}
...
}
class MainActivity : AppCompatActivity() {
	lateinit var downloadBinder: MyService.DownloadBinder
	private val connection = object : ServiceConnection {//实现匿名内部类
		override fun onServiceConnected(name: ComponentName, service: IBinder) {
			downloadBinder = service as MyService.DownloadBinder
			downloadBinder.startDownload()
			downloadBinder.getProgress()
		}
		override fun onServiceDisconnected(name: ComponentName) {
		}
	}
	override fun onCreate(savedInstanceState: Bundle?) {
	...
		bindServiceBtn.setOnClickListener {
			val intent = Intent(this, MyService::class.java)
			bindService(intent, connection, Context.BIND_AUTO_CREATE) // 绑定Service
		}
		unbindServiceBtn.setOnClickListener {
			unbindService(connection) // 解绑Service
		}
	}
}

3. 生命周期

在这里插入图片描述

一旦在项目的任何位置调用了Context的startService()方法,相应的Service就会启动,并回调onStartCommand()方法。如果这个Service之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。
Service启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用,或者被系统回收。
调用Context的bindService()来获取一个Service的持久连接,这时就会回调Service中的onBind()方法。

七. ContentProvider (四大组件)

1. 介绍

ContentProvider主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。ContentProvider可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险

2. 基本用法

如果想要访问ContentProvider中共享的数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver()方法获取该类的实例。
ContentResolver中提供了一系列的方法用于对数据进行增删改查操作,其中**insert()**方法用于添加数据,**update()**方法用于更新数据,**delete()**方法用于删除数据,**query()**方法用于查询数据。

八. 数据持久化技术

1. 文件存储

Context类提供了一些方法
存储数据:openFileOutput() BufferedWriter(OutputStreamWriter(output))
读取数据:openFileInput(“data”) BufferedReader(InputStreamReader(input))

2. SharedPreferences

不同于文件的存储方式,SharedPreferences是使用键值对的方式来存储数据的。也就是说,当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。而且SharedPreferences还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型的;如果存储的数据是一个字符串,那么读取出来的数据仍然是字符串

  • 存储数据
    在这里插入图片描述
saveButton.setOnClickListener {
	val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
	editor.putString("name", "Tom")
	editor.putInt("age", 28)
	editor.putBoolean("married", false)
	editor.apply()
}
  • 读取数据
restoreButton.setOnClickListener {
	val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)
	val name = prefs.getString("name", "")
	val age = prefs.getInt("age", 0)
	val married = prefs.getBoolean("married", false)
	Log.d("MainActivity", "name is $name")
	Log.d("MainActivity", "age is $age")
	Log.d("MainActivity", "married is $married")
}

3. SQLite数据库

SQLiteOpenHelper

九. 网络技术

1. Webview控件

在自己的应用程序里嵌入一个浏览器,从而非常轻松地展示各种各样的网页

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		webView.settings.javaScriptEnabled=true
		webView.webViewClient = WebViewClient()
		webView.loadUrl("https://www.baidu.com")
		}
}

在这里插入图片描述

2.HTTP访问网络

2.1 HttpURLConnection

发送数据

	val url = URL("https://www.baidu.com")
	connection = url.openConnection() as HttpURLConnection
	connection.connectTimeout = 8000
	connection.readTimeout = 8000
	val input = connection.inputStream

提交数据

	connection.requestMethod = "POST"
	val output = DataOutputStream(connection.outputStream)
	output.writeBytes("username=admin&password=123456")

2.2 OkHttp

更常用,但后续会结合Retrofit使用,更简便
get请求

	val client = OkHttpClient()  
	val request = Request.Builder()
	.url("https://www.baidu.com")
	.build()
	val response = client.newCall(request).execute()
	val responseData = response.body?.string()  //解析Response的内容

post请求

	val requestBody = FormBody.Builder()
	.add("username", "admin")
	.add("password", "123456")
	.build()
	val request = Request.Builder()
	.url("https://www.baidu.com")
	.post(requestBody)
	.build()

3. 解析XML格式数据

  • pull
private fun parseXMLWithPull(xmlData: String) {
	try {
		val factory = XmlPullParserFactory.newInstance()
		val xmlPullParser = factory.newPullParser()
		xmlPullParser.setInput(StringReader(xmlData))
		var eventType = xmlPullParser.eventType
		var id = ""
		var name = ""
		var version = ""
		while (eventType != XmlPullParser.END_DOCUMENT) {
			val nodeName = xmlPullParser.name
		when (eventType) {
			// 开始解析某个节点
			XmlPullParser.START_TAG -> {
				when (nodeName) {
				"id" -> id = xmlPullParser.nextText()
				"name" -> name = xmlPullParser.nextText()
				"version" -> version = xmlPullParser.nextText()
				}
			}
		// 完成解析某个节点
		XmlPullParser.END_TAG -> {
				if ("app" == nodeName) {
				Log.d("MainActivity", "id is $id")
				Log.d("MainActivity", "name is $name")
				Log.d("MainActivity", "version is $version")
			}
		}
	}
	eventType = xmlPullParser.next()
	}
} catch (e: Exception) {
	e.printStackTrace()
	}	
}
  • SAX解析方式
private fun parseXMLWithSAX(xmlData: String) {
	try {
		val factory = SAXParserFactory.newInstance()
		val xmlReader = factory.newSAXParser().XMLReader
		val handler = ContentHandler()
		// 将ContentHandler的实例悊置到XMLReader中
		xmlReader.contentHandler = handler
		// 开始执行解析
		xmlReader.parse(InputSource(StringReader(xmlData)))
	} catch (e: Exception) {
		e.printStackTrace()
	}
}

4. 解析JSON格式数据

4.1 使用JSONObject

private fun parseJSONWithJSONObject(jsonData: String) {
	try {
		val jsonArray = JSONArray(jsonData)
		for (i in 0 until jsonArray.length()) {
		val jsonObject = jsonArray.getJSONObject(i)
		val id = jsonObject.getString("id")
		val name = jsonObject.getString("name")
		val version = jsonObject.getString("version")
		Log.d("MainActivity", "id is $id")
		Log.d("MainActivity", "name is $name")
		Log.d("MainActivity", "version is $version")
	}
	} catch (e: Exception) {
		e.printStackTrace()
	}
}

4.2 使用GSON

基本用法
在这里插入图片描述
在这里插入图片描述

5. 手动完成网络接口回调的操作

基于OKHttp

object HttpUtil {
...
	fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) {
		val client = OkHttpClient()
		val request = Request.Builder()
		.url(address)
		.build()
		client.newCall(request).enqueue(callback)
	}
}

可以把callback写成

HttpUtil.sendOkHttpRequest(address, object : Callback {
	override fun onResponse(call: Call, response: Response) {
	// 得到服务器返回的具体内容
	val responseData = response.body?.string()
	}
	override fun onFailure(call: Call, e: IOException) {
	// 在这里对异常情况进行处理
	}
})

6. Retrofit入门

6.1 基本用法

  • 引入依赖
  • 建Entity类
  • 写接口
interface AppService {
	@GET("get_data.json")
	fun getAppData(): Call<List<App>> //注解 + 返回值call
}
  • Retrofit创建
val retrofit = Retrofit.Builder()
	.baseUrl("http://10.0.2.2/")
	.addConverterFactory(GsonConverterFactory.create())
	.build()
val appService = retrofit.create(AppService::class.java)  //创建Service接口的动态代理对象
appService.getAppData().enqueue(object : Callback<List<App>> { //enqueue实现异步
// 需要注意的是,当发起请求的时候,Retrofit会自动在内部开启子线程,当数据回调到Callback中之后,
// Retrofit又会自动切换回主线程,整个操作过程中我们都不用考虑线程切换问题
	override fun onResponse(call: Call<List<App>>,
		response: Response<List<App>>) {
			val list = response.body()
			if (list != null) {
			for (app in list) {
			Log.d("MainActivity", "id is ${app.id}")
			Log.d("MainActivity", "name is ${app.name}")
			Log.d("MainActivity", "version is ${app.version}")
			}
		}
	}
	override fun onFailure(call: Call<List<App>>, t: Throwable) {
		t.printStackTrace()
	}
})
  • 网络安全配置 (HTTP明文)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.example.retrofittest">
	<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"
	android:networkSecurityConfig="@xml/network_config">  //安全配置
	...
	</application>
</manifest>

安全配置文件:允许我们以明文的方式在网络上传输数据,而HTTP使用的就是明文传输方式

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>

6.2 常用注解用于处理复杂接口

注解

6.3 Kotlin中标准写法

object ServiceCreator {
	private const val BASE_URL = "http://10.0.2.2/"
	private val retrofit = Retrofit.Builder()
		.baseUrl(BASE_URL)
		.addConverterFactory(GsonConverterFactory.create())
		.build()
	inline fun <reified T> create(): T = create(T::class.java) //泛型实例化  
}

调用方式:

val appService = ServiceCreator.create<AppService>()

十. Material Design

1. Toolbar

Toolbar 是 Android 中常用的一种应用程序顶部栏控件。它提供了一种通用的方式来显示应用程序的标题、导航按钮、菜单项等内容

2 滑动菜单

  • DrawerLayout:

在应用中创建一个可以从屏幕边缘滑出的小侧边栏,用于导航或其他操作

  • NavigationView:

NavigationView 是一个用于显示导航菜单的控件,通常与 DrawerLayout 一起使用,提供了一个滑动抽屉式的侧边栏菜单

3. 悬浮按钮和可交互提示

  • FloatingActionButton:

FloatingActionButton (FAB) 是一种常见的 UI 控件,通常用于在应用程序中执行主要操作。它通常位于屏幕的右下角,并以圆形形状出现。

Snackbar:

Snackbar 是一种轻量级的消息提示控件,它会在屏幕下方显示一条简短的消息。与 Toast 不同,Snackbar 可以提供用户交互,如撤销操作。

  • CoordinatorLayout:

CoordinatorLayout 是一个特殊的 ViewGroup,可以协调子视图之间的交互和动画。它常与 FloatingActionButton 和 Snackbar 一起使用,以实现更好的用户体验。

4. 卡片式布局

  • MaterialCardView:

本质是一个FrameLayout,只是额外提供了圆角和阴影等效果,看上去会有立体的感觉

  • AppBarLayout:

实际上是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装,并应用了一些
Material Design的设计理念。解决一些遮挡问题

5. 下拉刷新

  • SwipeRefreshLayout是一个Android支持库中的控件,它提供了一种简单的方式来实现下拉刷新功能。当用户下拉SwipeRefreshLayout时,会触发一个刷新事件,可以在该事件中执行数据刷新的操作。
  • 要在Android应用中使用SwipeRefreshLayout,需要在XML布局文件中添加SwipeRefreshLayout控件,并将需要添加下拉刷新功能的内容放置在SwipeRefreshLayout内部。通常,SwipeRefreshLayout包裹一个RecyclerView或者ListView等可以滚动的控件。
  • 在Java代码中,需要设置一个OnRefreshListener监听器来监听下拉刷新事件,并在回调方法中编写数据刷新的逻辑。当用户下拉时,OnRefreshListener中的onRefresh方法会被调用,你可以在这个方法中执行数据加载或刷新的操作。

6. 可折叠式标题栏

  • CollapsingToolbarLayout
    是 Android Support Design Library中的一个布局控件,用于实现可折叠的工具栏效果。它通常与 AppBarLayout 和 Toolbar一起使用,以创建具有折叠和展开效果的标题栏或工具栏。这个效果常见于大型应用的界面,如新闻应用或社交媒体应用中,当用户向下滚动页面时,工具栏会向上折叠,显示更多内容;当用户向上滚动页面时,工具栏会展开。

十一. Jetpack

1. ViewModel

  • 生命周期
    在这里插入图片描述
    当 Activity 处于前台的时候被销毁了,那么得到的 ViewModel 是之前实例过的 ViewModel;如果 Activity 处于后台时被销毁了,那么得到的 ViewModel 不是同一个。举例说,如果 Activity 因为配置发生变化而被重建了,那么当重建的时候,ViewModel 是之前的实例;如果因为长期处于后台而被销毁了,那么重建的时候,ViewModel 就不是之前的实例了。
    浅谈 ViewModel 的生命周期控制
    AndroidX设计架构MVVM之ViewModel生命周期分析
  • 使用及原理
    ViewModelProvider:  提供获取 ViewModel 实例的类,ViewModel 都是从这个类中获取。
    ViewModelStoreOwner:核心接口,该接口的实现类表示能够为外部提供 ViewModelStore 实例。
    ViewModelStore:   核心类,存储 ViewModel 实例的类,实际是一个HashMap。
    Factory:        核心接口,负责创建 ViewModel 实例。
// 获取viewModel类
val viewModel = ViewModelProvider(activity).get(HiViewModel::class.java)

由浅入深,详解ViewModel那些事

2. LifeCycles

  • 使用
class MyObserver : LifecycleObserver {
	@OnLifecycleEvent(Lifecycle.Event.ON_START)
	fun activityStart() {
		Log.d("MyObserver", "activityStart")
	}
	@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
	fun activityStop() {
		Log.d("MyObserver", "activityStop")
	}
}
lifecycleOwner.lifecycle.addObserver(MyObserver())
  • 五种生命状态
    在这里插入图片描述

3. LiveData

- 使用

// VM中
val counter = MutableLiveData<Int>()
// Activity中
viewModel.counter.observe(this, Observer { count ->
	infoText.text = count.toString()
}

标准写法:在非ViewModel中就只能观察LiveData的数据变化,而不能给LiveData设置数据

class MainViewModel(countReserved: Int) : ViewModel() {
	val counter: LiveData<Int>
	get() = _counter
	private val _counter = MutableLiveData<Int>()
	init {
		_counter.value = countReserved
	}
	fun plusOne() {
	val count = _counter.value ?: 0
		_counter.value = count + 1
	}
	fun clear() {
		_counter.value = 0
	}
}

在这里插入图片描述

public class MainViewModel extends ViewModel {
    private final MutableLiveData<Integer> _counter = new MutableLiveData<>();
    private final LiveData<Integer> counter = _counter;

    public MainViewModel(int countReserved) {
        _counter.setValue(countReserved);
    }

    public LiveData<Integer> getCounter() {
        return counter;
    }

    public void plusOne() {
        Integer count = _counter.getValue();
        if (count != null) {
            _counter.setValue(count + 1);
        } else {
            _counter.setValue(1);
        }
    }

    public void clear() {
        _counter.setValue(0);
    }
}
  • map和switchmap
    实现数据转换
  • MediatorLiveData

MediatorLiveData是 LiveData的子类,允许您合并多个 LiveData 源。只要任何原始的 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者。

4. Room

属于ORM框架。ORM(Object Relational Mapping)也叫对象关系映射

5. Workmanager

WorkManager很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据,等等

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值