Android 基础

文章目录

  • kotlin 基础知识点
  • solid 标签
  • R 文件格式
  • 变量声明: var val
  • as、as?
  • 程序的逻辑控制:
  • 继承与构造函数
  • lambda 编程
    • 集合的创建与遍历
    • 集合的函数式 API
    • Java 函数式 API 的应用
  • 空指针问题
    • 判空辅助工具 ?. 和 ?: 和 let
  • 字符串内嵌表达式(字符串拼接)
  • 函数的参数默认值
  • 创建和加载布局
  • 在Activity 中使用 Toast
  • 在 Activity 中使用 Menu
  • 销毁一个 Activity
  • Intent 的使用
    • 显示 Intent
    • 隐式 Intent
  • 隐式 Intent 的其他用法
  • 向下一个 Activity 传递数据
  • 返回数据给上一个 Activity
  • Activity 的生命周期
    • Activity 的状态
    • Activity 的生存期
    • 体验 Activity 的生命周期
    • Activity 被回收,数据保存问题
    • Activity 的启动模式
    • 知晓当前是在哪个 Activity
    • 随时随地退出程序
    • 启动 Activity 的最佳写法
    • 标准函数和静态方法
      • with
      • run
      • apply
    • 定义静态方法
  • 常用控件的使用方法
    • TextView
    • Button
    • EditText
    • ImageView
    • ProcessBar
    • AlertDialog
  • 三种布局
  • 创建自定义控件
    • 引入布局
    • 创建自定义控件
  • 最常用和最难用的控件:ListView
    • ListView 的简单用法
    • 定制 ListView 的界面
    • 提升 ListView 的运行效率
    • ListView 的点击事件
  • 滚动控件:RecyclerView
    • RecyclerView 的基本用法
    • 实现横向滚动和瀑布流布局
    • RecyclerView 的点击事件
  • 延长初始化和密封类
    • 对变量延迟初始化
    • 使用密封类优化代码
  • Fragment
    • Fragment 的简单用法
    • 动态添加 Fragment
    • 在 Fragment 中实现返回栈
    • Fragment 和 Activity 之间的交互
    • Fragment 的生命周期
    • 动态加载布局时的技巧
    • 使用限定符
    • 最小宽度限定符
  • 扩展函数和运算符重载
    • 扩展函数
    • 运算符重载
  • 高阶函数详解
  • 内联函数作用
    • noinline 和 crossinline
  • 详解持久化技术
  • 文件存储
    • 将数据存储到文件中
    • 从文件中读取数据
  • SharedPreferences 存储
    • 将数据存储到 SharedPreference
    • 从 SharedPreferences 文件读取数据
  • SQLite 数据库存储
    • 创建数据库
    • 升级数据库
    • 使用事务
  • 高阶函数的应用
    • 简化 SharedPreferences 的用法
  • 泛型和委托
    • 泛型
    • 委托
      • 类委托
      • 委托属性
  • 实现一个自己的 lazy 函数
  • 使用 infix 函数构建更可读的写法
  • 泛型的高级特性
    • 泛型的实化
    • 泛型的协变
    • 泛型的逆变
  • 使用协程编写高效的并发程序
    • 协程的用法
    • 更多作用域构建器
  • 编写好用的工具方法
    • 求 N 个数的最大最小值
    • 简化 Toast 用法
  • Java I/O 详解
  • WebView 的用法
    • 使用 HTTP 访问网络
      • 使用 HttpURLConnection
      • 使用 OkHttp
  • 解析 XML 格式数据
  • Material Design 实战
    • ToolBar
    • 滑动菜单
    • NavigationView
    • 悬浮按钮和可交互提示
      • FloatingActionButton
      • Snackbar
  • 卡片式布局
    • MaterialCardView
    • 下拉刷新
    • 可折叠式标题栏
  • AppBarLayout
  • CoordinatorLayout
  • AppBarLayout
  • CollapsingToolbarLayout
  • 探究 JetPack
    • ViewModel 的基本用法
    • Lifecycles
    • LiveData
      • LiveData 的基本用法
  • ViewPager 全面剖析及使用详解
    • PagerAdapter的使用
    • ViewPager的翻页动画
  • Merge
  • SparseArray 的使用及实现原理

kotlin 基础知识点

  • Kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类

  • 如果子类有主构造函数, 则基类必须在主构造函数中立即初始化

  • 子类继承父类时,不能有跟父类同名的变量,除非父类中该变量为 private,或者父类中该变量为 open 并且子类用 override 关键字重写

inflate 方法的原理:https://blog.csdn.net/guolin_blog/article/details/12921889

Android的Activity 、 Window 、 View之间的关系:https://www.jianshu.com/p/982ec37832bf

MVC 模式:对应到 Android 开发中,View 约等于 Layout 中的 xml ,Controller 就是 Activity,Model 同上。

solid 标签

有时,我们为了APP中节省空间,在能用颜色替代的地方就不要用图片,而如何将颜色组织成想要的形状及如何为指定的颜色添加描边、渐变等来模拟图片就显的极为重要了,这些就是靠shape来完成的。

https://blog.csdn.net/harvic880925/article/details/41850723

R 文件格式

一个module被编译时,会生成一个当前module的R文件
HelloWorld工程中的R.java文件:

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class layout {
        public static final int main=0x7f030000;
    }
    public static final class string {
        public static final int app_name=0x7f040001;
        public static final int hello=0x7f040000;
    }
}

变量声明: var val

val:声明不可变变量,相当于 Java 中的 final
var:声明可变变量

声明时显示指定类型:val a:Int =10

as、as?

as运算符用于执行引用类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功进行;如果类型不兼容,使用as?运算符就会返回值null。在Kotlin中,父类是禁止转换为子类型的。

程序的逻辑控制:

if 条件语句:
和 Java 相比有返回值:返回值是每一个条件语句中最后一行代码的返回值
例如:

fun largerNumber(num1:Int,num2:Int):Int{
    val num=if(num1>num2) num1
    else num2
    return num
}
fun main(){
    println(largerNumber(2,8));
}

返回值是 8

对 else 语句稍做修改:

fun largerNumber(num1:Int,num2:Int):Int{
    val num=if(num1>num2) num1
    else
    {
        num2
        10
    }
    return num
}

返回值是 10,即 else 语句的最后一行代码

以上代码可以用 kotlin 语法糖进行简化:

fun largerNumber(num1:Int,num2:Int)=if(num1>num2) num1 else num2

继承与构造函数

和 Java 不同的是在 kotlin 中,任何一个非抽象类都是不可继承的。抽象类和 Java 无区别

在类前面加上关键字 open 就可以让一个类可以被继承,如:

open class Person {
}
class Student(name:String,age:Int):Person(name,age) {
    
}

注意:以上 Student 类的主构造函数中的两个变量不能声明为 val 或者 var 。因为声明为 var 或者 val 的话,参数就会自动成为该类的字段,这就会导致和父类中同名的 name 和 age 字段造成冲突。不使用任何关键字修饰的话,他的作用范围就仅仅在主构造函数之中(XXXXX)

括号的意义: 子类中的构造函数必须调用父类中的构造函数,但是主构造函数没有函数体,只能使用 init 结构体去调用父类的构造函数,但大多时候,我们并不使用 init ,于是直接在继承的时候通过括号指定调用父类中的哪个构造函数。

主构造函数的意义?(XXXXX)

Tip:
1、当一个类没有显示声明主构造函数且定义了次构造函数时,它就是没有主构造函数的,次构造函数直接调用父类的构造函数即可
2、为什么要分为主构造函数和次构造函数?(XXXXX)

Java 可见性修饰符:
Android 基础_第1张图片
kotlin 可见性 修饰符:

在类前面声明 data 关键字,即表明这是一个数据类,他就会自动生成如 equals()、hashCode()、toString() 等固定且无实际逻辑意义的方法,如下:

data class Cellphone(var brand:String,var price:Double)

把一个类的 class 关键字改成 object ,即表明这是一个单例类,如下:

object Singleton {
}

lambda 编程

集合的创建与遍历

在 kotlin 中,集合主要有 List、Set、Map
List 的创建方法和 Set 差不多,有两种方式,可变的和不可变的创建。不可变是指集合一旦创建就不能再往里面添加、修改、删除元素

LIst :
不可变的写法:

fun main(){
    //println(largerNumber(2,8));
    var list=listOf("a","b",10)
    for(i in list) println(i)
}

可变的写法:

fun main(){
    //println(largerNumber(2,8));
    var list=mutableListOf("a","b","10")
    list.add("c")
    for(i in list) println(i)
}

set 和 list 差不多写法,对应:setOf() 和 mutableOf()

map 和 list、set 有较大的不同,和 Java 的区别就是可以写成如下形式:

fun main(){
    var map=HashMap<String,Int>()
    map["a"]=1
    map["b"]=2
    for((s,i) in map) println(s+" "+i)
}

声明方式也可以如下所示:

fun main(){
    var map= mapOf<String,Int>("a" to 1,"b" to 2)
    for((s,i) in map) println(s+" "+i)
}

集合的函数式 API

lambda 表达式的语法结构如下:

参数名1:参数类型,参数名2:参数类型 -> 函数体

以上的语法可以简化,lambda 表达式是方法的最后一个参数的时候,可以把大括号提出去,如果是唯一一个参数,还可以省略方法的括号。lambda 表达式的参数可以省略类型,只有一个参数的时候,还可以用 it 代替

常用的集合 API:

list.maxBy() // 取 list 中 length 最长的数据项
list.map() // 将 list 集合中的数据项映射成另一种形式
list.filter() // 按 filter 传入的条件进行过滤 

Java 函数式 API 的应用

Kotlin 在调用 Java 方法的时候,也可以使用 lambda 表达式,不过只有 Java 的函数式接口能使用 lambda 表达式。例如 Java 中的 Runnable 接口

Java 中常规写法:

        new Thread(new Runnable(){
            @Override
            public void run(){
                System.out.println("Running");
            }
        }).start();

在 kotlin 中的常规写法:

    Thread(object : Runnable{
        override fun run(){
            println("Running");
        }
    }).start();

kotlin 简化写法:
在调用Java中的函数式接口的时候,将Java接口名作为 lambda 表达式的方法名,如下:

    Thread(Runnable{
            System.out.println("Running");
    }).start();

因为 Thread 可以只有 Runnable 这一个参数,所以 Runnable 也可以省略,如下:

    Thread({
            System.out.println("Running");
    }).start();

且因为 Runnable 是 Thread 方法的最后一个参数,所以优化如下,去掉 Thread 方法的括号
    Thread{
            System.out.println("Running");
    }.start();

空指针问题

在 Java 中,空指针异常会在运行时作为异常抛出,但是在 Kotlin 中,对空指针异常的检测提前到了编译器,也就是在编译期间,如果你某个属性可能会 null 值,编译就不会通过

如果某个类型可以为空,那么你可以用 ? 来告诉编译器,这是一个可以为 null 的属性。例如:a: String?

判空辅助工具 ?. 和 ?: 和 let

.? 相当于如下代码,如果 a 对象为空,则什么都不做,否则,doSometing

if(a!=null) a.doSomething()
等价于
a?.doSomething()

?: 相当于如下代码,如果左边表达式不为空的,就返回左边表达式的值,否则返回右边表达式的值

var c=if(a!=null) a else b
等价于
var c=a?:b

let 是一个函数,语法如下:

obj.let{
obj2-> 具体代码
}

注意,obj2 和 obj 就是同一个对象

使用 .? 的方式相当于对每个语句进行 if 判断,这样降低代码效率。 let 的出现就是为了栏多个语句进行一次判空操作即可

obj.?obj.doSome1()
obj.?obj.doSome2()

等价于

if(obj!=null) obj.doSome1()
if(obj!=null) obj.doSome2()
obj?.let{
	it.dosome1()
	it.dosome2()
}

等价于

if(obj!=null) 
{
	obj.doSome1()
	obj.doSome2()
}

字符串内嵌表达式(字符串拼接)

使用 ${} 表达式,在运行的时候,${} 的执行结果会替换掉这个表达式,如:

"hello ${obj.name}"
如果传入参数,obj.name 为 张三
那么,上述表达式等价于
"hello 张三"

但表达式中只有一个变量的时候,{} 可省略,即 $obj.name 即可。

函数的参数默认值

次构造函数的主要功能是为函数的参数设置默认值,但是并不常用。函数的参数默认值可以使用键值对的方式直接指定。

赋值的时候,如果指定了默认值得那个参数可以不用传值,跳过。如果指定默认值的哪个参数不在最后一位,用键值对的方式赋值

创建和加载布局

在 xml 中,@id/button1 是引用一个 id,@id/button1则是定义一个 id

    <Button
        android:id="@+id/button1" 唯一标识符
        android:layout_width="match_parent" 当前元素和父元素一样宽
        android:layout_height="wrap_content"  刚好把文字包裹住
        android:text="button1" 指定元素中显示的中文内容
        />

演示效果如下:
在这里插入图片描述

在 Activity 中加载这个布局:
在 FirstActivity 中

class FirstActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //添加这句话,意思是,给当前 Activity 添加一个布局
        setContentView(R.layout.first_layout) //任何资源都会在 R 文件中生成一个 id
    }
}

接下来,所有的 Activity 都应该在 AndroidMainfest 中进行注册,但是 AS(以下 Android Studio 简称 AS)帮我们写好了,不用管,如下蓝色的语句就是进行注册,.FirstActivity 中 . 的前面是包路径,只是缩写了:
Android 基础_第2张图片

接下来,把 FirstActivity 设置成主 Activity,也即程序跑起来默认的那个 Activity,如下:

Android 基础_第3张图片

演示效果如下:

Android 基础_第4张图片

在Activity 中使用 Toast

Toast 就是在页面中弹出提示信息,并且不占用任何屏幕空间。

1、首先要有 Toast 的出发点,上面例子中的 button1 就可以
2、在上面例子的基础上修改代码如下

class FirstActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //添加这句话,意思是,给当前 Activity 添加一个布局
        setContentView(R.layout.first_layout) //任何资源都会在 R 文件中生成一个 id
        //findViewById :获取布局文件中定义的元素,需要显示的声明 button1 的类型为 Button,因为推断不出来
        var button1: Button =findViewById(R.id.button1)
        button1.setOnClickListener{
            //三个参数分别是:Toast要求的上下文;Toast 显示的文本内容;Toast 显示的时长
            // 第三个参数一般有两个取值:Toast.LENGTH_SHORT 和 Toast.LENGTH_LONG
            Toast.makeText(this,"点击了 button1",Toast.LENGTH_SHORT).show()
        }

    }
}

演示效果如下:
弹出了提示:点击了 button1

Android 基础_第5张图片

关于 findViewById() 这个方法,Kotlin 对他进行了一些操作,让他变得简洁,不用每次都写一次这句话来找到某个组件。kotlin 在 gradle 文件的头部默认引入了一个插件,这个插件会根据布局文件中定义的控件 id 自动生成一个具有相同名称的变量,因此我们可以直接在 Activity 中直接使用这个变量,而不用每次都去调用 findViewById() 方法

改进后代码如下:
Android 基础_第6张图片
注意:在使用 kotlin-android-extensions 插件的时候,需要在 builld.grade(模块下那个)添加以下语句:

Android 基础_第7张图片

最后一句话的意思是,把某些关闭了的功能都打开

在 Activity 中使用 Menu

1、新建 menu 文件夹
2、在 menu 文件夹下新建Menu resource file
Android 基础_第8张图片

3、在 main.xml 中添加代码
直接 control+O 选择 onCreateOptionsMenu 方法,就可以对他进行重写
Android 基础_第9张图片

4、定义菜单响应事件
Android 基础_第10张图片

Android 基础_第11张图片

销毁一个 Activity

直接调用 finish() 方法即可

Intent 的使用

Intent 的作用是从一个 Activity 跳转到另一个 Activity

显示 Intent

1、新建一个空的 Actvity 文件,并修改自动生成的 xml 文件为如下内容
Android 基础_第12张图片
2、在 FirstActivity 中代码如下:
Android 基础_第13张图片
演示结果:
Android 基础_第14张图片
点击BUTTON1之后,自动跳转到如下页面:
Android 基础_第15张图片

隐式 Intent

Android 基础_第16张图片
Android 基础_第17张图片
演示效果和上面一样

隐式 Intent 的其他用法

Android 基础_第18张图片
可以打开百度的主页面

使用 :intent.data=Uri.parse("tel:10086") 可以调用系统的拨号界面

向下一个 Activity 传递数据

使用 intent.putExtra("键","值") 方法向下传递,另一个页面使用 intent.getStringExtra("键")进行获取对应的 value

返回数据给上一个 Activity

在 FirstAvtivity 中:

class FirstActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.first_layout) //任何资源都会在 R 文件中生成一个 id
        button1.setOnClickListener{
            val intent=Intent(this,SecondActivity::class.java)
            //期望在 Activity 销毁的实施能够返回一个结果给上一个 Activity
            // 参数:1、传递数据的 intent 2、请求吗,用于回调的时候判断数据的来源
            startActivityForResult(intent,1)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        //1、requestCode 就是上面那个 1  ;2、第二个页面传回来的处理结果,状态码 3、携带数据返回的 Intent
        // 第二个参数一般只有两个取值:  RESULT_OK 或者 RESULT_CANCELED
        super.onActivityResult(requestCode, resultCode, data)
        when(requestCode){
            1->if(requestCode== RESULT_OK) {
                val returnedData = data?.getStringExtra("data_return")
                Log.d("FirstActivity","返回的数据是 $returnedData")
            }
        }
    }

}

在 SecondActivity 中


class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
         button2.setOnClickListener{
             val intent = Intent()
             intent.putExtra("data_return","向上传回给FirstActivity")
             //这个方法专门用于向上一个 Activity 返回数据
             //参数:1、返回结果 2、把带有数据的 intent 返回回去
             setResult(Activity.RESULT_OK,intent)
             finish()
         }

    }

}

那个 1 就相当于标识 firstActivity ,因为一个 Activity 可能回调多个 ,1 就是用来确定,你确实是回调给我的

演示效果:点击 button1 跳转到 SecondActivity ,再点击 button2 就跳转回来,并在控制台打印那句话Lod.d()里面那句

Activity 的生命周期

Android 是使用任务(task)来管理 Activity 的,一个任务就是一组存放在栈里的 Activity 集合。每当我们点击 Back 键或者调用 finish() 方法的时候,栈顶的 Activity 就会出栈,前一个入栈的 Activity 就会出现在栈顶的位置。系统总是显示处于栈顶的 Activity 给用户

Activity 的状态

每个 Activity 的生命周期中最多会有 4 种状态:
1、运行状态:位于返回栈栈顶的 Activity 就会处于运行状态
2、暂停状态:Activity 不在栈顶的位置,但是仍然处于用户可见的状态。比如界面弹出了一个对话框,但是对话框下的哪个页面我们一般还是看得见的,对话框下面哪个页面就是暂停状态
3、停止状态:Activity 不在栈顶的位置,且用户不可见的状态就是停止状态。处于停止状态的 Activity 可能会被系统回收
4、销毁状态:当 Activity 从返回栈中被移除后就变成了销毁状态,系统最倾向于回收处于这种状态的 Activity

Activity 的生存期

Activity 类中定义了 7 个回调方法,覆盖了 Activity 生命周期的每一个环节

  • onCreate():每个 Activity 中都会重写这个方法,他在Activity 第一次被创建的时候调用,一般用于做一些初始化操作,比如:加载布局、绑定事件
  • onStart():Activity 在不可见到可见状态的时候调用
  • onPause():这个方法在系统准备去启动和恢复另外一个 Activity 的时候调用,通常在这个方法中保存一些关键数据和释放一些资源
  • onStop():在 Activity 完全不可见的时候调用,和 onPause 的区别在于,如果新启动的 Activity 是一个对话框式的 Activity ,那么 onPause() 方法会执行,onStop 方法不会执行
  • onDestory():在 Activity 被销毁之前调用
  • onRestart():在 Activity 由停止状态变为运行状态的时候调用

体验 Activity 的生命周期

1、创建三个 Activity
Android 基础_第19张图片

内容对应如下:
NormalActivity 和 DialogActivity 不做修改,创建好就行
MainActivity 的内容如下:

class MainActivity : AppCompatActivity() {
    private val tag="MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Log.d(tag,"onCreate")
        setContentView(R.layout.activity_main)
        startNormal.setOnClickListener{
            val intent= Intent(this,NormalActivity::class.java)
            startActivity(intent)
        }

        startDialog.setOnClickListener{
            val intent= Intent(this,DialogActivity::class.java)
            startActivity(intent)
        }
    }

    override fun onStart() {
        super.onStart()
        Log.d(tag,"onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(tag,"onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(tag,"onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(tag,"onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(tag,"onDestory")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(tag,"onDestory")
    }
}

对应的 xml 文件:
activity_main.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/startNormal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start normal"
    />

    <Button
        android:id="@+id/startDialog"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start dialog"
        />
LinearLayout>

演示效果:

当程序第一次启动的时候:
Android 基础_第20张图片

点击按钮 start normal:
他会先出现 onPause ,过一会可能会出现 onStop
Android 基础_第21张图片

点击Back ,返回上一个页面:当前页面会被销毁(NormalActivity 会出栈,MainActivity 会出现在栈顶,所以 onStart,并且运行onResume)
在这里插入图片描述

当你第一次点击 start dialog 的时候:
只有 onPause ,没有 onStop ,也就是说 MainActivity 还是可见的,而且没有 onStart

Android 基础_第22张图片

Activity 被回收,数据保存问题

当一个 Activity 进入了停止状态,有可能会被系统回收,但是有的时候,用户点击回退,是希望这个页面数据还在的,由于Activity被回收了,数据就不在了,需要 onCreate ,用户重新填写,影响体验

在 Activity 中有一个回调方法 onSaveInstanceState() ,该方法携带 Bundle 类型的参数,Bundle 提供了一系列方法用于保存数据。比如可以使用 putString 保存字符串,使用 putInt 保存整型数据等等。每个保存方法需要传入两个参数,第一个是键,第二个是值,用于后面从 Bundle 中取值

在 MainActivity 中添加如下代码就可以将数据保存下来了:

    override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
        super.onSaveInstanceState(outState, outPersistentState)
        val tempData="需要被保存下来的数据"
        outState.putString("data_key",tempData)
    }

数据恢复:
注意到每个 onCreate 方法都有一个 Bundle 类型的参数,这个参数一般情况下为 null ,但是如果在 Activity 回收之前,你通过 onSaveInstanceState 方法保存了数据,这个参数就会带有之前保存的全部数据,通过这个参数就可以进行数据恢复了

修改 MainActivity 中的 onCreate 方法,就实现了数据恢复了:
Android 基础_第23张图片

Activity 的启动模式

Activity 的启动模式有:

  • standard:不管返回栈里面有没有这个 Activity ,都会在返回栈里面新建一个该 Activity 的实例
  • singleTop:在启动 Activity 的时候,如果发现栈顶已经是该 Activity ,则认为可以直接使用它,不会再创建新的 Activity 实例
  • singTask:保证在整个应用程序的上下文中值存在一个实例。原理就是,每次在新建一个 Activity 的时候,系统会去检查返回栈中时候已经存在该 Activity 的实例,如果已经存在就会把在该 Activity 之上的所有 Activity 出栈,使得该 Activity 出现在栈顶
  • singleInstance:每个应用程序都有自己的返回栈,所以一个 Activity 不能同时被多个程序共享(因为同一个 Activity 在不同的返回栈必然创建了新的实例对象)。在这种模式下,会有一个单独的返回栈来管理这个 Activity,不管是哪个程序来访问这个 Activity,都共用同一个返回栈,也就解决了共享 Activity 实例的问题

案例演示:
在 AndroidManifest.xml 中设置 SecondActivity 的启动模式如下:

Android 基础_第24张图片
你先点击 FirstActivity -> SecondActivity -> ThirdActivity ,再从 ThirdActivity 点击回退,你会发现是回退到 FirstActivity,而不是 SecondActivity,这就是因为 SecondActivity 设置成了 singleInstance 模式,单独存在于一个返回栈中

知晓当前是在哪个 Activity

相当于 Java 中的 obj.getClass().getName() ,知晓当前实例的类名

BaseActivity::class.java 表示获取 BaseActivity类的 Class 对象
javaClass.simpleName 表示获取当前实例对象的类名,simpleName 相当于 Java 的 getName()

随时随地退出程序

如何一次性退出所有 Activity,创建一个单例类,把所有的 Activity 同一管理即可,再在每个 Activity 的 onCreate() 初始化的时候,把自己加到你统一管理的哪个集合中就好了

杀掉进程的代码

 android.os.Process.killProcess(android.os.Process.myPid())
 myPid() 用来获取当前线程的 id

启动 Activity 的最佳写法

新的语法结构 companion object ,使用这个结构,就可以很清楚的知道当前 Activity 在启动的时候必须传入的参数

案例演示:

SecondActivity 需要的数据都是通过 actionStart() 方法的参数传过来的,在 SecondActivity 中添加如下代码:
Android 基础_第25张图片
再在 firstActivity 中添加如下代码,就可以启动 SecondActivity 了,而且就算 SecondActivity 是别人写的,你也可以很容易知道 SecondActivity 所需要的参数了,看 companion object 结构中的 actionStart 方法的参数即可:
Android 基础_第26张图片

标准函数和静态方法

Kotlin 标准函数指的是 Standard.kt 文件中定义的函数,任何 Kotlin 的代码都可以自由地调动所有标准函数
标准函数:

with

两个参数,第一个参数可以是任意对象,第二个参数是一个 lambda 表达式,因为是方法里面的最后一个参数,所以可以将大括号提出来,lambda 表达式的作用是提供第一个参数对象的上下文

语法结构:

val result = with(obj){
	//这里是 obj 的上下文
	"value" // with 函数的返回值
}

案例演示:
在 Java 里面,你定义了 StringBuilder s=new StringBuilder() 之后,你每次想要调用 append 方法的时候,都得加上对象名,如 s.append(i),但是用了 with 之后,就相当于把 s 这个对象名给提取出来了,然后直接用 append() 就好了

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val list=listOf("A","B","C")
        //相当于 new StringBuilder() :因为 kotlin 里面完全没有 new 这个关键字
        val result = with(StringBuilder()){
            append("start show")
            for(i in list){
                append(i).append("\n")
            }
        }
        toString()
    }
}

run

run 方法和 with 方法类似,只不过只有一个 lambda 参数,使用的时候是 obj.函数名,而不是把 obj 作为参数放在函数里面

run 方法的语法结构:

val result=obj.run{
	//这里是 obj 的上下文
	"value" // run 函数的返回值
}

案例演示:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val list=listOf("A","B","C")
        
        val result = StringBuilder().run{ //和 with 方法主要是这句话的差异
            append("start show")
            for(i in list){
                append(i).append("\n")
            }
            toString()
        }
    }
}

apply

和 run 方法类似,区别就是无法指定返回值,而是会自动返回调用对象本身

语法结构:

val result=obj.apply{
	//这里是 obj 的上下文
	
}
//result==obj 也就是说,返回值是调用对象本身,看下面例子

案例演示:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val list=listOf("A","B","C")
        val result = StringBuilder().apply{
            append("start show")
            for(i in list){
                append(i).append("\n")
            }
            //不能返回 toString() 了,因为是 StringBuilder 调用的,所以返回值是那个 StringBuilder对象
        }
        //只能这样获取到一个 String 对象,前面几个方法都是直接返回的
        println(result.toString()) 
    }

定义静态方法

kotlin 极度弱化了静态方法这个概念,想要在 kotlin 里面定义一个静态方法不像在 Java 里一样很容易。因为在 Kotlin 里面有比 静态方法更好的语法特性 – 单例类

单例类,可以直接类名.方法名调用,且存在唯一性,则就和 Java 里面静态方法的性质是一样的,所以可以替代

不过,单例类中的方法都有静态方法的性质,如果你只是想让某一个方法具有静态方法的性质,就可以使用前面提到过的 companion object 结构,示例如下:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Util.doSome2()
    }
}

class Util{
    fun doSome1(){
        println("doSome1")
    }

    //在这个结构里面的方法可以通过类名.方法名直接调用,而且也只会有一个,相当于 Java 中的静态方法
    companion object{
        fun doSome2(){
            println("do some2")
        }
    }
}

Android 基础_第27张图片

当然 kotlin 里面也有真正的静态方法,有两种实现方式:注解和顶层方法 , Kotlin 会将这两种方法编译成真正的静态方法

1、使用 @JvmStatic 注解

Android 基础_第28张图片

2、顶层方法
顶层方法就是不被类包裹着的方法,比如:
doSome2() 方法是写在 MainActivity 类的外面的
Android 基础_第29张图片

常用控件的使用方法

TextView

Android 基础_第30张图片

Android 基础_第31张图片

android:gravity="center"

可以让文字居中对齐,效果图如下:
Android 基础_第32张图片
另外,还可以对TextView 里面的文字进行修改,添加类似于以下代码即可

        android:textColor="#00ff00"
        android:textSize="24sp"

演示效果如下:
Android 基础_第33张图片

Button

Button 上的文字默认是大写,想要保留原来的样子,使用 android:textAllCaps="false"

除了使用函数式 API 的方式来注册监听器,也可以使用实现接口的方式进行注册,如下所示:

class FirstActivity : AppCompatActivity(), View.OnClickListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.first_layout) //任何资源都会在 R 文件中生成一个 id

//        button1.setOnClickListener{
//           SecondActivity.actionStart(this,"data1","data2")
//        }
        button1.setOnClickListener(this)

    }

    //接口中定义的方法,在这个方法里面写业务逻辑
    override fun onClick(v: View?) {

        when(v?.id){

            R.id.button1 -> {
                SecondActivity.actionStart(this,"data1","data2")
                Log.d("first:","kkkkk")
            }
        }
    }

}

EditText

相当于前端的输入框
相当于输入框中的提示文字

android:hint="提示文字"
android:maxLines="2" 指定行数为 2 行,当超过两行的时候,会自动加滚动条

ImageView

用于在界面上展示图片,图片一般放在 drawable 文件夹下面

android:src="@drawable/图片名"

ProcessBar

所有的 Android 控件都有一个可见属性,可选值有 3 种, visiable、invisible、gone
用于在界面显示一个进度条

利用 progressBar.visibility==View.visiable 等等,类似的写法去判断控件的可见状态

style 属性可以给进度条指定样式

AlertDialog

可以在界面弹出一个对话框,该对话框置顶于所有界面元素之上,能够屏蔽其他控件的交互能力

AlertDialog.Builder(this).apply{
	...
}

三种布局

LinearLayout:线性布局,包括垂直和水平
RelativeLayout:相对布局
FrameLayout : 帧布局,可以用 gravity 指定控件在布局中的对齐方式

创建自定义控件

Android 常用控件和布局继承结构:
Android 基础_第34张图片
所有控件直接或间接继承自View,所有的布局直接或间接继承自 ViewGroup ,View 是 Android 中最基本的一种 UI 组件,他可以在屏幕上绘制一块矩形区域,并能响应这块区的各种事件

引入布局

很多的 Android 程序顶部都会有一个标题栏,这样每个 Activity 都要去实现这样一个标题栏,就很没有必要。可以把标题栏的代码单独放在一个布局里面,其他布局直接引用这个布局,就可以实现代码复用了

1、在 layout 目录下新建一个 title.xml 布局,代码如下:
定义两个 Button 用于 Back 和 Edit
定义了 一个 TextView 显示标题文本

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#0277BD"
    >

    <Button
        android:id="@+id/titleBack"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="5dp"
        android:text="BACK"
        android:textColor="#fff"
        />

    <TextView
        android:id="@+id/titleText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:layout_gravity="center"
        android:text="Title Text"
        android:textColor="#2196F3"
        android:textSize="24sp"
        />


    <Button
        android:id="@+id/titleEdit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginRight="5dp"
        android:text="EDIT"
        android:textColor="#fff"
        />

</LinearLayout>

2、如现在在 FirstActivity 布局中引用这个标题栏,代码如下
Android 基础_第35张图片
3、隐藏 FirstActivity 自带的标题栏
Android 基础_第36张图片
android:layout_margin=“5dp”:指定控件在上下左右方的间距。还可以 marginLeft 和 marginRight
演示效果:
Android 基础_第37张图片

创建自定义控件

为标题栏中的控件添加点击事件:
1、新建 TitleLayout (一个 Activity 对应一个 xml 文件) 继承自 LinearLayout,让他成为我们自定义的标题栏控件,代码如下

class TitleLayout(context: Context,attrs:AttributeSet) : LinearLayout(context,attrs){
    init {
    //通过 from 方法构造出 LayoutInflater 对象,再调用 inflate 方法动态加载一个布局文件
    // inflate方法两个参数:1、布局文件的 id ;2、给加载好的布局再添加一个父布局,这里我们想要指定为 TitleLayout
        LayoutInflater.from(context).inflate(R.layout.title,this)
    }

}

2、上面就创建好了一个自定义控件,接下来在 firstActivity.xml 这个布局文件中添加这个自定义控件
Android 基础_第38张图片
演示效果和上面一样的:
Android 基础_第39张图片

3、为标题栏中的按钮添加监听事件

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,"可以编辑",Toast.LENGTH_SHORT).show()
        }
    }

}

在这里插入图片描述
注意到,在 xml 文件中配置的所有属性,都可以在 AttriButeSet 中获取到

最常用和最难用的控件:ListView

ListView 的简单用法

ListView 允许用户通过手指滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动出屏幕外,比如查看 qq 聊天记录等等
1、新建一个 ListViewTest 项目,并修改 activity_main.xml 中的代码,如下:

<?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"
    >

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>

2、在 MainActivity 中添加代码如下

class MainActivity : AppCompatActivity() {
    private var data= mutableListOf("A","B")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //构造数据
        for(i in 1..100){
            data.add("$i")
        }
    //三个参数:1、Activity 实例;2、子项布局的 id (这个是 Android 内置的一个布局);数据源(要展示的数据)
        val adapter=ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)
        listView.adapter =adapter
    }
}

演示效果,可以滑动
Android 基础_第40张图片

定制 ListView 的界面

演示效果如下:
1、定义一个实体类,作为 ListView 适配器的适配类型

class Fruit(val name:String,val imagedId:Int)

2、为 ListView 的子项指定一个我们自定义的布局,在 layout 文件下新建 fruit_item.xml ,代码如下:

<?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"
    >

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        />

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        />
</LinearLayout>

3、创建自定义适配器,新建类 FruitAdapter

class FruitAdapter(activity: Activity, val resourceId:Int,data:List<Fruit>):
    ArrayAdapter<Fruit>(activity,resourceId,data) {
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        /**
         * 使用 LayoutInflater 来为这个子项加载我们传入的布局
         */
        val view = LayoutInflater.from(context).inflate(resourceId,parent,false)
        val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
        val fruitName : TextView = view.findViewById(R.id.fruitName)
        val fruit=getItem(position) // 获取当前项的 Fruit 实例
        if(fruit!=null){
            fruitImage.setImageResource(fruit.imagedId)
            fruitName.text=fruit.name
        }
        return view;
    }
}

4、在 MainActivity 添加如下代码

class MainActivity : AppCompatActivity() {
    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits() //初始化
        val adapter = FruitAdapter(this,R.layout.fruit_item,fruitList)
        listView.adapter=adapter
    }

    private fun initFruits(){
        // 将 lambda 表达式执行两遍
        repeat(2){
            for(i in 1..10)  fruitList.add(Fruit("$i",R.drawable.download))
        }
    }
}
  • android:layout_gravity="center_vertical":垂直方向上居中显示

inflate 方法参数:第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局

演示效果:
Android 基础_第41张图片

提升 ListView 的运行效率

getView 方法的作用是,在每个子项被滚动到屏幕内的时候会被调用

上面写的这个程序运行效率是很低的,因为在 FruitAdaptr 的 getView() 方法中,每次都将布局重新加载了一遍。在 getView() 方法中,convertView 这个参数可以用来将之前加载好的布局进行缓存,以便之后进行重用。

性能优化如下:
Android 基础_第42张图片

convertView 可以让程序不会去重复加载布局,大大提升程序效率。但是每次调用 getView 方法的时候还是得用 finViewVyId() 去找组件,这样很影响效率,我们可以用 viewHolder 来对 ImageView 和 TextView 控件进行缓存,代码如下:

Android 基础_第43张图片

ListView 的点击事件

修改 MainActivity 中的代码,如下:
Android 基础_第44张图片

演示效果就是点击子项,会有对应的文字弹出

Tip :对于方法中用不到的参数可以使用下划线代替

滚动控件:RecyclerView

写的好的链接:https://blog.csdn.net/qq_33275597/article/details/93849695
ListView 不能横向滚动,RecyclerView 可以,并且 RecyclerView 相对于 ListView 做了更多优化

RecyclerView只是一个ViewGroup,它只认识View,所以需要一定的规则来使 Datas 中的数据展示在 RecyclerView 上面,所以适配器的功能就是在做这个事情

Android 基础_第45张图片

如上所示,RecyclerView 表示只会和 ViewHolder 接触,而 Adapter 的工作就是将 Data 转换为 RecycleView 认识的 ViewHolder ,因此 RecyclerView 就间接认识了 Datas

但是 RecycleView 并不像自己去管理 View,所以他会把完成布局的任务交给 LayoutManager

Android 基础_第46张图片
LayoutManager 协助 RecycleView 来完成布局,但是 LayoutManager 只知道如何将一个一个的 View 布局再 RecyclerView 上,并不想自己管理 View,所以会把管理 View 的任务交给 Recycler ,LayoutManager 在需要 View 的时候向 Recycler 进行说去,当 LayoutManager 不需要 View 的时候,就直接将废弃的 View 丢给 Recycler ,图示如下:
Android 基础_第47张图片

RecyclerView 的基本用法

1、新建一个 RecyclerViewTest 项目
2、添加 RecyclerView 的依赖
Android 基础_第48张图片

    implementation 'androidx.recyclerview:recyclerview:1.0.0'

3、修改 activity_main.xml 中的代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>

4、为 RecyclerView 准备一个适配器,新建 FruitAdapter 类,继承自 RecyclerView.Adapter,代码如下

class Fruit(val name:String,val imagedId:Int)

class FruitAdapter(val fruitList:List<Fruit>) : RecyclerView.Adapter<FruitAdapter.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)
    }
// 创建 viewHolder 实例
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)
        return ViewHolder(view)
    }

    override fun getItemCount()=fruitList.size

    //用于对 RecyclerView 中的子项进行赋值,在每个子项滚动到屏幕内的时候执行
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.fruitImage.setImageResource(fruit.imagedId)
        holder.fruitName.text=fruit.name
    }
}

5、修改 MainActivity 中的代码

class MainActivity : AppCompatActivity() {

    private val fruitList =ArrayList<Fruit>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initFruits() // 初始化水果数据

        //创建一个 LinearLayoutManager 对象,并把它设置到 recylerView 中
        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager=layoutManager

        val adapter = FruitAdapter(fruitList)
        recyclerView.adapter=adapter

    }

    private fun initFruits(){
        repeat(2){
            for(i in 1..20)
            fruitList.add(Fruit("$i",R.drawable.download))
        }
    }
}

演示效果和上面一样

实现横向滚动和瀑布流布局

1、修改 first_item.xml 的布局
Android 基础_第49张图片
2、修改 MainActivity
Android 基础_第50张图片

效果演示:
Android 基础_第51张图片
你可以用手指在水平方向上互动来查看屏幕外的数据

为什么 ListView 很难或者根本无法实现的效果在 RecyclerView 上这么轻松就实现了呢?

因为 ListView 的布局排列是由自身管理的,而 RecycleView 则将这个工作交给了 LayoutManagerLayoutManager 制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能指定出各种不同排列方式的布局了

RecyclerView 的点击事件

和 ListView 不同,RecylerView 没有提供类似于 setOnInteClickListerner() 这样的注册监听器方法,而是需要我们自己给子项具体的 View 去注册点击事件,这样做的好处就是当只需要点击某一个子项的时候,不点击其他子项的时候就很方便

Android 基础_第52张图片

// 创建 viewHolder 实例
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)
        val viewHolder = ViewHolder(view)
        // itemView 是 View 类型,是 ViewHolder 类的一个成员变量
        viewHolder.itemView.setOnClickListener{
            val position = viewHolder.adapterPosition
            var fruit = fruitList[position]
            Toast.makeText(parent.context,"你点击了${fruit.name}",Toast.LENGTH_SHORT).show()
        }


        viewHolder.fruitImage.setOnClickListener{
            val position = viewHolder.adapterPosition
            var fruit = fruitList[position]
            Toast.makeText(parent.context,"你点击了图片",Toast.LENGTH_SHORT).show()
        }
        return viewHolder
    }

延长初始化和密封类

对变量延迟初始化

有时候,类中存在很多全局变量实例,为了使他们满足 Kotlin 的空指针检查机制,你不得不做很多费控判断保护才行,即使你知道他们不会为空
对全局变量延迟初始化就可以解决上述问题
格式:

定义一个变量的时候:lateinit var 变量名:变量类型 
判断一个延迟加载的全局变量时候已经完成初始化操作:
if(::变量名.isInitialized){ 
	println("已经初始化了")
}

使用密封类优化代码

密封类的关键字是 sealed class
例如:sealed class Result

密封内及其所有子类只能定义在同一个文件的顶层位置

密封类的作用就是,可以识别出他的所有子类,在你写条件语句的时候,不用判断 other

Fragment

Fragment 是一种可以嵌入在 Activity 当中的 UI 片段,在平板上应用广泛,可以看成迷你型的 Activity

Fragment 的简单用法

案例要求:在一个 Activity 当中添加两个 Fragment,并让这两个 Fragment 平分 Activity 空间

1、 新建一个左侧 Fragment 的布局 left_fragment.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"
        />


LinearLayout>

2、新建一个右侧 Fragment 的布局 right_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00ff00"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="24sp"
        android:text="这是右边部分"
        />
</LinearLayout>

3、新建一个 LestFragment 类继承自 Fragment

class LeftFragment : Fragment(){
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.left_fragment, container, false)
    }
}

4、新建一个 RightFragment

class RightFragment : Fragment(){
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.right_fragment, container, false)
    }
}

5、修改 activity_main.xml

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

演示效果:
Android 基础_第53张图片

动态添加 Fragment

Fragment 强大的地方在于他可以动态的添加到 Activity 中
1、新建一个 another_right_fragment.xml ,代码如下:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffff00"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="24sp"
        android:text="这是右边部分"
        />
LinearLayout>

2、新建 AnotherFragment 作为另外一个 Fragment

class AnotherRightFragment : Fragment(){
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.another_right_fragment, container, false)
    }
}

3、在 activity_main.xml 添加如下代码,将 Fragment 替换成了 FrameLayout

Android 基础_第54张图片

4、在 MainActivity 修改代码如下

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener{
            replaceFragment(AnotherRightFragment())
        }
        replaceFragment(RightFragment())
    }

    private fun replaceFragment(fragment: Fragment){
        val fragmentManger =supportFragmentManager
        val transaction = fragmentManger.beginTransaction()
        transaction.replace(R.id.rightLayout,fragment)
        transaction.commit()
    }
}

总结动态添加 Fragment 主要分为以下 5 步:
1、创建待添加的 Fragment 实例
2、获取 FragmentMannager
3、开启一个事物
4、向容器添加或替换 Fragment ,一般使用 replace() 方法实现,需要传入容器的 ID 和待添加的 Fragment 实例
5、提交事物

演示效果:
点击按钮之前:

Android 基础_第55张图片

点击按钮之后:
Android 基础_第56张图片

在 Fragment 中实现返回栈

通过按钮添加了一个 Fragment 之后,按下 Back 键并不会回到上一个 Fragment ,而是会直接退出。
addToBackStack() 方法,可以将一个事物添加到返回栈中

修改 MainActivity 中的代码:

Android 基础_第57张图片

Fragment 和 Activity 之间的交互

为了方便 Fragment 和 Activity 之间的交互 ,FragmentManager 提供了一个类似于 findViewById() 的方法,专门用于从布局文件中获取 Fragment 实例

不同的 Fragment 通过 Activity 进行调用

Fragment 的生命周期

Fragment 提供的附加的回调方法

  • onAttach() :当 Fragment 和 Activity 建立联系的时候调用
  • onCreateView():为 Fragment 创建视图(加载布局)时调用
  • onActivityCreated() :确保 Fragment 相关联的 Activity 已经创建完毕时调用
  • onDestoryView():当与 Fragment 关联的视图移除时调用
  • onDetach():当 Fragment 和 Activity 解除关联时调用

动态加载布局时的技巧

动态加载布局的技巧:让程序根据设备的分辨率或屏幕大小,在运行时决定加载哪个布局

使用限定符

1、修改 activity_main.xml 文件,代码如下:

Android 基础_第58张图片
2、在 res 下新建一个layout-large文件夹,在这个文件夹下新建一个 activity_main.xml 文件,内容如下
Android 基础_第59张图片
3、修改 MainActivity

Android 基础_第60张图片

演示效果:
平板:
Android 基础_第61张图片

手机:
Android 基础_第62张图片
可以看到,layout/activity_main 布局只包含了一个 Fragment ,即单页模式,而 layout-large/activity_main 布局包含了两个 Fragment ,即双页模式。其中 large 就是一个限定符,那些屏幕被认为是 large 的设备就会自动加载 layout-large 文件夹下的布局,小的设备还是会加载 layout 文件夹下的布局

最小宽度限定符

新建 layout-sw600dp 文件夹,其他和上面一样

扩展函数和运算符重载

扩展函数

扩展函数:即使在不修改某个类的源码的情况下,仍然可以打开这个类,向该类中添加新的函数

语法结构:

fun ClassName.methodName(parm1: Int,parm2" Int){
	return 0
}

运算符重载

运算符重载使用的是 operator 关键字,只要在指定函数前加上这个关键字即可,这个指定函数对应的重载函数式固定的,比如加号运算符重载对应 plus(),减号运算符重载对应 minus()

语法糖表达式和实际调用函数对照表:
Android 基础_第63张图片

高阶函数详解

高阶函数定义:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数

定义一个函数类型,语法规则:

(String,Int) -> Unit

案例演示:

    fun example(func:(Int,Int) -> ){ //接收了一个函数类型的参数
        func(1,2) // 调用函数类型的参数
    }

只要传入不同的函数类型参数,那么他的执行逻辑和最终返回结果就可能是完全不同的,比如,上面那个 example 函数,你可以传一个 add(Int,Int) 函数,你也可以传一个 minus(Int,Int)

Tip:Lambda 表达式是最常见的高阶函数调用方式

内联函数作用

高阶函数的原理实际上是我们每一次调用 lambda 表达式,都会创建一个新的匿名类实现

内联函数的用法就是在定义高阶函数的时候加上 inline 关键字的声明即可

内联函数的工作原理是:在 Kotlin 编译的时候会把内联函数中的代码在编译的时候自动替换到调用它的地方,这样就不存在运行时开销了

noinline 和 crossinline

noinline :排除内联功能

noinline 意义:内联的函数参数类型参数在编译的时候会被进行代码替换,因此他没有真正的参数属性。非内联的函数类型参数可以只有地传递给其他任何函数,因为他就是一个真实的参数,而内联的函数类型参数只允许传递给另外一个内联函数,这就是他最大的局限性。

Tip:内联函数所引用的 lambda 表达式中可以使用 return 关键字来进行函数返回,而非内联函数只能进行局部返回,因为内联函数是字节替换成主体代码的一部分,return 就像是字节写在主体里面的

crossinline:解决矛盾的:内联函数中的 lambda 表达式中允许使用 return 关键字,而高阶函数的匿名类实现中不允许使用 return 关键字造成了冲突

详解持久化技术

Android 系统中主要提供了 3 种方式用于简单的实现数据持久化功能:文件存储、 SharedPreferences 存储以及数据库存储

文件存储

文件存储是 Android 最基本的数据存储方式,不对存储的内容进行任何格式化处理,所有数据都是原封不动的保存到文件当中的,因而他比较适合存储一些简单的文本数据或二进制数据

将数据存储到文件中

Context 类中提供了一个 openFileOutput() 方法,用于将数据存储到指定的文件中。这个方法接收两个参数:

  • 文件名,在创建文件的时候使用(不写文件路径)
  • 文件的操作模式:MODE_PRIVATE 和 MODE_APPEND 。默认是 mode_private ,表示当指定相同文件名的时候,所写内容将会覆盖原文件中的内容。mode_append 表示如果该文件已经存在,就往文件里面追加内容,步存在就创建新的文件。还有另外两种:mode_world_readable 和 mode_world_writeable ,这两种模式表示允许其他应用程序对我们程序中的文件进行读写操作,不过已废弃

openFileOutput() 方法返回的是一个 FileOutputStream 对象

use 扩展函数 是 Kotlin 提供的一个内置扩展函数,它会保证在 lambda 表达式中的代码全部执行完之后自动将外层的流关闭

案例演示:

1、新建一个 FilePersistenceTest 项目,修改 activity_main.xml 中的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="在这里编辑"
        />
</LinearLayout>

2、在Activity 被销毁之前,把编辑框中输入的内容持久化

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onDestroy() {
        super.onDestroy()
        val inputText = editText.text.toString()
        save(inputText)
    }

    private fun save(inputText:String){
        try{
            val output=openFileOutput("data", Context.MODE_APPEND)
            val write = BufferedWriter(OutputStreamWriter(output))
            write.use{
                it.write(inputText)
            }
        }catch(e:IOException){
            e.printStackTrace()
        }
    }
}

当你按下 Back 关闭程序的时候,我们输入的内容就保存到文件中了,可以借助 Device File Explorer 工具进行查看,这个工具相当于一个设备文件浏览器

Android 基础_第64张图片

data 已经保存好了你刚刚在编辑框中输入的内容
在这里插入图片描述

从文件中读取数据

Context 类提供的 openFileInput() 方法泳衣从文件中读取数据,该方法只有一个参数,即要读取的文件名,系统会自动的到 /data/data//files/目录下加载这个文件,并返回一个 FileInputStream 对象

forEachLine 是 kotlin 提供的一个内置扩展函数,它会将读到的每行内容都回调到 lambda 表达式中

SharedPreferences 存储

SharedPreferences 使用键值对的方式来存储数据,也就是说,当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把对应的值取出来。而且 SharedPreference 支持多种不同的数据类型存储

将数据存储到 SharedPreference

获取 SharedPreference 对象的两种方法:

  • Context 类提供的 getSharedPerferences()
    该方法提供两个参数:1、指定 SharedPreference 文件的名称,如果指定的文件不存在则会创建一个 2、用于指定操作模式,目前只有默认的 mode_private 这一种模式可选

  • Context 类中提供的 get Perferences()
    该方法只有一个参数,因为这个方法会自动将 Activity 的类名作为 SharedPreference 的文件名

得到 SharedPreference 对象,就可以开始向 SharedPreference 文件中存储数据了

案例演示:
1、新建一个 SharedPreferenceTest 项目,修改 activity_main.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/saveBUtton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存"
        />
LinearLayout>

2、修改 MainActivity 中的代码

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        saveBUtton.setOnClickListener{
            //1、调用 SharedPreferences 对象的 edit 方法获取一个 SharedPreferences.Editor 对象
            val editor = getSharedPreferences("data2", Context.MODE_PRIVATE).edit()
            editor.putString("name","张三")
            editor.putInt("age",18)
            //调用 apply 方法将数据提交
            editor.apply()
        }
    }
}

Android 基础_第65张图片

可以看到生成了一个 data2.xml 文件,内容如下:
Android 基础_第66张图片

可以看到,我们刚刚在按钮的点击事件中添加的所有数据都已经成功保存下来, SharedPreferences 文件是使用 XML 格式来对数据进行管理的

从 SharedPreferences 文件读取数据

直接使用 SharedPreference 对象的 getXXX() 方法即可

SQLite 数据库存储

SQLite 是 Android 系统内置的数据库,不仅支持标准的 SQL 语句,还遵循数据库的 ACID 事物

创建数据库

Android 为了让我们更加方便的管理数据库,专门提供了一个 SQLiteOpenHelper 帮助类,借助这个类可以很方便的对数据库进行创建和升级

SQLiteOpenHelper 是一个抽象类,有两个抽象方法:onCreate() 和 onUpgrade() 分别对应创建和升级数据库

SQLiteOpenHelper 有两个重要的实例方法:

  • getWritableDatabase()
  • getReadableDatabase()
    这两个方法都可以用于创建和打开一个现有的数据库,如果数据库存在则直接打开,否则则创建一个数据库

SQLiteOpenHelper 还有两个构造方法可供重写:

/**
 * Context:有他才能对数据库进行操作
 * name:数据库名,创建数据库时使用的就是这里指定的名称
 * factory:允许我们在查询数据的时候返回一个自定义的 Cursor ,一般传入 Null
 * version:表示当前数据库的版本号,可对数据库进行升级操作
 */
public SQLiteOpenHelper(Context context, String name,CursorFactory factory, int version) {
    this(context, name, factory, version, null);
}

案例要求:创建一个名为 BookStore.db 的数据库,然后在这个数据库中新建一张 Book 表,表中有 id,作者、价格等列

案例演示:
1、新建 MyDatabaseHelper 类继承自 SQLiteOpenHelper,代码如下:

class MyDatabaseHelper (val context: Context, name:String, version:Int):
    SQLiteOpenHelper(context,name,null,version){

    override fun onCreate(db: SQLiteDatabase?) {
        if (db != null) {
            db.execSQL(createBook)
            Toast.makeText(context,"创建成功!",Toast.LENGTH_SHORT).show()
        }

    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {

    }

    private val createBook ="create table Book(" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real)"
}

2、修改 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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/createDatabase"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="创建数据库"
        />
</LinearLayout>

3、在 MainActivity 中添加如下内容

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dbHelper = MyDatabaseHelper(this,"BookStore.db",1)
        createDatabase.setOnClickListener{
            //两个重要的实例方法中的其中一个
            dbHelper.writableDatabase
        }
    }
}

运行结果:

Android 基础_第67张图片

升级数据库

当需要对数据库进行修改的时候,就可以使用 onUpgrade 方法,比如,在 Book 表中新增一个字段之类的

当你传参的数据库版本号大于之前的版本号,onUpgrade 方法就会被执行
contentValues ,提供了一系列 put() 方法重载,用于向 ContentValues 中添加数据,只要将表中的每个列名以及相应的待添加数据传入即可

使用事务

val db=dbHelper.writableDatabase
db.beginTransaction() 开启事务
db.setTransactionSuccesful() // 事务已经执行成功
db.endTransaction() // 结束事务
  • vararg 对应的是 Java 中的可变参数

高阶函数的应用

简化 SharedPreferences 的用法

SharedPreferences 的常规用法:
Android 基础_第68张图片

使用高阶函数简化 SharedPreferences 的用法:
1、新建一个 SharedPreferences.kt 文件,然后在里面加入如下代码:
Android 基础_第69张图片
2、修改 activity_main 中的代码
Android 基础_第70张图片

演示效果和上面是一样的:
生成了 data.xml
Android 基础_第71张图片
data.xml 中的数据就是我们 put 进去的:
Android 基础_第72张图片
其实在 Google 提供的 KTX 扩展库中已经包含了上述 SharedPreferences 的简化用法,这个扩展库会在 Android Studio 创建项目的时候自动引入依赖,如下:
Android 基础_第73张图片
我们在使用的时候,直接代用 edit 方法就好了,也就是将上面的 open 换成 edit 就好了,如下:

Android 基础_第74张图片

泛型和委托

泛型

什么是泛型: 在一般的编程模式下,我们需要给任何一个变量指定一个具体的那类型,而泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码拥有更好的扩展性

定义泛型的两种方式:一种是定义泛型类,一种是定义泛型方法,使用的语法结构都是 ,括号内的 T q其实可以是任意字母,但是我们一般使用 T

Kotlin 允许我们对泛型的类型进行限制,比如:
泛型的上界默认是 Any

class MyClass{
// 这里的意思就是将 method 方法的泛型上界设置成 Number 类型
	fun <T : Number> method(param:T):T{
		return param
	}
}

委托

类委托

委托实际上是一种设计模式,他的基本理念是 : 操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。就像 Java 里面的 HashSet 类,HashSet 其实是基于 HashMap 实现的,HashSet 直接封装了 HashMap ,很多方法也就直接调用的 HashMap 类里面的。在这里 HashMap 就是一个辅助对象。

委托模式的意义就在于:对辅助对象稍加修改,就可以形成另一个全新的数据结构,比如 HashSet 和 HashMap

但是 Java 中并没有委托的实现,比如,HashSet 想使用 HashMap 的方法,他选择的是 HashMap 作为参数,但是还可以通过继承的方法去实现,如果通过继承的方式去实现的话,假如 HashMap 是个接口,就得重写 HashMap 里面所有的方法, Kotlin 在这方面做了改进

直接在接口声明的后面使用 by 关键字,再接上受委托的辅助对象,就可以免去一大堆模板式的代码了

委托属性

委托属性的核心思想就是将一个属性(字段)的具体实现委托给另一个类去完成

委托属性语法结构:

class MyClass{
	var p by Delegate()
}

这种写法的意思是将 p 属性的具体实现委托给了 Delegate 类去实现,当调用 p 属性的时候会自动调用 Delegate 类的 getValue() 方法,当给 p 属性赋值的时候,会自动调用 Delegate 类的 setValue() 方法

所以 Delegate 类要实现 getValue() 和 SetValue() 方法

实现一个自己的 lazy 函数

懒加载:把想要延迟执行的代码放在 by lazy 代码块中,这样带阿玛在一开始的时候不会执行,只有在初次被调用的时候,代码块中的代码才会执行

by lazy 的语法结构:

var p by lazy{}

其实这个 lazy 就是一个高阶函数,在 lazy 中会创建并返回一个 Delegate 对象,当我们调用 p 属性的时候,实际上调用的是 Delegate 的 getValue() 方法

使用 infix 函数构建更可读的写法

A to B 可以构建一个键值对, 但是 to 并不是 Kotlin 中的一个关键字,而是由于 Kotlin 提供了一种高级的语法糖特性: infix 函数。比如 A to B ,实际上是 A.to(B)

下面我们就通过两个具体的例子来学习一下 infix 函数的用法,先从简单例子开始

String 类中有一个 startWith() 函数,语法结构如下:

"mmmmm".startsWith("m")

借助 infix 函数,我们可以把他改成更可读的写法:

//定义了一个 String 类的扩展函数,他在内部实现就是调用 String 类的 startsWith() 函数
infix fun String.beginsWith(prefix:String) = startsWith(prefix)

这样之后,上面那段话等价于:

"mmmmm" beginsWith "m"

to() 函数的原理:

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

泛型的高级特性

泛型的实化

Java 的泛型功能是通过类型擦除机制来实现的,也就是说泛型对于类型的约束只在编译使其存在

所有基于 JVM 的语言,他们的泛型功能都是通过擦除机制来实现的,Kotlin 也是这样。这种机制使得我们不可能使用 a is T 或者 T::class.java 这样的语法,因为 T 的实际类型在运行的时候已经被擦除了,而且是把 T被替换成 Object,所以 object.getClass() 没什么意义,得不到他实际的 Class 对象

但是在 Kotlin 中,有一个内联函数,这个内联函数的功能就是会把代码在编译的时候自动替换到调用它的地方,这样的话,也就不存在什么泛型擦除的问题了,代码在编译之后会直接使用实际的类型来替代内联函数中的泛型声明

泛型实化的时候该函数必须是内联函数才行,在声明泛型的地方必须加上 reified 关键字来表示该泛型要进行实化,语法结构如下:

inline fun <reified T> getGenericType(){
}
inline fun <reified T> getGenericType()=T::class.java

getGenericType 函数直接返回了当前指定泛型的实际类型,使得 T.class 变得合法

泛型的协变

注意:Student 是 Person 的子类,但是 List 并不是 List的子类

泛型协变的定义:假如定义了一个 MyClass 的泛型类,其中 A 是 B 的子类,同时 MyClass又是 MyCLass 的子类型,那么我们就可以称 MyClass 在 T 这个泛型上是协变的

但是 MyClass如果是 MyCLass 的子类型这种,存在安全隐患,解决办法是,让泛型类在其泛型类型的数据上是只读,而要实现这一点,则需要让MyClass类中的所有的方法都不能接收 T 类型的参数,换句话说,T 只能出现在 out 位置上,而不能出现在 in 位置上

泛型的逆变

泛型逆变的定义:假如定义了一个 MyClass 的泛型类,其中 A 是 B 的子类(AB),同时 MyClass又是 MyCLass 的子类型(BA),那么我们就可以称 MyClass 在 T 这个泛型上是协变的

也就是 T 只能出现在 in 的位置,不能出现在 out 的位置

使用协程编写高效的并发程序

协程可以看成是轻量级的线程,他可以让我们在单线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完全是由编程语言来控制的,和操作系统无关

协程的用法

Kotlin 并没有将协程纳入标准库的 API 中,而是以依赖库的形式提供的,所以在使用协程前要先导入依赖:

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'

使用 GlobalScope.launch 开启一个协程

fun main() {
    //开启一个协程
    GlobalScope.launch{
        println("xxxxxx")
        //delay(1500)
    }
}

delay() 函数可以让当前协程延迟指定实践后再运行,delay() 函数只能在协程的作用域或者其他挂起函数中调用

runBlocking(){} 函数同样会创建一个协程的作用域,但是他可以保证在协程作用域内的所有代码和子协程没有全部执行之前一直阻塞当前线程。但是需要注意的是,runBlocking 函数通常应该在测试环境下使用

单独的 launch 函数只能在协程的作用域中才能调用,如下:
Android 基础_第75张图片
演示效果:
在这里插入图片描述

这里的 launch 和 GlobalScope.launch 不一样, GlobalScope.launch 创建的是顶级协程。launch 创建的是子协程,子协程的特点就是在外层作用域协程结束之后,该作用域下的所有子协程也会一同结束

suspend 关键字,可以将任意函数声明为挂起函数,挂起函数之间可以互相调用

但是 suspend 只能将任意函数声明为挂起函数,但是是无法给她提供协程作用域的

coroutineScope 函数的特点是会继承外部协程作用域并创建一个子作用域

更多作用域构建器

怎么取消一个协程?不管是 GlobalScope.launch 还是 launch ,都会返回一个 job 对象,调用 job.cancle() 就可以取消一个协程

asyc 函数
withContext函数

  • Android 要求网络请求必须在子线程中进行

编写好用的工具方法

求 N 个数的最大最小值

fun <T : Comparable<T> > max(vararg nums:T):T{
    if(nums.isEmpty()) throw RuntimeException("参数不能为空")
    var maxNum=nums[0]
    for(num in nums) maxNum=max(maxNum,num)
    return maxNum
}

简化 Toast 用法

给 String 类新增一个 showToast 函数,就可以直接调用 ”字符串“.showToast(context) 了

Java I/O 详解

Android 基础_第76张图片

WebView 的用法

借助 webView 控件我们就可以在自己的应用程序里嵌入一个浏览器,从而非常轻松的展示各种各样的网页

案例演示:
1、新建一个 WebViewTest,修改 activity_main.xml 中的代码


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

   <WebView
       android:id="@+id/webView"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       />
LinearLayout>

2、修改 MainActivity 中的代码

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //通过 WebView 的 getSettings() 方法可以设置一些浏览器的属性
        webView.settings.setJavaScriptEnabled(true) //支持 JavaScript 脚本
        //给 webView 传入一个WebViewClient 实例,这段代码的作用是,
        // 当需要从一个网页跳转到另一个网页时,我们希望目标网页仍然在当前的 WebView 中显示
        webView.webViewClient = WebViewClient()
        webView.loadUrl("https://www.baidu.com")
    }
}

3、修改 AndroidManifest.xml 文件,加入权限声明

<uses-permission android:name="android.permission.INTERNET"/>

Android 基础_第77张图片

Android 基础_第78张图片

使用 HTTP 访问网络

使用 HttpURLConnection

案例演示:
首先需要创建一个 URL 对象,并传入目标的网络地址,然后调用一下 openConnection() 方法即可,如下所示:

        val url = URL("https://www.baidu.com")
        val connection =url.openConnection() as HttpURLConnection

得到 connection 之后,就可以设置一些属性了,比如说:

//HTTP 请求时的方法
connection.requestMethod="GET"
//读取超时的毫秒数
connection.readTimeout=80000

之后再调用 getInputStream() 方法就可以获取到服务器返回的输入流了,剩下的就是对输入流进行读取

connection.inputStream

最后可以调用 disconnect() 方法将这个流关闭

connection.disconnect()
ScrollView 控件,可以以滚动的形式查看屏幕外的内容

案例演示:
1、新建一个 NetworkTest 项目,修改 activity_main.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/sendRequestBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送数据"
        />
    
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/responseText"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    ScrollView>
LinearLayout>

2、修改 MainActivity 中的代码如下:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sendRequestBtn.setOnClickListener{
            sendRequestWithHTTPURLConnection()
        }
    }
    
    private fun sendRequestWithHTTPURLConnection(){
        //开启线程发起网络请求
        thread{
            var connection:HttpURLConnection?=null
            try {
                val response=StringBuilder()
                val url= URL("https://www.baidu.com")
                connection =url.openConnection() as HttpURLConnection
                connection.connectTimeout=8000
                connection.readTimeout=8000
                val input=connection.inputStream
                //对获取到的输入流进行读取
                val reader = BufferedReader(InputStreamReader(input))
                reader.use{
                    reader.forEachLine { 
                        response.append(it)
                    }
                }
                showResponse(response.toString())
            }catch (e:Exception){
                e.printStackTrace()
            }
        }
    }
    
    private fun showResponse(response:String){
        runOnUiThread{
            //在这里进行 UI 操作,将结果显示到界面上
            responseText.text = response
        }
    }
}

3、开启网络权限
在这里插入图片描述

演示效果
Android 基础_第79张图片

使用 OkHttp

1、在项目中添加 OkHttp 库的依赖

    implementation 'com.squareup.okhttp3:okhttp:4.1.0'

2、创建一个 OkHttlClient 的实例

val client= OkHttpClient()

3、如果想要发起一个 HTTP 请求,就需要再创建一个 Request 对象:

val request=Request.Builder().build()

4、调用 OkHttpClient 的 Call() 方法创建一个 call 对象,并调用它的 execute() 方法来请求并获取服务器返回的数据

val response=client.newCall(request).execute()

5、得到返回的具体内容

val responseData=response.body?.string()

如果是 POST 方法还要先构造一个请求体

解析 XML 格式数据

Material Design 实战

ToolBar

这是由 Androidx库提供的,和 ActionBar 有相似的功能。ActionBar 就是前面提到过的系统自带的原生的那个标题栏

ToolBar 的强大之处在于他不仅继承了 ActionBar 的所有功能,而且灵活性很高,可以配合其他控件实现一些 Material Design 效果

每个项目默认都是会显示 ActionBar 的,这个 ActionBar 从哪来呢?其实是根据项目中指定的主题来显示的。打开 AndroidManifest.xml 文件,可以看到:

Android 基础_第80张图片

这个主题是在 res/values/styles.xml 文件下定义的,代码如下:
Android 基础_第81张图片

现在我们准备用 ToolBar 来替代 ActionBar ,因此需要指定一个不带 ActionBar 的主题,通常有 Theme.AppCompat.NoActionBar 和 Theme.AppCpmpat.Light.NoActionBar 这两种主题可选。其中 Theme.AppCompat.NoActionBar 表示深色主题他会将界面的主题颜色设为深色,陪衬颜色设置为浅色。 而 Theme.AppCpmpat.Light.NoActionBar 表示浅色主题,他会将主题颜色设置为浅色,陪衬颜色设置为深色

属性重写,各属性指定颜色的位置:
Android 基础_第82张图片

1、修改 activity_main.xml 的代码如下


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    指定了一个新的命名空间,为了兼容老系统
    xmlns:aop="http://schemas.android.com/apk/res-auto">
定义了一个 Toolbar 控件,这个控件是由 appcompat 库提供的
<androidx.appcompat.widget.Toolbar
	android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="@color/design_default_color_primary"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    单独将弹出的菜单项指定成浅色主题
    aop:popupTheme="@style/ThemeOverlay.AppCompat.Light"
   />
FrameLayout>

2、修改 MainActivity 布局

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
    }
}

3、

演示效果:

还可以修改标题栏上的文字,代码如下:
Android 基础_第83张图片
效果演示:
Android 基础_第84张图片

还可以在标题栏上添加一些图片:
步骤如下:
1、新建 menu 文件夹,并在 menu 文件夹下新建 toolbar.xml 文件,并编写如下代码:


<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/backup"
        android:icon="@drawable/download"
        android:title="backup"
        app:showAsAction="always"
        />

    <item
        android:id="@+id/delete"
        android:icon="@drawable/download"
        android:title="delete"
        app:showAsAction="ifRoom"
        />


    <item
        android:id="@+id/setting"
        android:icon="@drawable/download"
        android:title="setting"
        app:showAsAction="never"
        />
menu>

aop:showAsAction 有三个值可选:always 表示永远显示在 ToolBar 中,如果屏幕空间不够则不显示、ifRoom 表示屏幕空间足够的情况下显示在 ToolBar 中,不够则显示在拆散中,never 则表示永远显示在菜单中。注意:Toolbar 中的 action 按钮只会显示图标,菜单中的 action 按钮只会显示文字

2、修改 MainActivity 中的代码:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.toolbar,menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId){
            R.id.backup -> Toast.makeText(this,"点击了BackUp",Toast.LENGTH_SHORT).show()
        }
        return true
    }
}

滑动菜单

所谓滑动菜单就是讲一些菜单选项隐藏起来,而不是放置在主屏幕上

Androidx 提供了一个 DrawerLayout 控件,可以帮助我们实现滑动菜单

DrawerLayout 就是一个布局,在布局中允许放入两个直接子控件:第一个控件是主屏幕上显示的内容,第二个子控件是滑动菜单上显示的内容

1、修改 activity_main.xml 代码如下Android 基础_第85张图片

TextView 必须指定 gravity 属性,才能告诉 DrawerLayout 滑动菜单在屏幕的左边还是右边
Android 基础_第86张图片

当从左向右滑动的时候,演示效果如下:
Android 基础_第87张图片

NavigationView

NavigationView 是 Material 库中提供的一个控件,它不仅严格按照 Material Design 的要求来设计的,而且可以将滑动菜单页面的实现变得非常简单

需要导入依赖

    implementation 'com.google.android.material:material:1.2.1'
    implementation 'de.hdodenhof:circleimageview:3.0.1'

悬浮按钮和可交互提示

FloatingActionButton

悬浮按钮

Snackbar

提示工具,和 Toast 有点像,不过他在提示中加入了一个可交互按钮,当用户点击的时候,可以进行一些额外的逻辑操作

卡片式布局

MaterialCardView

是用于实现卡片式布局效果的重要控件,只是额外提供圆角和阴影效果,看上去会有立体的感觉

下拉刷新

可折叠式标题栏

AppBarLayout

相当于垂直布局的 LinearLayout ,只是在 LinearLayout 上加了一些材料设计的概念,它可以让你定制当 某个可滚动View 的滚动手势发生变化时,其内部的子 View 实现何种动作

内部的子 View 布局通过 app:layout_scrollFlags 设置执行的动作

NestedScrollView 可以看成是 ScrollView,可以给他设置 behavior 属性

CollapsingToolBarLayout:是用来对 ToolBar 进行包装的,主要是实现折叠(伸缩)效果,需要放在 AppBarLayout 布局里面,且作为 AppBarLayout 的直接子 View

CoordinatorLayout

CoordinatorLayout 是加强版的 FrameLayout,可以监听其子控件的所有事件,根据我们的定制,可以帮我们协调子 View

CoordinatorLayout 的核心是 Behavior ,Behavior 就是执行你定制的动作, Dependency 这个 view 发生了变化。那么 Child 的这个 view 就要发生相应的变化,具体发生的变化执行的代码就放在 Behavior

在 CoordinatorLayout 内部,每个 child 都必须携带一个 Behavior (其实不携带也可以,不携带就不能被协调),CoordinatorLayout 就根据每个 child 所携带的 Behavior 信息进行协调

注意:Behavior 不仅仅协助联动,而且还是接管了 child 的三大流程,有点类似于 RecyclerView 的 LayoutManager

AppBarLayout

你可以把它当成垂直布局的LinearLayout来使用,它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作。

内部的子 View 通过在布局中添加 app:layout_scrollFlags设置执行的动作

CollapsingToolbarLayout

CollapsingToolbarLayout 是用来对 ToolBar 进行再次包装的 ViewGroup ,主要是用于实现折叠的 AppBar 效果,CollapsingToolbarLayout 需要作为 AppBarLayout 的直接子View才会有效,他的子 View 通过app:layout_collapseMode="parallax|pin" 来设置效果

探究 JetPack

JetPack 是一个开发组件工具集,通常定义在 Androidx 中,主要由基础、架构、行为、界面四部分组成。

ViewModel :只要是界面上能看到的数据,他的相关变量都应该放在ViewModel 中,而不是 Activity 中,这样可以在一定程度上减少 Activity 中的逻辑。还有个很重要的功能就是在发生竖屏旋转的时候,Activity 不会被重建

Lifecycles :感受 Activity 的生命周期

LiveData:可以包含任何类型的数据,并在数据发生变化时通知给观察者

Room 主要由 Entity、Dao、DataBase 组成

WorkManager 处理定时的工具

ViewModel 的基本用法

案例要求:实现一个计数器的功能

1、导入依赖

    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

通常来讲比较好的编程规范就是给每个 Activity 和 Fragment 都创建一个对应的 ViewModel

2、为 MainActivity 创建一个 对应的 MainViewModel 类,并让他继承自 ViewModel,并放置一个 counter 变量用于计数

class MainViewModel:ViewModel() {
    var counter=0
}

3、在界面上添加一个按钮,每点击一次就让他加1,并把最新的计数显示在界面上,修改 activity_main.xml 中的代码如下:


<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/infoText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="32sp"
        />
    
    <Button
        android:id="@+id/plusButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Plus one"
        />
LinearLayout>

4、修改 MainActivity 中的代码,实习计数器逻辑

class MainActivity : AppCompatActivity() {
    lateinit var viewModel:MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //不可以直接创建 ViewModel 实例,必须通过 ViewModelProvider 去创建
        //this是你的  activity 或者 fragment 实例
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        plusButton.setOnClickListener{
            viewModel.counter++
            refreshCounter()
        }
    }

    private fun refreshCounter(){
        infoText.text=viewModel.counter.toString()
    }
}

演示效果:
Android 基础_第88张图片

Lifecycles

Lifecycles 的作用就是感知 Activity 的生命周期,而且可以在一个 非 Activity 中去感知他的生命周期

方法一:通过在 Activity 中嵌入一个隐藏的 Fragment 来进行感知,或者通过手写监听器的方式来感知,例如:

通过手写监听器的方式来对 Activity 的生命周期进行感知:
Android 基础_第89张图片
可以看到,当 Activity 的生命周期发生变化的时候,就会主动去通知 MyObserver,但是需要我们在 Activity 中编写很多额外的逻辑,而 Lifecycles 组件的出现就是为了解决这个问题的,他可以让任何一个类都能轻松的感知到 Activity 的生命周期,同时又不需要在 Activity 中编写大量的逻辑处理

案例演示:

class MainActivity : AppCompatActivity() {
    lateinit var observer: MyObserver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //每个 Activity 本身就是一个 lifeCycleOwner
        lifecycle.addObserver(MyObserver())
    }
}

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")
    }
}

功能和上面一样,MyObserver 可以感知到 Activity 的生命周期发生了变化,但是却不能主动获知当前的生命周期状态,解决办法如下:
Android 基础_第90张图片

之后,在任何地方调用 lifecycle.currentState 来主动获知当前的生命周期状态,生命状态一共有 5 种,和 Activity 的生命周期回调对应的关系如下图

LiveData

LiveData 是 Jetpack 提供的一种响应式编程组件,他可以包含任意类型的数据,并在数据发生变化的时候通知给观察者,适合结合 ViewModel 来使用

LiveData 的基本用法

比如将前面的计数器中的 counter 使用 LiveData 包装起来,然后再 Activity 中去观察他,就可以主动的将数据变化通知给 Activity 了

ViewPager 全面剖析及使用详解

写得好的链接:https://www.runoob.com/w3cnote/android-tutorial-viewpager.html

  • 这个类可以让用户左右切换当前的 View

  • 继承了 ViewGroup 类,所以是一个容器类,可以在其中添加其他 View 类

  • 需要 PageAdapter 提供数据

  • 简介中提到了PagerAdapter,PagerAdapter 是一个基类适配器,我们经常用它来实现 app 引导图,它的子类有 FragmentPagerAdapterFragmentStatePagerAdapter

    • FragmentPageAdapter:和PagerAdapter一样,只会缓存当前的Fragment以及左边一个,右边 一个,即总共会缓存3个Fragment而已

    • FragmentStatePagerAdapter:当Fragment对用户不 见得时,整个 Fragment 会被销毁, 只会保存Fragment 的状态!而在页面需要重新显示的时候,会生成新的页面!

  • FragmentPagerAdapter适用于页面比较少的情况,FragmentStatePagerAdapter适用于页面比较多的情况,因此不同的场合选择合适的适配器才是正确的做法
    源码中我们可以看出FragmentStatePagerAdapter中fragment实例在destroyItem的时候被真正释放,所以FragmentStatePagerAdapter省内存。FragmentPagerAdapter中的fragment实例在destroyItem的时候并没有真正释放fragment对象只是detach

PagerAdapter的使用

ViewPager的翻页动画

  • ViewPager提供了PageTransformer接口用于实现翻页动画
  • 实现翻页动画的关键就是重写transformPage方法,方法里有两个参数view和position
  • 我们可以通过 setPageTransformer() 方法为我们的ViewPager 设置切换时的动画效果
  • Google官方是建议我们使用 Fragment 来填充 ViewPager 的

实现翻页动画的关键就是重写transformPage方法,方法里有两个参数view和position,理解这两个参数非常重要。假设有三个页面view1,view2,view3从左至右在viewPager中显示

往左滑动时:view1,view2,view3的position都是不断变小的。
view1的position: 0 → -1 → 负无穷大
view2的position: 1 → 0 → -1
view3的position: 1 → 0
往右滑动时:view1,view2,view3的position都是不断变大的。
view1的position: -1 → 0
view2的position: -1 → 0 → 1
view3的position: 0 → 1→ 正无穷大
当position是正负无穷大时view就离开屏幕视野了。因此最核心的控制逻辑是在[-1,0]和(0,1]这两个区间,通过设置透明度,平移,旋转,缩放等动画组合可以实现各式各样的页面变化效果。

案例演示:
https://www.runoob.com/w3cnote/android-tutorial-viewpager.html

代码地址:

Merge

merge必须放在布局文件的根节点上。
merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。
merge标签被添加到A容器下,那么merge下的所有视图将被添加到A容器下。
因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。
因为merge不是View,所以对merge标签设置的所有属性都是无效的。

SparseArray 的使用及实现原理

https://juejin.cn/post/6844903442901630983

你可能感兴趣的:(Android 基础)