## apply零散内容
activity -> viewModel -> repository
### 常见的依赖
```groovy
id 'kotlin-android-extensions'
// https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview
runtime group: 'androidx.recyclerview', name: 'recyclerview', version: '1.2.0-beta01'
```
### companion object
companion object 修饰为伴生对象,伴生对象在类中只能存在一个,类似于java中的静态方法 Java 中使用类访问静态成员,静态方法
### 自定义插件
```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="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_gravity="center_vertical"
android:layout_margin="10dp"
android:background="@color/cardview_dark_background"
android:text="back"
android:textColor="#fff"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:layout_gravity="center_vertical"
android:text="Title"
android:textSize="25sp"
android:textStyle="bold" />
<Button
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="@color/cardview_dark_background"
android:text="Edit"
android:textColor="#fff"/>
</LinearLayout>
</LinearLayout>
```
```xml
<include layout="@layout/title"/>
--------------------------
supportActionBar?.hide() // 隐藏默认actionbar
```
```kotlin
package com.example.learnandroid
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import kotlinx.android.synthetic.main.title.view.*
class TitleLayout(context: Context,attrs:AttributeSet) :LinearLayout(context,attrs){
init {
var view: View = LayoutInflater.from(context).inflate(R.layout.title,this)
backBtn.setOnClickListener {
(context as Activity).finish()
}
editBtn.setOnClickListener {
Toast.makeText(context,"edit",Toast.LENGTH_SHORT).show()
}
}
}
----------------------------
// 在layout引入自己自定义的布局
<com.example.learnandroid.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.learnandroid.TitleLayout>
```
### fragmentDemo
```kotlin
// MainActivity.kt 文件
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 点击按钮切换fragment
myBtn.setOnClickListener {
replaceFragment(AnotherRightFragment())
}
// 默认使用RightFragment 这个布局
replaceFragment(RightFragment())
}
// 自己是实现的转换fragment函数 分为5步
// 1 创建待添加的fragment的实例 2获取fragmentManager 3 开启一个事务
// 4向容器中添加或者替换fragment 5提交事务
private fun replaceFragment(fragment: Fragment){
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.rightLayout, fragment)
transaction.addToBackStack(null) // 返回栈 如果按返回将不会直接退出 返回上一个fragment
transaction.commit()
}
}
//---------------------------------------------------------------------------------------------
// LeftFragment.kt 类
class LeftFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 加载 left_fragment 这个布局文件
return inflater.inflate(R.layout.left_fragment, container, false)
}
}
//---------------------------------------------------------------------------------------------
// RightFragment.kt 类
class RightFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 加载 right_fragment 这个布局文件
return inflater.inflate(R.layout.right_fragment, container, false)
}
}
// anotherRightFragment同理
```
```xml
<!--activity_main.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/leftFrag"
android:name="haonan.tech.fragmentlearn.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<!-- <fragment-->
<!-- android:id="@+id/rightFrag"-->
<!-- android:name="haonan.tech.fragmentlearn.RightFragment"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="match_parent"-->
<!-- android:layout_weight="1"/>-->
<!--上边的是写死的 但是下边的可以进行变换-->
<FrameLayout
android:id="@+id/rightLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
<!--##############################################################################################-->
<!--left_fragment.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"
android:orientation="vertical">
<Button
android:id="@+id/myBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="button">
</Button>
</LinearLayout>
<!--##############################################################################################-->
<!--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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="24sp"
android:text="i am rightFragment">
</TextView>
</LinearLayout>
<!--another_right_fragment同理-->
```
## 安卓项目简介
### 目录结构
```shell
F:.
│ .gitignore # git忽略文件
│ build.gradle # 项目全局的gradle构建脚本
│ gradle.properties # 项目全局的gradle配置文件,在这里配置的属性会影响到项目中以的所有的gradle编译脚本
│ gradlew # linux命令行执行gradle命令的脚本
│ gradlew.bat # windows执行gradle命令的脚本
│ local.properties # 用于指定本机sdk得路径 自动生成
│ settings.gradle # 用于指定项目中所有引入的模块 新建项目 汇总也就引入了 app这个项目
│
├─.gradle # Android studio 自动生成的文件夹 不需要手动更改
│ ├─6.5
│ │ │ gc.properties
│ │ │
│ │ ├─executionHistory
│ │ │ executionHistory.lock
│ │ │
│ │ ├─fileChanges
│ │ │ last-build.bin
│ │ │
│ │ ├─fileHashes
│ │ │ fileHashes.bin
│ │ │ fileHashes.lock
│ │ │
│ │ └─vcsMetadata-1
│ ├─buildOutputCleanup
│ │ buildOutputCleanup.lock
│ │ cache.properties
│ │
│ ├─checksums
│ │ checksums.lock
│ │
│ └─vcs-1
│ gc.properties
│
├─.idea # Android studio 自动生成的文件夹 不需要手动更改
│ │ .gitignore
│ │ compiler.xml
│ │ gradle.xml
│ │ jarRepositories.xml
│ │ misc.xml
│ │ modules.xml
│ │ workspace.xml
│ │
│ ├─caches
│ │ build_file_checksums.ser
│ │
│ ├─libraries
│ │ Gradle__androidx_activity_activity_1_0_0_aar.xml
│ │ Gradle__androidx_annotation_annotation_1_1_0.xml
│ │ Gradle__androidx_annotation_annotation_experimental_1_0_0_aar.xml
│ │ Gradle__androidx_appcompat_appcompat_1_2_0_aar.xml
│ │ Gradle__androidx_appcompat_appcompat_resources_1_2_0_aar.xml
│ │ Gradle__androidx_arch_core_core_common_2_1_0.xml
│ │ Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml
│ │ Gradle__androidx_cardview_cardview_1_0_0_aar.xml
│ │ Gradle__androidx_collection_collection_1_1_0.xml
│ │ Gradle__androidx_coordinatorlayout_coordinatorlayout_1_1_0_aar.xml
│ │ Gradle__androidx_core_core_1_3_2_aar.xml
│ │ Gradle__androidx_core_core_ktx_1_3_2_aar.xml
│ │ Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml
│ │ Gradle__androidx_customview_customview_1_0_0_aar.xml
│ │ Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml
│ │ Gradle__androidx_fragment_fragment_1_1_0_aar.xml
│ │ Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml
│ │ Gradle__androidx_lifecycle_lifecycle_common_2_1_0.xml
│ │ Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml
│ │ Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml
│ │ Gradle__androidx_lifecycle_lifecycle_runtime_2_1_0_aar.xml
│ │ Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml
│ │ Gradle__androidx_loader_loader_1_0_0_aar.xml
│ │ Gradle__androidx_recyclerview_recyclerview_1_1_0_aar.xml
│ │ Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml
│ │ Gradle__androidx_test_core_1_3_0_aar.xml
│ │ Gradle__androidx_test_espresso_espresso_core_3_3_0_aar.xml
│ │ Gradle__androidx_test_espresso_espresso_idling_resource_3_3_0_aar.xml
│ │ Gradle__androidx_test_ext_junit_1_1_2_aar.xml
│ │ Gradle__androidx_test_monitor_1_3_0_aar.xml
│ │ Gradle__androidx_test_runner_1_3_0_aar.xml
│ │ Gradle__androidx_transition_transition_1_2_0_aar.xml
│ │ Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml
│ │ Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml
│ │ Gradle__androidx_versionedparcelable_versionedparcelable_1_1_0_aar.xml
│ │ Gradle__androidx_viewpager2_viewpager2_1_0_0_aar.xml
│ │ Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml
│ │ Gradle__com_google_android_material_material_1_2_1_aar.xml
│ │ Gradle__com_google_code_findbugs_jsr305_2_0_1.xml
│ │ Gradle__com_squareup_javawriter_2_1_1.xml
│ │ Gradle__javax_inject_javax_inject_1.xml
│ │ Gradle__junit_junit_4_12.xml
│ │ Gradle__junit_junit_4_13_1.xml
│ │ Gradle__org_hamcrest_hamcrest_core_1_3.xml
│ │ Gradle__org_hamcrest_hamcrest_integration_1_3.xml
│ │ Gradle__org_hamcrest_hamcrest_library_1_3.xml
│ │ Gradle__org_jetbrains_annotations_13_0.xml
│ │ Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_4_10.xml
│ │ Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_4_10.xml
│ │
│ └─modules
│ │ LearnActiivity.iml
│ │
│ └─app
│ LearnActiivity.app.iml
│
├─app # 项目的代码、资源等内容都是放置在这个目录下的
│ │ .gitignore #与外层的一样 将app模块指定的目录或者文件排除在版本控制之外
│ │ build.gradle # app模块的gradle构建脚本
│ │ proguard-rules.pro # 指定项目代码的混淆规则 如果不希望自己的代码被别人破解 可以进行混淆
│ │ build # 包含编译时自动生成的文件 不需要过多关心
│ │
│ ├─libs # 如果项目使用了第三方jar包 就需要把这些jar包放到libs目录下,这些jar包会自动添加到项目的构建路径中
│ └─src
│ ├─androidTest # 编写测试用例的,可以对项目进行一些自动化测试
│ │ └─java
│ │ └─com
│ │ └─example
│ │ └─learnactiivity
│ │ ExampleInstrumentedTest.kt
│ │
│ ├─main
│ │ │ AndroidManifest.xml
│ │ │
│ │ ├─java
│ │ │ └─com
│ │ │ └─example
│ │ │ └─learnactiivity
│ │ │ FirstActivity.kt #activity 文件
│ │ │
│ │ └─res # 图片、布局、字符串等资源存放地址
│ │ ├─drawable #图片
│ │ │ ic_launcher_background.xml
│ │ │
│ │ ├─drawable-v24
│ │ │ ic_launcher_foreground.xml
│ │ │
│ │ ├─layout # layout
│ │ │ first_layout.xml
│ │ │
│ │ ├─mipmap-anydpi-v26 # mipmap 开头的都是放应用图标的 分辨率不同
│ │ │ ic_launcher.xml
│ │ │ ic_launcher_round.xml
│ │ │
│ │ ├─mipmap-hdpi
│ │ │ ic_launcher.png
│ │ │ ic_launcher_round.png
│ │ │
│ │ ├─mipmap-mdpi
│ │ │ ic_launcher.png
│ │ │ ic_launcher_round.png
│ │ │
│ │ ├─mipmap-xhdpi
│ │ │ ic_launcher.png
│ │ │ ic_launcher_round.png
│ │ │
│ │ ├─mipmap-xxhdpi
│ │ │ ic_launcher.png
│ │ │ ic_launcher_round.png
│ │ │
│ │ ├─mipmap-xxxhdpi
│ │ │ ic_launcher.png
│ │ │ ic_launcher_round.png
│ │ │
│ │ ├─values # 字符串
│ │ │ colors.xml
│ │ │ strings.xml
│ │ │ themes.xml
│ │ │
│ │ └─values-night
│ │ themes.xml
│ │
│ └─test # unit Test 测试使用
│ └─java
│ └─com
│ └─example
│ └─learnactiivity
│ ExampleUnitTest.kt
├─build
│ └─kotlin
│ └─sessions
└─gradle
└─wrapper
gradle-wrapper.jar
gradle-wrapper.properties
```
### 一些典型文件的内容解析
#### 外层的build.gradle文件
```java
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.4.10"
repositories { //对应了相应的代码仓库
google() //google自家的仓库
jcenter() //第三方的开源库
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.1"//gradle以插件形式存在的,因为gradle支持多种语言
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" //使用kotlin语言
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
```
#### app(内层)的build.gradle文件
```java
plugins {
id 'com.android.application' // 表示一个应用程序模块 com.android.application表示库文件 前者独立运行
id 'kotlin-android' // 要想使用kotlin来开发 这个插件必备
id 'kotlin-android-extensions' // 提供一些非常好用的kotlin扩展功能
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.example.learnactiivity" // 每一个应用的唯一标识符
minSdkVersion 16 // 最低兼容的android系统版本 16代表 Android4.1.2
targetSdkVersion 30 // 表示在这个android版本上做了充分的测试,系统将会开启一些新功能
// 比如安卓6.0之后版本号是23 系统就会开启运行时权限功能 指定小于23的版本不会开启这个功能
versionCode 1 // 指定当前项目的版本号
versionName "1.0" // 指定当前项目版本的项目名
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // 表示启用junit单元测试
}
buildTypes { // 指定生成安装文件的相关配置 通常含有两个内容 一个是debug(默认省略) 一个是release
release {
minifyEnabled false // 是否开启代码混淆
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' //指定混淆时使用的规则文件
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies { // 通常依赖就有三种 本地依赖 库依赖和远程依赖
// implementation fileTree是本地依赖声明
// implementation 远程依赖声明 可以对jcenter的开源项目添加依赖关系
// 远程依赖声明由3部分组成 例如下边的spring 总结来说是 xx公司开发的yy项目版本号是zz
// compile group: 'org.springframework', name: 'spring-context', version: '5.3.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
testImplementation 'junit:junit:4.+' //
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
```
API等级与Android版本对应列表如下:
| API等级 | Android版本号 |
| :------ | :----------------------- |
| 14 | Android4.0 |
| 15 | Android4.0.3 |
| 16 | Android4.1.2 |
| 17 | Android4.2.2 |
| 18 | Android4.3.1 |
| 19 | Android4.4.2 |
| 20 | Android4.4W.2 |
| 21 | Android5.0.1 |
| 22 | Android5.1 |
| 24 | Android7.0 |
| 25 | Android7.1.1 |
| 26 | Android8.0 |
| 27 | Android8.1 |
| 28 | Android9.0 |
| 29 | Android10.0(Android Q) |
#### AndroidManifest.xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.learnactiivity">
<application
android:allowBackup="true" //
android:icon="@mipmap/ic_launcher" //应用的矩形图标
android:label="@string/app_name" // app的名字
android:roundIcon="@mipmap/ic_launcher_round" //应用的圆形图标
android:supportsRtl="true" //声明你的application是否愿意支持从右到左(right-to-left)的布局。
android:theme="@style/Theme.LearnActiivity">
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
```
### 日志工具的使用
#### 5个方法的介绍
安卓的工具类是**Log(android.util.Log)** ,这个类提供了如下5个方法来供我们打印日志
| 方法 | 含义 |
| ------- | ------------------------------------------------------------ |
| Log.v() | 这个方法用于打印那些最为琐碎的,意义最小的日志信息。对应级别 verbose,是Android 日志里面级别最低的一种 |
| Log.d() | 这个方法用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别 debug,比 verbose高一级。 |
| Log.i() | 这个方法用于打印一些比较重要的数据,这些数据应该是你非常想看到的,可以帮你分析用户行为的那种。对应级别 info,比 debug高一级。 |
| Log.w() | 这个方法用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别 warn,比 info高一级。 |
| Log.e() | 这个方法用于打印程序中的错误信息,比如程序进入到了 catch 语句当中。当有错误信息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级别 error,比 warn高一级。 |
#### 简单使用
在MainActivity中添加一句话
```kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("mainActivity","hello,world") // logcat里边选中com.example.myapplication 选中debug日志
// 然后 输入 mainActivity 就能找到打印的日志
//2020-11-29 22:29:51.800 24256-24256/com.example.myapplication D/mainActivity: hello,world
}
}
```
## 第一个android项目
新建一个no activity的安卓项目
## 系统知识
### 像素
- **px : 其实就是像素单位,比如我们通常说的手机分辨列表800\*400都是px的单位**
- **dp : 虚拟像素,在不同的像素密度的设备上会自动适配**
- **sp : 同dp相似,还会根据用户的字体大小偏好来缩放**
- **dip: 同dp**
pixel,即像素,1px代表屏幕上的一个物理的像素点。但px单位不被建议使用。因为同样像素大小的图片在不同手机显示的实际大小可能不同。要用到px的情况是需要画1像素表格线或阴影线的时候,如果用其他单位画则会显得模糊。
而dp也叫dip,是device independent pixels。设备不依赖像素的一个单位。在不同的像素密度的设备上会自动适配,比如:
在320x480分辨率,像素密度为160,1dp=1px
在480x800分辨率,像素密度为240,1dp=1.5px
计算公式:
pixs =dips \* (densityDpi/160).
dips=(pixs\*160)/densityDpi
**sp作为字体大小单位,会随着系统的字体大小改变,而dp作为单位则不会。所以建议在字体大小的数值要使用sp作为单位**
### layout初始化布局
```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/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/button" />
</LinearLayout>
```
### AndroidManifest.xml文件解析
```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.learnandroid">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LearnAndroid">
<activity android:name=".MainActivity">
<intent-filter>
<!--action的内容如果相同 那么跳转的时候会让用户选择-->
<action android:name="android.intent.action.MAIN" />
<!--如果两个 activity 的 category的值都是android.intent.category.LAUNCHER
那么桌面上会多一个名字一样的程序-->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".secondActivity">
<intent-filter>
<action android:name="action.YHNACTIVIT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ThirdActivity"></activity>
</application>
</manifest>
```
### intent
#### intent显式跳转(三种方法)
```kotlin
// 显式启动 写法一 class 跳转
val intent1 = Intent(this, secondActivity::class.java )
startActivity(intent1)
// 显式启动 写法二 包名 跳转
val intent2 = Intent()
intent2.setClassName(this,"com.example.learnandroid.secondActivity" )
startActivity(intent2)
// 显式启动 写法三 Componentname
val intent3 = Intent()
val cname = ComponentName(this,secondActivity::class.java)
intent3.component=cname
startActivity(intent3)
```
#### indent的隐式跳转
```kotlin
//AndroidManifest.xml文件
<activity android:name=".secondActivity" >
<intent-filter>
<action android:name="action.YHNACTIVIT"/> // 注意action.YHNACTIVIT
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
--------------------------------------------------------------------------------------------------
// mainActivity.kt 文件
//隐式启动 第一种方式
val intent1 = Intent()
intent1.action= "action.YHNACTIVIT"
startActivity(intent1)
// 隐式启动 第二种方式
val intent2 = Intent("action.YHNACTIVIT")
startActivity(intent2)
```
### activity的生命周期
![1467278-3a28d45b96ce5745.png (664×705) (jianshu.io)](https://upload-images.jianshu.io/upload_images/1467278-3a28d45b96ce5745.png?imageMogr2/auto-orient/strip|imageView2/2/w/664/format/webp)
(1)onCreate:create表示创建,这是Activity生命周期的第一个方法,也是我们在android开发中接触的最多的生命周期方法。它本身的作用是进行Activity的一些初始化工作,比如使用setContentView加载布局,对一些控件和变量进行初始化等。但也有很多人将很多与初始化无关的代码放在这,其实这是不规范的。此时**Activity还在后台,不可见**。所以动画不应该在这里初始化,因为看不到……,可以做加载布局,绑定事件。
(2)onStart:start表示启动,activity从不可见变成可见。这是Activity生命周期的第二个方法。此时Activity已经**可见**了,但是还没出现在前台,我们还看不到,无法与Activity交互。其实将Activity的初始化工作放在这也没有什么问题,放在onCreate中是由于官方推荐的以及我们开发的习惯。
(3)onResume:resume表示继续、重新开始,这名字和它的职责也相同。此时Activity经过前两个阶段的初始化已经蓄势待发。Activity在这个阶段已经出现在前台并且**可见**了。这个阶段可以打开独占设备,这个方法在activity准备好喝用户进行交互时调用,此时的activity一定位于`返回栈的栈顶`,并且处于`运行状态`。
(4)onPause:pause表示暂停,当Activity要跳到另一个Activity或应用正常退出时都会执行这个方法。此时Activity在前台并**可见**,我们可以进行一些轻量级的存储数据和去初始化的工作,不能太耗时,通常会将一些`消耗cpu的资源释放掉`,以及`保存一些关键数据`,因为在跳转Activity时只有当一个Activity执行完了onPause方法后另一个Activity才会启动,而且android中指定如果onPause在500ms即0.5秒内没有执行完毕的话就会强制关闭Activity。从生命周期图中发现可以在这快速重启,但这种情况其实很罕见,比如用户切到下一个Activity的途中按back键快速得切回来。
(5)onStop:stop表示停止,此时Activity已经**不可见**了,但是Activity对象还在内存中,没有被销毁。这个阶段的主要工作也是做一些资源的回收工作。它和onPause的主要区别在于,如果启动的新的activity是一个对话框式的activity,那么onPause方法会得到执行,而onstop方法不会
(6)onDestroy:destroy表示毁灭,这个阶段Activity被销毁,**不可见**,我们可以将还没释放的资源释放,以及进行一些回收工作。
(7)onRestart:restart表示重新开始,Activity在这时**可见**,当用户按Home键切换到桌面后又切回来或者从后一个Activity切回前一个Activity就会触发这个方法,也就是重新启动activity会触发的操作。这里一般不做什么操作。
#### 1.onCreate和onStart之间有什么区别?
(1)可见与不可见的区别。前者不可见,后者可见。
(2)执行次数的区别。onCreate方法只在Activity创建时执行一次,而onStart方法在Activity的切换以及按Home键返回桌面再切回应用的过程中被多次调用。因此Bundle数据的恢复在onStart中进行比onCreate中执行更合适。
(3)onCreate能做的事onStart其实都能做,但是onstart能做的事onCreate却未必适合做。如前文所说的,setContentView和资源初始化在两者都能做,然而想动画的初始化在onStart中做比较好。
#### 2.onStart方法和onResume方法有什么区别?
(1)是否在前台。onStart方法中Activity可见但不在前台,不可交互,而在onResume中在前台。
(2)职责不同,onStart方法中主要还是进行初始化工作,而onResume方法,根据官方的建议,可以做开启动画和独占设备的操作。
#### 3.onPause方法和onStop方法有什么区别?
(1)是否可见。onPause时Activity可见,onStop时Activity不可见,但Activity对象还在内存中。
(2)在系统内存不足的时候可能不会执行onStop方法,因此程序状态的保存、独占设备和动画的关闭、以及一些数据的保存最好在onPause中进行,但要注意不能太耗时。
#### 4.onStop方法和onDestroy方法有什么区别?
onStop阶段Activity还没有被销毁,对象还在内存中,此时可以通过切换Activity再次回到该Activity,而onDestroy阶段Acivity被销毁
#### 5.为什么切换Activity时各方法的执行次序是(A)onPause→(B)onCreate→(B)onStart→(B)onResume→(A)onStop而不是(A)onPause→(A)onStop→(B)onCreate→(B)onStart→(B)onResume
(1)一个Activity或多或少会占有系统资源,而在官方的建议中,onPause方法将会释放掉很多系统资源,为切换Activity提供流畅性的保障,而不需要再等多两个阶段,这样做切换更快。
(2)按照生命周期图的表示,如果用户在切换Activity的过程中再次切回原Activity,是在onPause方法后直接调用onResume方法的,这样比onPause→onStop→onRestart→onStart→onResume要快得多。
#### 7.与生命周期密切相关的onSaveInstanceState方法和onRestoreInstanceState方法在什么时候执行?
通过阅读源码会发现,当targetSdkVersion小于3时onSaveInstanceState是在onPause方法中调用的,而大于3时是在onStop方法中调用的。
而onRestoreInstanceState是在onStart之后、onResume之前调用的。
### fragment 的生命周期
![fragment 的生命周期](https://upload-images.jianshu.io/upload_images/1688279-0424d62f50035b43.png?imageMogr2/auto-orient/strip|imageView2/2/w/317/format/webp)
```kotlin
class RightFragment : Fragment() {
companion object {
const val TAG = "RightFragment"
}
override fun onAttach(context: Context) {
super.onAttach(context)
Log.e(TAG, "onAttach")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e(TAG, "onCreate")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.e(TAG, "onCreateView")
return inflater.inflate(R.layout.right_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
Log.e(TAG, "onActivityCreated")
super.onActivityCreated(savedInstanceState)
}
override fun onStart() {
Log.e(TAG, "onStart")
super.onStart()
}
override fun onResume() {
Log.e(TAG, "onResume")
super.onResume()
}
override fun onPause() {
Log.e(TAG, "onPause")
super.onPause()
}
override fun onStop() {
Log.e(TAG, "onStop")
super.onStop()
}
override fun onDestroyView() {
Log.e(TAG, "onDestroyView")
super.onDestroyView()
}
override fun onDestroy() {
Log.e(TAG, "onDestroy")
super.onDestroy()
}
override fun onDetach() {
Log.e(TAG, "onDetach")
super.onDetach()
}
// 这个函数可以保存数据 因为销毁的fragment会在内存不足的时候被清除
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
}
}
/*创建fragment后打印
2020-12-16 21:48:12.366 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onAttach
2020-12-16 21:48:12.366 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onCreate
2020-12-16 21:48:12.367 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onCreateView
2020-12-16 21:48:12.369 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onActivityCreated
2020-12-16 21:48:12.369 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onStart
2020-12-16 21:48:12.371 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onResume
-----------------------------------------------------------------------------------------------------
切换fragment后打印
2020-12-16 21:52:07.128 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onPause
2020-12-16 21:52:07.128 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onStop
2020-12-16 21:52:07.128 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onDestroyView
----------------------------------------------------------------------------------------------------
点击返回 或者是切换回来的时候打印
2020-12-16 21:53:09.251 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onCreateView
2020-12-16 21:53:09.255 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onActivityCreated
2020-12-16 21:53:09.255 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onStart
2020-12-16 21:53:09.255 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onResume
---------------------------------------------------------------------------------------------------
再次返回 打印
2020-12-16 21:54:29.404 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onPause
2020-12-16 21:54:29.404 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onStop
2020-12-16 21:54:29.404 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onDestroyView
2020-12-16 21:54:29.405 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onDestroy
2020-12-16 21:54:29.406 27890-27890/haonan.tech.fragmentlearn E/RightFragment: onDetach
*/
```
### Android 常用限定符
| 屏幕特性 | 限定符 | 描述 |
| :--------- | :------ | :----------------------------------------------------------- |
| 屏幕尺寸 | small | 小屏幕 |
| | normal | 基准屏幕 |
| | large | 大屏幕 |
| | xlarge | 超大屏幕 |
| 屏幕密度 | ldpi | <=120dpi |
| | mdpi | <= 160dpi |
| | hdpi | <= 240dpi |
| | xhdpi | <= 320dpi |
| | xxhdpi | <= 480dpi |
| | xxhdpi | <= 640dpi(只用来存放icon) |
| | nodpi | 与屏幕密度无关的资源.系统不会针对屏幕密度对其中资源进行压缩或者拉伸 |
| | tvdpi | 介于mdpi与hdpi之间,特定针对213dpi,专门为电视准备的,手机应用开发不需要关心这个密度值. |
| 屏幕方向 | land | 横向 |
| | port | 纵向 |
| 屏幕宽高比 | long | 比标准屏幕宽高比明显的高或者宽的这样屏幕 |
| | notlong | 和标准屏幕配置一样的屏幕宽高比 |
````java
res/layout/my_layout.xml // layout for normal screen size ("default")
res/layout-large/my_layout.xml // layout for large screen size
res/layout-xlarge/my_layout.xml // layout for extra-large screen size
res/layout-xlarge-land/my_layout.xml // layout for extra-large in landscape orientation
res/drawable-mdpi/graphic.png // bitmap for medium-density
res/drawable-hdpi/graphic.png // bitmap for high-density
res/drawable-xhdpi/graphic.png // bitmap for extra-high-density
res/drawable-xxhdpi/graphic.png // bitmap for extra-extra-high-density
res/mipmap-mdpi/my_icon.png // launcher icon for medium-density
res/mipmap-hdpi/my_icon.png // launcher icon for high-density
res/mipmap-xhdpi/my_icon.png // launcher icon for extra-high-density
res/mipmap-xxhdpi/my_icon.png // launcher icon for extra-extra-high-density
res/mipmap-xxxhdpi/my_icon.png // launcher icon for extra-extra-extra-high-density
````
在一般情况下在layout文件夹下是默认的布局文件,而在大屏幕设备上,这个布局文件可能就不怎么适用了。
因此,我们可以利用限定符large,在res目录下新建一个layout_large文件夹,再新建一个同名的布局文件。
这样,当程序运行在了大屏幕设备上时,android就会自动地加载layout_large文件夹下的布局文件,其他情况下还是会加载layout文件下的布局文件。
这样,当程序运行在不同屏幕大小的手机上时都可以有比较完美的布局了。
有时候我们也不希望由系统来为我们来判断加载哪个布局,而是希望能够为之设定一个临界值,当屏幕宽度小于临界值时,加载默认布局,当大于时则加载另一个专门准备的布局文件。
可以在res目录下新建一个文件夹,命名为layout_ sw650dp,再将专门准备的同名布局文件放置在其中。
这样,当程序运行在屏幕大于650dp的设备上时,就会加载layout_ sw650dp文件夹下的布局文件了
| 屏幕特性 | 限定符 | 描述 |
| :------------- | :----------------------------------- | :----------------------------------------------------------- |
| 最小宽度限定符 | sw<N>dp 例如sw600dp, sw720dp | 屏幕的最小尺寸,就是屏幕可用区域的最小尺寸,是指屏幕可用高度或宽度的最小值(你可以默认是屏幕的最小宽度).你能用这个限定符确保,无论屏幕方向如何,这个限定符修饰下的布局需要的屏幕最小尺寸是Ndp. 例如,如果你的布局在运行时需要的最小屏幕宽度是600dp,则你可以利用这个限定符创建布局资源目录res/layout-sw600dp.只有当屏幕的最小宽度或最小高度是600dp时,系统才会使用这些布局文件或者资源文件.最小屏幕宽度是固定设备的特有屏幕尺寸,当屏幕方向发生变化时,设备的最小宽度值不变. 设备的最小宽度值要考虑屏幕的尺寸和系统UI.例如,如果在屏幕上有一些系统持久化UI元素,则系统的最小宽度值要比实现的屏幕尺寸小一些,因为这些系统的UI元素你的应用是无法使用到的. 当你使用之前的广义限定符是,你可以定义连续的一系列限定符.用最小宽度来决定广义屏幕尺寸是有意义的,是因为宽度是影响你UI设计的关键因素.UI在竖直方向上会经常滚动,但是在水平方向上往往是固定的布局.可见不论是适配手机或者平板,宽度往往都是布局的关键因素.因此,你需要关心你手机上的最小宽度值. |
| 屏幕可用宽度 | w<N>dp Examples: w720p w1024p | 指定资源使用时需要的最小宽度.当屏幕方向发生变化时,系统会调整这个值,使其始终为你UI显示的宽度. 这个属性经常被用来判断当前是否需要显示多屏布局,因为哪怕用户当前正在使用平板,你也可能不希望用户在平板竖屏时显示多个屏幕的布局样式.这时,你就可以使用这个限定符来标明你布局需要的最小宽度 |
| 屏幕可用高度 | h<N>dp Examples: h720dp h1024dp etc. | 标明资源使用时需要的最小高度.当屏幕发生旋转时,系统会自动选择当前大的一方作为高度值.大部分应用很少需要这个限定符,因此不做过多讲解 |
在不考虑屏幕方向只关注布局最小空间的前提下,使用sw<N>的方式更加简单.
例如:
```shell
res/layout/main_activity.xml #手机布局
res/layout-sw600dp/main_activity.xml #7寸平板布局
res/layout-sw720dp/main_activity.xml #10寸平板布局
```
dp = 屏幕像素宽度/(屏幕像素密度/160) 160是基准屏幕像素密度 这个用来计算以上的sw后面的数值
通用公式:
dp = px/(dpi/160)
px = dp*(dpi/160)
### 布局
#### 线性布局 (linearLayout)
##### layout_gravity属性
确定自身元素的位置**,**并且一次性可以设置多个属性值,用“|”隔开该属性常用取值如下
| 属性值 | 说明 |
| ----------------- | ------------------------------------ |
| right | 在父元素中放在右边(vertival有效) |
| left | 在父元素中放在左边(vertival有效) |
| top | 在父元素中放在顶部(horizontal有效) |
| bottom | 在父元素中放在底部(horizontal有效) |
| center_vertical | 该元素垂直居中(horizontal有效) |
| center_horizontal | 该元素水平居中(vertical有效) |
| center | 该元素居中显示 |
##### gravity属性
确定子元素的位置,并且一次性可以设置多个属性值,用“|”隔开
| 属性值 | 说明 |
| ----------------- | ------------------------------------ |
| right | 子元素放在布局右边(vertival有效) |
| left | 子元素放在布局左边(vertival有效) |
| top | 子元素放在布局顶部(horizontal有效) |
| bottom | 子元素放在布局底部(horizontal有效) |
| center_vertical | 子元素垂直居中(horizontal有效) |
| center_horizontal | 子元素水平居中(vertical有效) |
| center | 子元素居中显示 |
##### example0
```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<!--该元素放在父元素的右边,其中文字(相当于子元素)放在中间-->
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:layout_gravity="right"
android:gravity="center"
android:text="这是文字1!"
android:textSize="25sp"/>
<!--该元素放在父元素的左边,其中文字(相当于子元素)放在顶部-->
<TextView
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="10dp"
android:layout_gravity="left"
android:gravity="bottom"
android:text="这是文字2"
android:textSize="15sp"/>
<!--该元素放在父元素的水平中间,其中文字(相当于子元素)放在底部并且中间-->
<TextView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="10dp"
android:layout_gravity="center_horizontal"
android:gravity="bottom|center"
android:text="这是文字3"
android:textSize="20sp"/>
</LinearLayout>
```
##### example1
```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--注意这个线性布局默认使用水平布局 最重要的属性是layout_weight属性
第一个button 占总份数的8份
第二个占 1份
-->
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="8"
android:hint="Input a string">
</EditText>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="button1">
</Button>
</LinearLayout>
```
##### example2
![example2](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/Android%E5%AD%A6%E4%B9%A0/AndroidLinearLayoutExample1.png)
```xml
<?xml version="1.0" encoding="utf-8"?>
<!--总布局
LinearLayout
- EditText 没填意思是最小的空间占用
- EditText
- EditText 20
- LinearLayout
- button1 1
- button2 1
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="To:"
android:inputType="text"
android:singleLine="true">
</EditText>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="subject:">
</EditText>
<EditText
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="20"
android:hint="Message:"
android:gravity="start"
>
</EditText>
<!--下边这个layout是水平分布 两个按钮分别占一份 也就是每人50width 注意第一个设置了marginright-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="10dp"
android:text="reset">
</Button>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="send">
</Button>
</LinearLayout>
</LinearLayout>
```
#### 相对布局(relativeLayout)
##### **相对于父元素给控件布局**
android:layout_centerHrizontal 水平居中
android:layout_centerVertical 垂直居中
android:layout_centerInparent 相对于父元素完全居中
android:layout_alignParentBottom 位于父元素的下边缘
android:layout_alignParentLeft 位于父元素的左边缘
android:layout_alignParentRight 位于父元素的右边缘
android:layout_alignParentTop 位于父元素的上边缘
android:layout_alignWithParentIfMissing 如果对应的兄弟元素找不到的话就以父元素做参照物
##### **属性值必须为id的引用名“@id/id-name”**
android:layout_below 位于元素的下方
android:layout_above 位于元素的的上方
android:layout_toLeftOf 位于元素的左边
android:layout_toRightOf 位于元素的右边
android:layout_alignTop 该元素的上边缘和某元素的的上边缘对齐
android:layout_alignLeft 该元素的左边缘和某元素的的左边缘对齐
android:layout_alignBottom 该元素的下边缘和某元素的的下边缘对齐
android:layout_alignRight 该元素的右边缘和某元素的的右边缘对齐
##### **给属性赋予像素值**
android:layout_marginBottom 底边缘的距离
android:layout_marginLeft 左边缘的距离
android:layout_marginRight 右边缘的距离
android:layout_marginTop 上边缘的距离
##### example0
```xml
<?xml version="1.0" encoding="utf-8"?>
<!--布局类似下方所示
button1 button2
button6 button7
button5
button8 button9
button3 button4
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button1" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="button2" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button3"
android:layout_alignParentBottom="true"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button4"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
/>
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="button5" />
<!--下边4个是一组 4个元素都 相对于中心button5 进行布局 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button5"
android:layout_toLeftOf="@id/button5"
android:text="button6" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button5"
android:layout_toRightOf="@id/button5"
android:text="button7"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button5"
android:layout_toLeftOf="@id/button5"
android:text="button8" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button5"
android:layout_toRightOf="@id/button5"
android:text="button9" />
</RelativeLayout>
```
##### example1
```xml
<!--布局类似下方所示
input Text
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
|确定| |取消|
-->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Input Text"
>
</EditText>
<!--与文本框 右侧对齐-->
<Button
android:id="@+id/button_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"
android:layout_below="@id/edit_text"
android:layout_alignRight="@id/edit_text"
>
</Button>
<!--android:layout_below="@id/edit_text"
可以替换为
android:layout_alignTop="@id/button_cancel"-->
<Button
android:id="@+id/button_"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定"
android:layout_below="@id/edit_text"
android:layout_toLeftOf="@id/button_cancel">
</Button>
</RelativeLayout>
```
#### 帧布局(FrameLayout)
##### example0
```xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--后边的正方形纯色方块叠在前一个上边
注意 android:layout_gravity="center"-->
<TextView
android:layout_width="350dp"
android:layout_height="350dp"
android:background="#ff0000"
android:layout_gravity="center">
</TextView>
<TextView
android:layout_width="250dp"
android:layout_height="250dp"
android:background="#00ff00"
android:layout_gravity="center">
</TextView>
<TextView
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#0000ff"
android:layout_gravity="center">
</TextView>
</FrameLayout>
```
#### 百分比布局(PersentLayout) 已弃用
**'androidx.percentlayout.widget.PercentRelativeLayout' is deprecated**
##### 种类
```groovy
//增加一条
dependencies {
implementation 'androidx.percentlayout:percentlayout:1.0.0'
}
```
`PercentRelativeLayout`、`PercentFrameLayout`,通过名字就可以看出,这是继承自`FrameLayout`和`RelativeLayout`两个容器类;
##### PercentRelativeLayout
| 属性名 | 含义 |
| :--------------------------- | :------------------- |
| `layout_widthPercent` | 宽度比例 |
| `layout_heightPercent` | 高度比例 |
| `layout_marginPercent` | 外边距间隔百分比 |
| `layout_marginLeftPercent ` | 左外边距间隔百分比 |
| `layout_marginTopPercent` | 上外边距间隔百分比 |
| `layout_marginRightPercent ` | 右外边距间隔百分比 |
| `layout_marginBottomPercent` | 下外边距间隔百分比 |
| `layout_marginStartPercent` | 开头外边距间隔百分比 |
| `layout_marginEndPercent` | 结尾外边距间隔百分比 |
##### example0
![example0](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/Android%E5%AD%A6%E4%B9%A0/AndroidPercentRelativeLayoutExample0.png)
```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.percentlayout.widget.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_left_top"
app:layout_widthPercent = "30%"
app:layout_heightPercent = "13%"
android:background="#ff0000"
android:text="30%"
android:textSize="25sp"
android:textStyle="bold"
android:gravity="center">
</TextView>
<!--相对布局 让70%的左边缘 与30%的右边缘对上-->
<TextView
android:id="@+id/tv_right_top"
app:layout_widthPercent = "70%"
app:layout_heightPercent = "13%"
android:background="#0000ff"
android:layout_toRightOf="@id/tv_left_top"
android:text="70%"
android:textSize="25sp"
android:textStyle="bold"
android:gravity="center">
</TextView>
<TextView
android:id="@+id/tv_left_center"
app:layout_widthPercent = "70%"
app:layout_heightPercent = "13%"
app:layout_marginTopPercent = "5%"
android:background="#00ff00"
android:layout_below="@id/tv_left_top"
android:text="70%"
android:textSize="25sp"
android:textStyle="bold"
android:gravity="center">
</TextView>
<TextView
android:id="@+id/tv_left_bottom"
app:layout_widthPercent = "80%"
app:layout_heightPercent = "13%"
app:layout_marginTopPercent = "5%"
android:background="#00ffdd"
android:layout_below="@id/tv_left_center"
android:text="80%"
android:textSize="25sp"
android:textStyle="bold"
android:gravity="center">
</TextView>
</androidx.percentlayout.widget.PercentRelativeLayout>
```
##### PercentFrameLayout
#### 约束布局(constraintLayout)
#### RecycleView
##### RecycleViewActivity.kt
````kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import haonan.tech.experiment2.adapter.FruitAdapter
import haonan.tech.experiment2.entity.Fruit
import kotlinx.android.synthetic.main.activity_recycle_view.*
import java.lang.StringBuilder
class RecycleViewActivity : AppCompatActivity() {
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycle_view)
initFruits()
val layoutManager = StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL)
myRecycleView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
myRecycleView.adapter = adapter
}
private fun initFruits(){
repeat(2){
fruitList.add(Fruit("adafdfa",R.drawable.l1))
fruitList.add(Fruit(getRandomLengthString("banana"),R.drawable.l2))
fruitList.add(Fruit(getRandomLengthString("abcdasd"),R.drawable.l3))
fruitList.add(Fruit(getRandomLengthString("eareeeee"),R.drawable.l4))
fruitList.add(Fruit(getRandomLengthString("fffffff"),R.drawable.l5))
}
}
private fun getRandomLengthString(str:String):String{
val n = (1..20).random()
val builder = StringBuilder()
repeat(n){
builder.append(str)
}
return builder.toString()
}
}
````
##### FruitAdapter.kt
```kotlin
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.example.learnandroid.R
import haonan.tech.experiment2.entity.Fruit
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)
}
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)
// view的点击事件
viewHolder.itemView.setOnClickListener{
val position = viewHolder.absoluteAdapterPosition
val fruit = fruitList[position]
Toast.makeText(parent.context, "点击了view ${fruit.fruitName}",Toast.LENGTH_SHORT).show()
}
// 图片的点击事件
viewHolder.fruitImage.setOnClickListener{
val position = viewHolder.absoluteAdapterPosition
val fruit = fruitList[position]
Toast.makeText(parent.context, "点击了图片 ${fruit.fruitName}",Toast.LENGTH_SHORT).show()
}
return viewHolder
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
holder.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.fruitName
}
override fun getItemCount(): Int {
return fruitList.size
}
}
```
##### activity_recycle_view.xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RecycleViewActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/myRecycleView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
```
##### fruit_item.xml
```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_margin="5dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="150dp"
android:layout_height="200dp"
android:layout_gravity="center_vertical"
android:layout_marginTop="10dp">
</ImageView>
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_gravity="left"
android:textColor="@color/cardview_dark_background"
android:text="safdjsaljf">
</TextView>
</LinearLayout>
```
## kotlin
### 简要介绍
kotlin是jetbrain公司开发的第三方语言,Kotlin可以编译成`Java字节码`,也可以编译成`JavaScript`,方便在没有JVM的设备上运行。除此之外Kotlin还可以编译成二进制代码直接运行在机器上(例如嵌入式设备或 iOS)
原理
编程语言可以分为编译型语言和解释型语言,
java代码虽然是经过编译的,但是其编译结果是生成.class文件,并不是计算机可以直接识别的二进制文件,只有java的虚拟机可以识别(Android的虚拟机叫做ART),这个虚拟机其实就是承担了解释器的功能,他会在程序运行时将被编译后的class文件解释成计算机可以识别的二进制格式数据进行执行。所以说java属于解释型语言。
所以kotlin之所以能够全方位兼容java ,是因为他经过编译生成的.class 文件符合java的格式,相当于kotlin在java的基础上又进行了一步封装。
### **SuppressLint**
Lint是一个静态检查器,它围绕Android项目的正确性、安全性、性能、可用性以及可访问性进行分析。
它检查的对象包括XML资源、位图、ProGuard配置文件、源文件甚至编译后的字节码。
Lint包含了API版本检查、性能检查以及其他诸多特性。
可以使用**@SuppressLint**标注忽略指定的警告。
### kotlin的第一行代码
新建一个安卓项目,其中在`java.com.example`下面新建一个 **kotlin file/class ** ,文件的扩展名是.kt
```kotlin
fun main(){
val str = "world"
println("hello,$str") // 输出 hello,world
val a:String="10" // 指定变量的类型 如果没有那么自动推导
println(a)
}
```
### 基本内容
#### kotlin的变量
val (value的缩写): 用来声明一个不可变的变量,初始复制之后就不能改变了
var(variable的缩写): 声明一个可变的变量
#### 函数
```kotlin
// 格式
fun methodName(param1: param1Type , param2: param2Type): returnType{
}
// 例子
fun largerNumber(num1: Int,num2 : Int): Int{
return max(num1, num2)
}
```
##### 函数的语法糖
当一个函数中只有一行代码的时候,kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可。下边的例子中连return 都可以省略,等号足可以表达返回值的意思。
```kotlin
fun largerNumber(num1: Int,num2 : Int): Int = max(num1, num2)
```
if语句的语法糖
```kotlin
fun largerNumber(num1: Int,num2 : Int): Int {
var value : Int = 0;
if(num1 > num2){
value = num1
}
else
value = num2
return value
}
---------------------------------------------------
fun largerNumber(num1: Int,num2 : Int): Int {
var value : Int = if(num1 > num2)
num1
else
num2
return value
}
------------------------------------------------
fun largerNumber(num1: Int,num2 : Int): Int {
return if(num1 > num2)
num1
else
num2
}
-------------------------------------------------
fun largerNumber(num1: Int,num2 : Int): Int = if(num1 > num2) num1 else num2
```
#### when语法的使用
```kotlin
fun getScore(name:String) = if(name =="Tom"){
86
}else if (name == "Jim"){
77
}else {
0
}
-------------------------------------------------
fun getScore(name:String) = when(name) {
"Tom" -> 86
"Jim" -> 77
else -> 0
}
----------------------------------------------
不带参数的when
fun getScore(name:String) = when {
name.startsWith("wang")-> 9
name == "Tom" -> 86
name == "Jim" -> 77
else -> 0
}
-----------------------------------------------
fun getName(id:Int):String = when(id) {3->"asf" else ->"asdf"}
```
#### for循环
```kotlin
for(i in 0..10){
println(i)
}
var range = 0 until 10 // 左闭右开 [1,10)
for(i in 0 until 10 step 2){
println(i) // 输出 0 2 4 6 8
}
for(i in 10 downTo 1 step 2){
println(i) //输出 10 8 6 4 2
}
```
#### 类
kotlin 中的所有非抽象类类默认都是不支持被继承的。相当于给类加上了final声明。
##### 类的构造器
- **每个次构造函数都调用主构造函数**
- **次构造函数 ————> 次构造函数 ————> 主构造函数**
- **如果一个类没有主构造函数,次级构造函数就不必显示调用主构造函数**
```kotlin
class Parent(name: String) { // 主构造器 是关于name
var name= ""
var age = 0
var sex = "man"
init {// 主构造器执行的函数体
this.name = name
}
constructor(name: String, age: Int) : this("Main name 1") { // 次构造器1 必须直接或者间接地调用主构造器
this.age = age;
println("constructor 1 $name , $age , $sex")
}
constructor(nickName: String, sex: String) : this("Main name 2") { // 次构造器2 必须直接或者间接地调用主构造器
this.sex = sex;
println("constructor 2 $nickName , $age , $sex")
}
fun learn() {
println(" learn ")
}
}
fun main() {
Parent("lucy", "woman").learn() // 调用了 第二个次级构造器
```
##### 类的继承
###### 父类
```kotlin
open class Person(name:String,age:Int) { // open关键字是告诉kotlin编译器 Person 是专门为继承而设计的
var name = ""
var age = 0
init { // init需要放到var定义之后
this.name = name
this.age = age
}
fun eat(){
println(name + "is eating . He is " + age + "years old.")
}
}
fun main(){
Person("张三",19).eat() //张三is eating . He is 19years old.
}
```
###### 子类
```kotlin
class Student(sno:String,grade:Int,name:String,age:Int) : Person(name, age){ //注意这里调用父类的构造方法
constructor(sno:String,grade:Int):this(sno,grade,"张三",19)
// 这个类会有两种构造方式 一个是用主构造函数 另一个是利用次构造函数间接调用主构造函数
// 最终的落脚点都是主构造函数
var sno = ""
var grade = 0
}
fun main(){
// 不需要new 类的两种构造方式
Student("10",98,"李四",99).eat()
Student("10",98).eat()
}
```
##### 数据类和单例类
###### 数据类
java中的数据类
```java
//java中的数据类
public class Cellphone {
private String brand;
private double price;
public Cellphone(String brand, double price) {
this.brand = brand;
this.price = price;
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cellphone cellphone = (Cellphone) o;
return Double.compare(cellphone.price, price) == 0 &&
Objects.equals(brand, cellphone.brand);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public int hashCode() {
return Objects.hash(brand, price);
}
@Override
public String toString() {
return "Cellphone{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
```
kotlin中的数据类
```kotlin
data class Cellphone(val brand:String, val price:Int)
```
##### 单例类
java中的单例类
```java
public class Singleton {
private static Singleton instance;
private Singleton(){} // 默认构造
/**
synchronized 同步锁
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
*/
public synchronized static Singleton getInstance(){ //获取实例 静态方法 保证只创建一个实例
if(instance == null){
instance = new Singleton(); // 如果为空才new 这样就保证全局只有一个对象
}
return instance;
}
public void singletonTest(){
System.out.println("singletonTest is called");
}
}
```
kotlin的单例
```kotlin
object Singleton {
fun singletonTest(){
println("singletonTest is called");
}
}
fun main(){
// 看上去非常像静态方法的调用,但是其实kotlin子背后自动帮我们创建了一个singleton这样的实例,
//并且保证全局只存在一个singleton实例
Singleton.singletonTest()
}
```
#### 接口
```
interface Study {
fun readBooks()
fun doHomeWork()
}
------------------------
class StudyImpl :Study {
override fun readBooks() {
println("reading")
}
override fun doHomeWork() {
println("doHomeWork")
}
}
```
#### 修饰符
| 修饰符 | java | kotlin |
| --------- | ---------------------------------- | -------------------- |
| public | 所有类可见 | **所有类可见(默认)** |
| private | 当前类可见 | 当前类可见 |
| protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
| default | **同一包路径下的类可见(默认)** | 无 |
| internal | 无 | 同一模块中的类可见 |
#### lambda
##### list和map的新写法
```kotlin
// list
val listaa =ArrayList<Any>() // val 是不可变变量 但是这个list是可变集合
listaa.add("adsf")
listaa.add(false)
listaa.add(123)
----------------
val list = listOf("adsf",false,123) // val 是不可变变量 所以这个list是不可变集合
val listbb = mutableListOf("adsf",false,123)
listbb.add("我是新的") // 这个listbb是可变list 可以进行添加 list无法实现
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// map
val mapaa = HashMap<Any,Any>()
// 两种形式创建map的键值对
mapaa.put(false,1)
mapaa["banana"]="afds"
----------------
val map = mapOf(false to 1,"banana" to "afds") // mapOf函数生成的map也是一个不可变集合
val mapbb = mutableMapOf(false to 1,"banana" to "afds") // 可变的map
mapbb.put("我是新的","我是新的") // mapbb是可变map 可以添加新的东西
```
##### 集合或者map的遍历
```kotlin
fun main(){
val map = mapOf("apple" to 1,"banana" to 2)
for((fruit,num) in map){
println("fruit is $fruit, number is $num")
}
}
//fruit is apple, number is 1
//fruit is banana, number is 2
```
##### 集合的函数式API
###### maxBy函数
```kotlin
// 传统获取一个list中长度最长的字符串的方法
val fruitList = listOf("apple", "banana", "orange")
var maxLengthFruit = ""
for(fruit in fruitList){
if(fruit.length > maxLengthFruit.length)
maxLengthFruit = fruit
}
println("max length fruit is $maxLengthFruit")
-----------------------------------------------------------------------------------------------------
// maxBy接受的是一个lambda表达式
val lambda = {fruit: String -> fruit.length}
val maxLengthFruit = fruitList.maxBy(lambda)
-----------------------------------------------------------------------------------------------------
// 不需要定义lambda表达式
val maxLengthFruit = fruitList.maxBy({fruit: String -> fruit.length})
-----------------------------------------------------------------------------------------------------
// 当lambda参数是函数的最后一个参数的时候 可以将lambda表达式移动到函数括号的外边
val maxLengthFruit = fruitList.maxBy(){fruit: String -> fruit.length}
-----------------------------------------------------------------------------------------------------
// 当lambda参数是函数的唯一参数的时候 可以将函数的括号省略
val maxLengthFruit = fruitList.maxBy{fruit: String -> fruit.length}
-----------------------------------------------------------------------------------------------------
//参数类型kotlin会自动推导 不需要声明
val maxLengthFruit = fruitList.maxBy{fruit -> fruit.length}
-----------------------------------------------------------------------------------------------------
// 当lambad表达式的参数列表只有一个参数时,也不必声明参数名,而是可以用it关键字来代替
val maxLengthFruit = fruitList.maxBy{it.length}
-----------------------------------------------------------------------------------------------------
// 最终经过代码缩写得到的式子
val fruitList = listOf("apple", "banana", "orange")
val maxLengthFruit = fruitList.maxBy{it.length}
println("max length fruit is $maxLengthFruit")
```
###### map函数
```kotlin
// 将fruitList中的所有元素全部转换为大写 然后放到一个新list中
fun main(){
val fruitList = listOf("apple", "banana", "orange")
val newList = fruitList.map { it.toUpperCase() }
println(newList) // [APPLE, BANANA, ORANGE]
}
```
###### filter函数
```kotlin
// 筛选出小于等于5个字符的元素 然后把它转换为大写 可以进行链式调用
// 注意链式调用的顺序会影响执行的效率 如果map在前 浪费资源
fun main(){
val fruitList = listOf("apple", "banana", "orange")
val newList = fruitList.filter { it.length <=5 }.map { it.toUpperCase() }
println(newList) //[APPLE]
}
```
###### any和all函数
```kotlin
//any 判断是否存在符合条件的元素 只要有一个就成立
//all 判断是否所有的元素都满足条件 必须全满足才返回true
val fruitList = listOf("apple", "banana", "orange")
val anyResut = fruitList.any{it.length <= 5}
val allResut = fruitList.all{it.length <= 5}
println("anyResult is $anyResut, allResult is $allResut")
// anyResult is true, allResult is false
```
#### java函数式api的使用
**注意,这下方展示的代码仅限于从kotlin中调用java方法,冰洁单抽象方法的接口也必须是java语言定义的,kotlin中有专门的高阶函数来实现更阿基强大的自定义函数api功能,从而不需要像java这样借助单抽象方法接口来实现**
kotlin中调用java方法的时候可以使用函数式api,只不过有一定的条件限制,具体来说,如果我们在kotlin代码中调用了一个java方法,并且该方法接受一个java单抽象方法接口参数,就可以使用函数式api,单抽象方法接口是指的接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式api。
例如 Java原生api中有一个最为常见的单抽象方法接口 Runnable接口
```java
public interface Runnable {
public abstract void run();
}
```
thread类的构造方法中接受了一个Runnable参数,可以使用如下代码执行线程
```java
public class Main {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread is running");
}
}).start();
}
}
```
翻译成kotlin版本
````kotlin
fun main(){
Thread(object : Runnable{
override fun run() {
println("thread is running")
}
}).start()
}
````
目前的thread类的构造方法是支持java函数式api的使用条件的,可以使用lambda表达式进行简化
```kotlin
//第一步简化
Thread(Runnable{
println("thread is running")
}).start()
//第二步简化 如果一个java方法的参数列表中不存在一个以上的java单抽象方法接口参数 可以将接口名进行省略
Thread({
println("thread is running")
}).start()
//第三步简化 当lambda参数是函数的最后一个参数的时候 可以将lambda表达式移动到函数括号的外边
//第四步简化 当lambda参数是函数的唯一参数的时候 可以将函数的括号省略
Thread{
println("thread is running")
}.start()
```
#### 空指针检查
空指针在其他常见语言中是一种不受编程语言检查的**运行时异常**
下边是java的一个小demo 判空是必须要做的一件事,而kotlin将`空指针异常的检查提前到了编译时期`
##### 小demo
###### java版本
```java
class MyString {
String MyToUpperCase(String str){
if(str!= null){
return str.toUpperCase();
}
else return "传入了空指针";
}
}
public class Main {
public static void main(String[] args) {
MyString myString = new MyString();
System.out.println(myString.MyToUpperCase("asdfasfdasdf"));
}
}
```
###### kotlin版本
```kotlin
class MyString{
fun MyToUpperCase(str:String):String{
return str.toUpperCase()
}
}
fun main(){
MyString().MyToUpperCase(null) // 这里编译会报错 Null can not be a value of a non-null type String
}
```
##### kotlin的可空类型系统
```kotlin
// 注意 这里String后边跟了一个? 表示可空 但是这样还是编译不过 因为需要手动判空了
fun MyToUpperCase(str:String?):String{
if(str !=null){
return str.toUpperCase();
}else{
return "空指针"
}
}
```
##### 判空省略
```kotlin
// ?. 运算符 如果为空什么也不做 如果不为空才执行代码
if(xxx != null){
xxx.function()
}
//简化版
xxx?.function()
-------------------------------------------------------------------------
// ?:运算符 左方表达式不为空就返回左方的结果 否则返回右方的
fun MyToUpperCase(str:String?):String{
if(str !=null){
return str.toUpperCase();
}else{
return "空指针"
}
}
//简化版
fun MyToUpperCase(str:String?):String{
return str?.toUpperCase() ?: "空指针"
}
```
##### 强行通过空指针编译
```kotlin
var content: String? = "hello"
fun main(){
if(content != null){ // 提前判空了 但是编译器不能做到万能 所以需要强制通过编译
printUpperCase()
}
}
fun printUpperCase(){
val upperCase = content.toUpperCase() // 这里一定会报错
println(upperCase)
}
-----------------------------------------------------
fun printUpperCase(){
val upperCase = content!!.toUpperCase() // 加了!!. 就是向编译器表明这里一定不会有空指针
println(upperCase)
}
```
##### let函数的使用
**let函数的作用是处理全局变量的判空问题的**
```kotlin
class Sout{
fun soutxx(){
println("soutxx")
}
fun soutyy(){
println("soutyy")
}
}
fun test(){
sout?.let{ // 这里不报错
it.soutxx()
it.soutyy()
}
}
//Smart cast to 'Sout' is impossible, because 'sout' is a mutable property that could have been changed by this time
//fun test(){
// if(sout!=null){
// sout.soutyy() // 这里报错
// sout.soutyy() // 这里报错
// }
//}
// 这个是个全局变量 因为全局变量可以被其他线程所更改 所以if判空不允许
var sout:Sout? = null
// 只能去使用let函数
fun main(){
test()
}
```
#### 小技巧
##### 字符串内嵌表达式
```kotlin
fun main(){
var str = "ABCD"
println("字符串是 ${str} $ $str")
//字符串是 ABCD $ ABCD
}
```
##### 函数参数默认值
这个小技巧 可以在很大程度上替代次构造函数的作用
```kotlin
fun printParam(param1:String = "asfdsaf",param2: String){
println("param1 is $param1,param2 is $param2")
}
fun main(){
var str = "ABCD"
printParam(param2 = str) //输出 param1 is asfdsaf,param2 is ABCD param1的值是默认的
}
```
### 进阶内容
#### 扩展函数和运算符重载
##### 一元运算符
| 表达式 | 对应的函数 |
| ------ | -------------- |
| +a | a.unaryPlus() |
| -a | a.unaryMinus() |
| !a | a.not() |
| a++ | a.inc() |
| a-- | a.dec() |
##### **二元操作符**
| 表达式 | 对应的函数 |
| ------- | -------------- |
| a+b | a.plus(b) |
| a-b | a.minus(b) |
| a*b | a.tims(b) |
| a/b | a.div(b) |
| a%b | a.mod(b) |
| a.b | a.rangeTo(b) |
| a in b | b.contains(a) |
| a !in b | !b.contains(a) |
##### **方括号操作符重载**
| 表达式 | 对应的函数 |
| ---------------- | ------------------ |
| a[i] | a.get(i) |
| a[i,j] | a.get(i,j) |
| a[i_1,...,i_n] | a.get(i_1,..,i_n) |
| a[i]=b | a.set[i,b] |
| a[i,j]=b | a.set(i,j,b) |
| a..b | a.rangeTo(b) |
| a[i_1,...,i_n]=b | a.set(i_1,..i_n,b) |
##### **赋值操作符重载**
| 表达式 | 对应的函数 |
| ------ | ---------------- |
| a+=b | a.pulsAssign(b) |
| a-=b | a.minusAssign(b) |
| a*=b | a.timesAssign(b) |
| a/=b | a.divAssign(b) |
| a%=b | a.modAssign(b) |
##### **逻辑操作符重载**
| 表达式 | 对应的函数 |
| ------ | ----------------------- |
| a>b | a.compaerTo(b)>0 |
| a<b | a.compaerTo(b)<0 |
| a>=b | a.compaerTo(b)>=0 |
| a<=b | a.compaerTo(b)<=0 |
| a==b | a?.equals(b)?(b==null) |
| a!=b | !a?.equals(b)?(b==null) |
##### 第一个小demo
```kotlin
// 为String类增加lettersCount方法 可以不更改源码的情况下自己增加功能 这个是真牛逼
fun String.lettersCount():Int{
var count = 0
for (char in this){
count++
}
return count
}
fun main(){
var strTemp:String = "abcdefg"
println(strTemp.lettersCount()) // 输出为7
}
---------------------------------------------------------------------------------------------
//语法格式
fun ClassName.methodName(param1: type1 .....): typen{
}
//运算符重载
// 运算符+的重载 其实底层是转换为 obj1.plus(obj2)的方式
// 运算符可以进行多重重载 以下是 money和money对象之间的相加 money和int之间的相加
class Money(val value: Int){
operator fun plus(money: Money):Money{
val sum = value + money.value
return Money(sum)
}
operator fun plus(newValue: Int):Money{
val sum = value + newValue
return Money(sum)
}
}
fun main(){
val money1 = Money(5)
val money2 = Money(4)
val money3 = money1 + money2 //money和money对象之间的相加
println(money3.value) // 9
val money4 = money3 + 3 // money和int之间的相加
println(money4.value) // 12
}
```
##### 第二个demo
```kotlin
// 生成随机字符串 重复次数可能是1 到 20
fun getRandomLengthString(str: String):String{
val n = (1..20).random()
val builder = StringBuilder()
repeat(n){
builder.append(str)
}
return builder.toString()
}
fun main(){
println(getRandomLengthString("str"))
}
--------------------------------------------------------------------------
//更优雅的写法
// 运算符重载 + 扩展函数
operator fun String.times(n: Int) = repeat(n)
fun main(){
// 更优雅的写法
println("str" * (1..20).random())
}
```
android学习