DataBinding viewBinding(视图绑定与数据双向绑定)简单案例 (kotlin)

先上效果:

4个view的文字都是通过DataBinding填充的。交互事件:点击图片,切换图片

创建项目(android Studio 2023.3.1)

Build.gradle(:app) 引入依赖库(完整源码)

buildFeatures {

viewBinding true

compose true

}

dataBinding {

enabled = true

}

复制代码
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)
    id 'kotlin-kapt'
}

android {
    namespace 'com.example.lanidemokt'
    compileSdk 31

    defaultConfig {
        applicationId "com.example.lanidemokt"
        minSdk 24
        targetSdk 30
        versionCode 1
        versionName "1.0"


        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        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'
    }
    lintOptions {
        abortOnError false
    }
    buildFeatures {
        viewBinding true
        compose true
    }
    dataBinding {
        enabled = true
    }
}

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
    implementation "androidx.compose.ui:ui:1.0.1"
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'androidx.appcompat:appcompat-resources:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.compose.material:material:1.0.1'

    implementation 'com.github.bumptech.glide:compiler:4.11.0'
    implementation 'com.github.bumptech.glide:glide:4.11.0'
}

1. 基本使用意向绑定数据显示在界面

MainActivity.kt (完整源码)

在MainActivity.kt里,Databinding和我们的XML文件绑定起来了,现在你点击Databinding会发现直接可以跳转到对应的XML文件里面去了,

复制代码
package com.example.lanidemokt

import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.databinding.BaseObservable
import androidx.databinding.DataBindingUtil
import com.catchpig.utils.LogUtils
import com.example.lanidemokt.adapter.MainActivityBindingAdapter
import com.example.lanidemokt.databinding.ActivityMainBinding
import com.example.lanidemokt.viewmodel.ButtonClickListener
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Date

class MainActivity : AppCompatActivity() {

    /*
    * DataBinding 对应一个Binding对象,对象名是布局文件文称加上Binding后缀
    * binding,activity_main.xml的布局实例
    * xml上所有变量与点击事件,必须是binding的成员属性或者成员方法函数,否则操作界面无效
    *
    * */
    var binding: ActivityMainBinding? = null // 操作布局实例
    private var login: Login? = null  //声明一个响应式对象,用于ui
    var clickListener: ButtonClickListener? = null // 布局点击对象封装


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState) //        setContentView(R.layout.activity_main)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        initView()
        initData()
        corGlobalTest()
    }

    private fun initData() {
        LogUtils.init(this)
    }

    /* *
     * 在Controller层,将我们的data与model相关联
     * */
    data class Login(var name: String = "LLL", var msg: String) //意向绑定响应式

    data class Student(var name: String = "LLL", var score: Int) : BaseObservable() //双击绑定响应式

    private fun initView() {
        binding?.msg?.setText("我是谁")
        login = Login("LANI", "我是谁")
        //  binding?.login = Login("LANI", "我是谁")
        //  这一步必须要,否则点击没反应,否则界面不显示对应的名字与信息
        binding?.setLogin(Login("LANI", "我是谁"))

        binding?.setStudent(Student("LEE", 199))
        binding?.picture1?.setOnClickListener {
            println("图片点击")
            MainActivityBindingAdapter.loadStudentDetails(
                it as ImageView,
                "http://192.168.1.207:8080/download/88.jpg"
            )
        }
        clickListener=ButtonClickListener()
        binding?.btnHandler = clickListener

    }

    /*
    * 协程创建
    * */
    fun corGlobalTest() {
        GlobalScope.launch {
            println("|--开始global${Date()}")
            delay(1000)
            println("|--END global${Date()}")
        }

        println("|--END ${Date()}")

    }
}
activity_main.xml

现在我们就来看看如何给我们的XML文件里面的View设置值。

在XML文件的layout标签下,创建data标签,在data标签中再创建variable标签,variable标签主要用到的就是name属性和type属性,类似于Java语言声明变量时,需要为该变量指定类型和名称。新建一个名为Login的数据类。

在XML文件中声明好variable属性后,接下来就可以在XML使用它了。

使用variable属性时需要使用到布局表达式:@{ }

可以在布局表达式**@{ }**中获取传入variable对象的

activity_main.xml (源码 )
复制代码
<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

        <variable
            name="login"
            type="com.example.lanidemokt.MainActivity.Login" />

        <variable
            name="student"
            type="com.example.lanidemokt.MainActivity.Student" />

    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{login.name}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="名字" />

        <TextView
            android:id="@+id/msg2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{login.msg}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/msg"
            tools:text="消息" />

        <TextView
            android:id="@+id/msg4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ ` `+ student.score}"
            app:layout_constraintBottom_toBottomOf="@id/login"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/msg2"
            tools:text="消息2" />



    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

2. 给控件View添加响应事件:

方式一:直接在Controller层通过原来的方式添加

复制代码
binding?.login?.setOnClickListener {
          
}

方式二:

创建一个工具类,在类中定义响应的点击事件

第一步:创建点击的工具类 ButtonClickListener.kt

第二步:在XML文件中添加工具类 ,在XML文件中添加响应事件:

第三步:在XML文件中添加响应事件android:onClick="@{btnHandler::click}"

第四步:在Controller里面进行关联binding?.btnHandler = clickListener

activity_main.xml (增加点击事件的完整源码)
复制代码
<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>
        <!--需要使用到响应数据类引入
        data标签中再创建variable标签,variable标签主要用到的就是name属性和type属性 ,
        类似于Java语言声明变量时,需要为该变量指定类型和名称
        -->
        <import type="android.view.View" />
        <!--        <import type="com.example.lanidemokt.MainActivity" />-->


        <!--        <variable
                    name="login"
                    type="com.example.lanidemokt.MainActivity.Login" />-->

        <variable
            name="btnHandler"
            type="com.example.lanidemokt.viewmodel.ButtonClickListener" />

        <variable
            name="login"
            type="com.example.lanidemokt.MainActivity.Login" />

        <variable
            name="student"
            type="com.example.lanidemokt.MainActivity.Student" />

    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{login.name}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="名字" />

        <TextView
            android:id="@+id/msg2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{login.msg}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/msg"
            tools:text="消息" />

        <TextView
            android:id="@+id/msg4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{ ` `+ student.score}"
            app:layout_constraintBottom_toBottomOf="@id/login"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/msg2"
            tools:text="消息2" />

        <Button
            android:id="@+id/login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="杀生丸哥哥"
            android:onClick="@{btnHandler::msgTextClickListener}"
            android:layout_marginBottom="20dp"
            app:layout_constraintBottom_toBottomOf="@id/picture1"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/msg4"
            tools:text="消息2" />

        <ImageView
            android:id="@+id/picture1"
            android:layout_width="300dp"
            android:layout_height="200dp"
            android:layout_marginBottom="20dp"
            android:layout_marginTop="20dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/login"
            app:layout_constraintBottom_toBottomOf="parent"
            app:url="@{`http://192.168.1.207:8080/download/kn.png`}" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
ButtonClickListener.kt 源码
复制代码
package com.example.lanidemokt.viewmodel

import android.view.View
import android.widget.TextView
import com.example.lanidemokt.utils.LogSetting

class ButtonClickListener {
    /*
    * 界面点击事件封装
    * */
    fun msgTextClickListener(view: View) {
        view.text = "杀生丸丸哥哥一直很帅"
//        view.setText( "杀生丸丸哥哥一直很帅") //Use of setter method instead of property access syntax

    }
}

3. XXXBindingAdapter方法实现响应

使用DataBinding库时,DataBinding会针对控件属性生成对应的XXXBindingAdapter类,如TextViewBindingAdapter类,其对TextView的每个可以使用DataBinding的属性都生成了对应的方法,而且每个方法都使用了@BindingAdapter注解,注解中的参数就是对应View的属性。

自定义BindingAdapter 编写一个处理图片的自定义BindingAdapter类。然后定义一个静态方法,主要用于添加 BindingAdapter 注解,注解值是 ImageView 控件自定义的属性名,如下所示。

MainActivityBindingAdapter.kt (源码)

图片资源是部署到本地的Nginx上的:

http://192.168.1.207:8080/download/kn.png

复制代码
package com.example.lanidemokt.adapter

import android.util.Log
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide
import com.catchpig.utils.LogUtils
import com.example.lanidemokt.utils.LogSetting

class MainActivityBindingAdapter {
    companion object {
        val TAG: String = "MainActivityBindingAdapter"
        /*
        * 通过默认adapter 设置自定app:xxx属性,并设置xx属性值,实现响应式修改更新
        *
        * */
        @BindingAdapter("url")
        @JvmStatic
        fun loadStudentDetails(
            view: ImageView,
            url: String = "http://192.168.1.207:8080/download/kn.png"
        ) {
            Glide.with(view!!).load(url).into(view)
        }
    }
}

多个参数的话,修改@BindingAdapter有value

@BindingAdapter(value = ["url", "placeholder", "error"])

@JvmStatic

4.双向响应绑定(输入框)

输入数字时,消息text同步更新

Build.gradle(:app) 引入依赖库(完整源码)

增加自动生成BR实体的依赖库,

复制代码
id 'kotlin-kapt'
kapt {
    generateStubs = true
}
kapt  "androidx.room:room-compiler:2.4.0"
复制代码
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)
    id 'kotlin-kapt'
}

android {
    namespace 'com.example.lanidemokt'
    compileSdk 31

    defaultConfig {
        applicationId "com.example.lanidemokt"
        minSdk 24
        targetSdk 30
        versionCode 1
        versionName "1.0"


        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        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'
    }
    lintOptions {
        abortOnError false
    }
    buildFeatures {
        viewBinding true
//        dataBinding  true
        compose true
    }
   dataBinding {
        enabled = true
    }
    kapt {
        generateStubs = true
    }
}
dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
    implementation "androidx.compose.ui:ui:1.0.1"
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'androidx.appcompat:appcompat-resources:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.compose.material:material:1.0.1'

    implementation 'com.github.bumptech.glide:compiler:4.11.0'
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    kapt  "androidx.room:room-compiler:2.4.0"
}

MainActivity.kt 源码

增加绑定viewmodel: binding?.order = OrderViewModel() // 绑定双向响应实体

复制代码
package com.example.lanidemokt

import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.databinding.BaseObservable
import androidx.databinding.DataBindingUtil
import com.catchpig.utils.LogUtils
import com.example.lanidemokt.adapter.MainActivityBindingAdapter
import com.example.lanidemokt.databinding.ActivityMainBinding
import com.example.lanidemokt.viewmodel.ButtonClickListener
import com.example.lanidemokt.viewmodel.OrderViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Date

class MainActivity : AppCompatActivity() {

    /*
    * DataBinding 对应一个Binding对象,对象名是布局文件文称加上Binding后缀
    * binding,activity_main.xml的布局实例
    * xml上所有变量与点击事件,必须是binding的成员属性或者成员方法函数,否则操作界面无效
    * 布局取响应式值 -表达式: `@{ }
    *
    * */
    var binding: ActivityMainBinding? = null // 操作布局实例
    private var login: Login? = null  //声明一个响应式对象,用于ui
    var clickListener: ButtonClickListener? = null // 布局点击对象封装
    var vm: OrderViewModel = OrderViewModel()


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState) //        setContentView(R.layout.activity_main)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        initView()
        initData()
        corGlobalTest()
    }

    private fun initData() {
        LogUtils.init(this)
    }

    /* *
     * 在Controller层,将我们的data与model相关联
     * */
    data class Login(var name: String = "LLL", var msg: String) //意向绑定响应式

    data class Student(var name: String = "LLL", var score: Int) : BaseObservable() //双击绑定响应式

    private fun initView() {
        binding?.msg?.setText("我是谁")
        login = Login("LANI", "我是谁")
        //  binding?.login = Login("LANI", "我是谁")
        //  这一步必须要,否则点击没反应,否则界面不显示对应的名字与信息
        binding?.setLogin(Login("LANI", "我是谁"))

        binding?.setStudent(Student("LEE", 199))
        MainActivityBindingAdapter.loadStudentDetails(
            binding?.picture1 as ImageView,
            "http://192.168.1.207:8080/download/kn.png"
        )
        binding?.picture1?.setOnClickListener {
            println("图片点击")
            LogUtils.d("图片点击")
            MainActivityBindingAdapter.loadStudentDetails(
                it as ImageView,
                "http://192.168.1.207:8080/download/88.jpg"
            )
        }
        clickListener = ButtonClickListener()
        binding?.btnHandler = clickListener //给控件添加响应事件 :点击事件
        binding?.order = OrderViewModel() // 绑定双向响应实体


    }

    /*
    * 协程创建
    * */
    fun corGlobalTest() {
        GlobalScope.launch {
            println("|--开始global${Date()}")
            delay(1000)
            println("|--END global${Date()}")
        }

        println("|--END ${Date()}")

    }
}
OrderViewModel.kt 源码

实现双向绑定 viewmodel,BaseObservable :普通的数据对象包装成一个可观察的数据对象

复制代码
package com.example.lanidemokt.viewmodel

import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.catchpig.utils.LogUtils
import com.example.lanidemokt.BR


class OrderViewModel : BaseObservable() {
    /*
    * 实现双向绑定 viewmodel,
    * BaseObservable :普通的数据对象包装成一个可观察的数据对象
    * 当使用name字段发生变更后,若想UI自动刷新,
    * 要求方法名必须以get开头并且标记Bindable注解
    * 注解才会自动在build目录BR类中生成entry
    * 数据模型继承 BaseObservable;
   * 要求获取数据方法名必须以 get 开头并且标记 @Bindable 注解;
   * 设置数据方法必须以 set 开头然后调用 notify() 函数既可以刷新视图。
      * BR 类是 BaseObservable 子类中由 @Bindable 注解修饰的函数生成;
     * BR 类生成位置在 //app\build\generated\source\kapt\debug\com\example\lanidemokt
     *
    * */


    @get:Bindable
    var orderCount: String? = "100"
        set(orderCount) {
            LogUtils.d("当前orderCount=${orderCount}")
            field = orderCount
            notifyPropertyChanged(BR.orderCount)
        }

}
activity_main.xml源码

引入viewmodel:

<variable name="order" type="com.example.lanidemokt.viewmodel.OrderViewModel" />

复制代码
<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>
        <import type="android.view.View" />

        <variable
            name="order"
            type="com.example.lanidemokt.viewmodel.OrderViewModel" />

        <variable
            name="btnHandler"
            type="com.example.lanidemokt.viewmodel.ButtonClickListener" />

        <variable
            name="login"
            type="com.example.lanidemokt.MainActivity.Login" />

        <variable
            name="student"
            type="com.example.lanidemokt.MainActivity.Student" />

    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <Button
            android:id="@+id/login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:onClick="@{btnHandler::msgTextClickListener}"
            android:text="杀生丸哥哥"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/msg4"
            tools:text="杀生丸哥哥" />

        <TextView
            android:id="@+id/msg5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="@{ ` 当前订单数量:`+order.orderCount}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/login"
            tools:text="消息2" />

        <!-- 双向响应数据,赋值语法  @={xx.xx}-->
        <EditText
            android:id="@+id/username"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="@={order.orderCount}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/msg5" />

        <!--  XXXBindingAdapter方式设置app:url   -->
        <ImageView
            android:id="@+id/picture1"
            android:layout_width="300dp"
            android:layout_height="200dp"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/login"
            app:url="@{`http://192.168.1.207:8080/download/kn.png`}" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

双向响应完结。

-- 设置网络图片在ImageView 打开网络权限

AndroidManifest.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:theme="@style/Theme.LaniDemoKt"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:usesCleartextTraffic="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

app\src\main\res\xml\network_securit_config.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
相关推荐
Kapaseker22 分钟前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 小时前
Andorid Google 登录接入文档
android
黄林晴3 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab15 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿18 小时前
Android MediaPlayer 笔记
android
Jony_18 小时前
Android 启动优化方案
android
阿巴斯甜18 小时前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇19 小时前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android
_小马快跑_1 天前
Kotlin | 从SparseArray、ArrayMap的set操作符看类型检查的不同
android