Android 架构组件之 DataBinding

2024-02-25 16:58

本文主要是介绍Android 架构组件之 DataBinding,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 1. DataBinding 是什么?
    • 2. 分析 DataBinding 的实现原理
    • 3. 通过示例,介绍如何使用 DataBinding
      • 3.1 DataBinding 的基本使用
      • 3.2 layout 变量和 layout 表达式
      • 3.3 使用 layout 表达式处理事件
      • 3.4 使用 DataBinding 对界面数据动态更新
      • 3.5 生成的绑定类
      • 3.6 绑定适配器 BindingAdapter
      • 3.7 将布局View 绑定到架构组件
    • 参考链接

DataBinding 是 Google 推出的数据绑定的支持库,本文将从以下几个方面来介绍 DataBinding:

  • DataBinding 是什么?
  • 简单分析 DataBinding 的实现原理
  • 通过示例,介绍如何使用 DataBinding

1. DataBinding 是什么?

DataBinding 总结起来就是可以通过声明式的语法,完成 ViewModel 和 View 中组件的绑定。

在 Android 的 Model-View-ViewModel 模式中:

  • Model 对应实体 Bean,还有 Retrofit 中的 Service;
  • View 处理与 UI 相关的任务,View 包括 xml、Activity、Fragment;
  • ViewModel 完成业务相关的工作,ViewModel 不会持有 UI 中控件的引用,不会去更新 UI。

DataBinding 在 MVVM 模式中的位置,是完成 View 与 ViewModel 的绑定。

接下来,看一下 DataBinding 是如何实现 View与 ViewModel 绑定的。

2. 分析 DataBinding 的实现原理

具体说来,DataBinding 会在编译阶段,为每一个布局文件生成一个绑定类。默认情况下,绑定类名称基于布局文件的名称。它会转换为 Pascal 大小写形式并在末尾添加 Binding 后缀。

比如:布局文件名为 layout_main.xml,

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="name"type="String" /><variablename="lastName"type="String" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"></LinearLayout>
</layout>

生成的绑定类名称为 LayoutMainBinding,此类会包含从布局属性(例如,mName 变量)到布局视图的所有绑定,并且知道如何为绑定的表达式指定具体的值。生成的 LayoutMainBinding 代码:

public abstract class LayoutMainBinding extends ViewDataBinding {@Bindableprotected String mName;@Bindableprotected String mLastName;protected LayoutMainBinding(Object _bindingComponent, View _root, int _localFieldCount) {super(_bindingComponent, _root, _localFieldCount);}public abstract void setName(@Nullable String name);@Nullablepublic String getName() {return mName;}public abstract void setLastName(@Nullable String lastName);@Nullablepublic String getLastName() {return mLastName;}@NonNullpublic static LayoutMainBinding inflate(@NonNull LayoutInflater inflater,@Nullable ViewGroup root, boolean attachToRoot) {return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());}……

接下来通过一些简单的示例,介绍如何使用 DataBinding。

3. 通过示例,介绍如何使用 DataBinding

先来看 DataBinding 的基本使用。

3.1 DataBinding 的基本使用

如果要在一个项目中使用 DataBinding,需要完成以下几个步骤:

1.首先,打开 DataBinding 的控制,在 build.gradle 中

android {
...dataBinding {enabled true}
}

2. 用 <layout> 标签包装布局,需要将原来 xml 中的根元素,包装在 <layout> 标记中。

<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><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextView
...

3.根据需要添加 layout 变量和 layout 表达式

<data><variable name="name" type="String"/><variable name="lastName" type="String"/></data><TextViewandroid:id="@+id/plain_name"android:text="@{name}" ... />

使用 layout 表达式,有以下几个好处:

  • 改善应用程序的性能;
  • 能够防止内存泄漏和空指针异常;
  • 通过删除框架的调用,达到了简化 Activity 的目的。

4.修改 Activity 中的布局填充,使用 DataBindingUtil.setContentView() 方法替换 setContentView() 方法。

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//setContentView(R.layout.plain_activity)val binding: PlainActivitySolution2Binding =DataBindingUtil.setContentView(this, R.layout.plain_activity_solution_2)binding.name = "Scott"binding.lastName = "Song"
}

至此,就可以通过 binding 变量来控制 UI 中的内容了。

上面的示例中使用到了 layout 变量和 layout 表达式,我们接下来归纳一下它们的使用。

3.2 layout 变量和 layout 表达式

DataBinding 中 layout 变量是在 <data> 标签下,通过 <variable 变量来声明的,声明的变量可以是Java 的基本类型,也可以是自定义的对象类型。

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"><data><data>  <variable  name="str"  type="String"/>  <variable name="user" type="com.example.User"/></data>
......

使用 Java 中基本类型时,不需要进行导包,自定义的类型,需要指定完成的路径。每个 variable 都声明了一个在布局文件生成的绑定类的属性。

如果针对不同的配置,有多个布局文件,(比如横向和纵向),则变量会合并在一起,这些布局文件之间不能存在有冲突的变量定义。

在生成的绑定类中,每一个变量都有一个对应的 setter 和 getter,在调用 setter 之前,这些变量都有默认值,引用类型的默认值 null,boolean 类型的默认值 false,int 类型的默认值 0。

另外,系统会根据需要生成名为 context 的特殊变量,用于绑定表达式。context 的值是根 View 的 getContext() 方法中的 Context 对象。context 变量会被具有该名称的显式变量声明替换。

DataBinding 中的 layout 表达式是通过 lambda 定义的,格式为 @{ },比如上面示例代码中的User 类型的变量,layout 表达式中可以这样使用。

<TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.firstName}" />

layout 表达式的功能非常强大,我们总结归纳为以下几点:

1. layout 表达式中支持以下运算符的使用:

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 &lt;)
  • instanceof
  • 分组运算符 ()
  • 字面量运算符 - 字符、字符串、数字、null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元运算符 ?:

示例代码:

android:text="@{String.valueOf(index + 1)}"android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"android:transitionName='@{"image_" + id}'

2. 在 layout 表达式中不支持关键字 this、super、new 以及显示的泛型调用。

**3.layout 表达式会自动进行判空操作,避免出现空指针异常。**例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。另外 layout 表达式还支持 Null 合并运算符(??), 比如:

android:text="@{user.displayName ?? user.lastName}"

如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 null ,则选择右边运算数。

4.在 layout 表达式中使用数组、列表、Map、稀疏列表,首先需要使用<import>进行导包,然后使用 [] 运算符访问。比如:

<data><import type="android.util.SparseArray"/><import type="java.util.Map"/><import type="java.util.List"/><variable name="list" type="List&lt;String>"/><variable name="sparse" type="SparseArray&lt;String>"/><variable name="map" type="Map&lt;String, String>"/><variable name="index" type="int"/><variable name="key" type="String"/></data>…android:text="@{list[index]}"…android:text="@{sparse[index]}"…android:text="@{map[key]}"

示例中的 @{map[key]} 也可替换为 @{map.key},不要写成 List 形式,而是必须写成 List<String>,否则会导致 xml 的语法错误。

5.使用 layout 表达式访问资源时, 可以通过以下语法:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

如果是通过格式化字符串的方式,可以通过提供参数进行求值,比如:

Have an orange
Have %d orangesandroid:text="@{@plurals/orange(orangeCount, orangeCount)}"  <!--orangeCount是参数-->

对以下资源的访问,需要显示类型求值。

类型常规引用layout表达式引用
String[]@array@stringArray
int[]@array@intArray
TypedArray@array @typedArray
Animator @animator @animator StateListAnimator @animator @stateListAnimator color int @color @color ColorStateList @color @colorStateList

6.如果 layout 表达式中引入的多个包中,出现了类型冲突,可以使用别名。,比如:

<import type="android.view.View"/><import type="com.example.real.estate.View"alias="Vista"/>

在布局文件中使用 Vista 引用 com.example.real.estate.View,使用 View 引用 android.view.View。

还可以使用导入的类型来对表达式的一部分进行类型转换,比如:

<TextViewandroid:text="@{((User)(user.connection)).lastName}"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

示例将 connection 属性强制转换为类型 User。

此外,还可以在表达式中引用静态字段和方法,比如:

<data><import type="com.example.MyStringUtils"/><variable name="user" type="com.example.User"/></data><TextViewandroid:text="@{MyStringUtils.capitalize(user.lastName)}"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

示例使用 MyStringUtils 中的 capitalize() 方法。

**7. layout表达式还可以用在 <include>标签中,<include>不能作为 <merge>的直接子元素。**比如:

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:bind="http://schemas.android.com/apk/res-auto"><data><variable name="user" type="com.example.User"/></data><LinearLayoutandroid:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><include layout="@layout/name"bind:user="@{user}"/><include layout="@layout/contact"bind:user="@{user}"/></LinearLayout></layout>

示例中的布局文件中,包含 name.xml 和 contact.xml,变量可以从包含的布局传递到被包含布局中。在name.xml 以及 contact.xml 两个 layout 文件中必需要有 user 变量。

**DataBinding 不支持 include 作为 merge 元素的直接子元素。**比如,下面这样的布局是不支持的。

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:bind="http://schemas.android.com/apk/res-auto"><data><variable name="user" type="com.example.User"/></data><merge><!-- Doesn't work --><include layout="@layout/name"bind:user="@{user}"/><include layout="@layout/contact"bind:user="@{user}"/></merge></layout>

3.3 使用 layout 表达式处理事件

DataBinding 中处理事件有两种方式,分别是:方法引用监听器绑定。下面分别来看一下:

1.方法引用,在使用 DataBinding 以前,如果实现页面中的点击事件,可以 android:onClick 中指定Activity的一个方法,使用 DataBinding 处理事件,在编译时对方法进行安全性检查。不会出现因为方法不存在,或者方法签名不一致,出现崩溃的问题。

接下来看一下具体的实现:

class MyHandlers {fun onClickFriend(view: View) { ... }
}

使用 layout 表达式,将 android:onClick 指定到 onClickFriend() 方法:

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variable name="handlers" type="com.example.MyHandlers"/><variable name="user" type="com.example.User"/></data><LinearLayoutandroid:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.firstName}"android:onClick="@{handlers::onClickFriend}"/></LinearLayout></layout>

需要注意的是,layout 表达式中的方法签名必须与对象中的方法签名完全一致。

2.监听器绑定,监听器绑定和方法引用的主要区别在于创建时机的不同,监听器实现是在绑定数据时创建的,而不是在事件触发时创建的。另外在方法引用中,方法的参数必须与事件监听器的参数匹配。在监听器绑定中,只有您的返回值必须与监听器的预期返回值相匹配。

接下来看一下监听器绑定的具体实现:

class Presenter {fun onSaveClick(task: Task){}
}

然后将点击事件绑定到 onSaveClick() 方法上。

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variable name="task" type="com.android.example.Task" /><variable name="presenter" type="com.android.example.Presenter" /></data><LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"><Button android:layout_width="wrap_content" android:layout_height="wrap_content"android:onClick="@{() -> presenter.onSaveClick(task)}" /></LinearLayout></layout>

在表达式中使用回调时,数据绑定会自动为事件创建并注册必要的监听器。当视图触发事件时,数据绑定会对给定表达式求值。

避免使用复杂的监听器,这样会使布局文件变得难以阅读和维护。

3.4 使用 DataBinding 对界面数据动态更新

通过 DataBinding,使得绑定到界面的数据对象,在发生更改时,界面会自动刷新,总结起来有两种方法,这两种方法的本质都是使数据对象具有可观察性。

可观察性是指一个对象将其数据变化通知给其他对象的能力。

  • 方法 1 DataBinding 配合 LiveData 使用。
  • 方法 2 使用 Observable 接口,ObservableArrayList 等类使数据类变得可观察。

接下来我们分别看一下这两种方法。

**方法 1 DataBinding 配合 LiveData 使用,**首先将数据类型声明为 LiveData 类型。

class ScheduleViewModel : ViewModel() {val userName: LiveDatainit {val result = Repository.userNameuserName = Transformations.map(result) { result -> result.value }}}

要将 LiveData 对象与绑定类一起使用,需要指定生命周期所有者(LifecycleOwner)来定义对象的范围。

class ViewModelActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {// Inflate view and obtain an instance of the binding class.val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)// Specify the current activity as the lifecycle owner.binding.setLifecycleOwner(this)}}

**方法 2 使用 Observable 接口,ObservableArrayList 等类使数据类变得可观察。**通常一个可观察类会有三种类型,分别是可观察字段、可观察对象和可观察集合。分别来看一下:

可观察字段(ObservableField),Java 语言使用 public final 属性,在 Kotlin 中创建只读属性,比如:

class User {val firstName = ObservableField<String>()val lastName = ObservableField<String>()val age = ObservableInt()}

DataBinding 组件为基本类型都定义了可观察的类型,包括:

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable

可观察集合,除了上述的基本类型,DataBinding 组件还提供集合的可观察类型,包括:

  • ObservableArrayMap
  • ObservableArrayList

示例代码:

ObservableArrayList<Any>().apply {add("Google")add("Inc.")add(17)}

在布局中,可通过索引访问列表,比如:

<data><import type="android.databinding.ObservableList"/><import type="com.example.my.app.Fields"/><variable name="user" type="ObservableList<Object>"/></data><TextViewandroid:text='@{user[Fields.LAST_NAME]}'android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'android:layout_width="wrap_content"android:layout_height="wrap_content"/>

可观察对象,具体实现过程,就是实现 BaseObservable 的数据类,对 getter 方法添加 @get:Bindable 注解,对 setter 方法中调用 notifyPropertyChanged() 方法。示例代码:

 class User : BaseObservable() {@get:Bindablevar firstName: String = ""set(value) {field = valuenotifyPropertyChanged(BR.firstName)}@get:Bindablevar lastName: String = ""set(value) {field = valuenotifyPropertyChanged(BR.lastName)}}

3.5 生成的绑定类

DataBinding 可以生成用于访问布局变量的绑定类,这里总结一些创建绑定类对象的方法:

1. 通过绑定类的 inflate() 方法。

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val binding: MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater)}

inflate() 方法还有一个接收 viewGroup 对象的重载方法:

    val binding: MyLayoutBinding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false)

2.通过 bind() 方法单独绑定。

    val binding: MyLayoutBinding = MyLayoutBinding.bind(viewRoot)

3.系统无法预先知道绑定类型。在这种情况下,可以使用 DataBindingUtil 类创建绑定。

 val viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
val binding: ViewDataBinding? = DataBindingUtil.bind(viewRoot)

4.通常 DataBinding 会在下一帧之前完成绑定,但有时需要立即绑定,可以强制执行executePendingBindings() 方法。

**5.有时,系统并不知道特定的绑定类。**例如,针对任意布局运行的 RecyclerView.Adapter 不知道特定绑定类。在调用 onBindViewHolder() 方法时,仍必须指定绑定值。

RecyclerView 绑定到的所有布局都有 item 变量。BindingHolder 对象具有一个 getBinding() 方法,这个方法返回 ViewDataBinding 基类。

override fun onBindViewHolder(holder: BindingHolder, position: Int) {item: T = items.get(position)holder.binding.setVariable(BR.item, item)holder.binding.executePendingBindings()}

DataBinding 会生成一个名为 BR 的类,其中包含了用于数据绑定的资源 ID,比如上面示例中,会自动生成BR.item 变量。

3.6 绑定适配器 BindingAdapter

有时候需要自定义控件的属性,这时候需要用到 BindingAdapter 注解。在函数上使用 @BindingAdapter 注解。

@BindingAdapter(value = ["app:progressScaled", "android:max"], requireAll = true)
fun setProgress(progressBar: ProgressBar, likes: Int, max: Int) {progressBar.progress = (likes * max / 5).coerceAtMost(max)
}

示例中的方法需要三个参数,其中参数 requireAll 定义何时使用 BindingAdapter,默认值是 true。当 requiireAlld的值为 true 时,xml 文件中必须包含所有元素;当 requireAll 值为 false 时,缺少的属性如果是对象类型,则为 null,Boolean 类型则为 false,Int 类型则为 0。

<ProgressBarandroid:id="@+id/progressBar"app:hideIfZero="@{viewmodel.likes}"app:progressScaled="@{viewmodel.likes}"android:max="@{100}"......

使用 BindingAdapter 自定义属性,一个非常有用的场景是加载图片。通常我们会使用到 Picasso、Glide 等图片加载框架,这些框架都是通过 url 来为 ImageView 设置显示图片的,但<ImageView>并没有为 url 设置的属性,这时,BindingAdapter 就派上用场了。

@BindingAdapter("imageUrl", "error")fun loadImage(view: ImageView, url: String, error: Drawable) {Picasso.get().load(url).error(error).into(view)}

定义 imageUrl 和 error 两个属性,分别用来设置 ImageView 正常情况下显示的图片,以及出错情况下显示的图片。

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

3.7 将布局View 绑定到架构组件

在介绍 使用 DataBinding 对界面数据动态更新时,讲了如何将 DataBinding 与 LiveData 进行绑定,接下来看一下如何将 DataBinding 与 ViewModel 绑定使用。

具体实现,概括起来就是先获取绑定类的实例,然后将 ViewModel 示例作为组件的属性,分配给绑定类。

 class ViewModelActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {// Obtain the ViewModel component.UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class)// Inflate view and obtain an instance of the binding class.val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)// Assign the component to a property in the binding class.binding.viewmodel = userModel}}

使用 layout 表达式,将 ViewModel 的属性和方法,分配给对应的 View 属性。

<CheckBoxandroid:id="@+id/rememberMeCheckBox"android:checked="@{viewmodel.rememberMe}"android:onCheckedChanged="@{() -> viewmodel.rememberMeChanged()}" />

至此,对 DataBinding 介绍就结束了,示例代码地址 https://github.com/sguotao/google_jetpack_example

更多内容,可以订阅 我的博客


参考链接

Android Data Binding
数据绑定库

这篇关于Android 架构组件之 DataBinding的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/746108

相关文章

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

系统架构设计师: 信息安全技术

简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 系统架构设计师: 信息安全技术前言信息安全的基本要素:信息安全的范围:安全措施的目标:访问控制技术要素:访问控制包括:等保

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk