17人参与 • 2025-09-28 • Android
binding adapter 是一个桥梁,它允许你在 xml 布局文件中,将自定义的属性(例如 app:image_corners_uri
)与 kotlin/java 代码中的方法绑定起来。它解决了 android 默认的 data binding 无法处理复杂逻辑或自定义 view 属性的问题。
class databindingadapter { companion object { @bindingadapter("media_source_icon", "media_source_name") @jvmstatic fun setmediasourceinfo(view: mediasourcebar, drawable: drawable?, name: string?) { name?.let { view.setmediasourcename(it) } drawable?.let { view.setmediasourceicon(it) } } @bindingadapter("media_source_right_button") @jvmstatic fun setmediasourcerightbutton(view: mediasourcebar, rotate: boolean?) { rotate?.let { view.setmediasourcerightbutton(it) } } @bindingadapter("data") @jvmstatic fun bindrecyclerview( recyclerview: recyclerview, list: mutablelist<listitem>?, ) { val adapter = recyclerview.adapter if (adapter is recyclerviewlistadapter) { list?.let { adapter.submitlist(it) } } } @bindingadapter(value = ["media_metadata", "media_source"], requireall = false) @jvmstatic fun bindmediametadatainfo( simplemediaview: playinfoview, mediametadata: mediametadata?, mediasource: servicebean? ) { simplemediaview.setmediametadata(mediametadata) if (mediasource?.packagename in mediabrowsermanager.musicmediasourceservices) { simplemediaview.setmediaservicebean(mediasource) } else { simplemediaview.setmediaservicebean(null) } } @bindingadapter("duration", "progress", requireall = false) @jvmstatic fun bindmediametadataprogress( simplemediaview: playinfoview, duration: long?, progress: long? ) { simplemediaview.setprogress(duration, progress) } @bindingadapter("player_commands", requireall = false) @jvmstatic fun bindmediametadatacommands( simplemediaview: playinfoview, commands: player.commands?, ) { simplemediaview.setplayercommands(commands) } @bindingadapter("isplaying", requireall = false) @jvmstatic fun bindmediametadataplayback( view: view, isplaying: boolean?, ) { if (view is playinfoview) { view.setplayback(isplaying) } if (view is tech.jidouauto.component.widgets.basic.imageview) { if (isplaying == true) { view.setimageresource(com.jidouauto.mediacenter.r.drawable.item_icon_pause) } else { view.setimageresource(com.jidouauto.mediacenter.r.drawable.item_icon_play) } } } @bindingadapter("playmode", requireall = false) @jvmstatic fun bindplaymode( simplemediaview: playinfoview, mode: playmode? ) { if (mode != null) { simplemediaview.setplaymode(mode) } } @bindingadapter("type") @jvmstatic fun settype(view: playinfoview, type: int) { view.settype(type) } @bindingadapter("disctype") @jvmstatic fun setdisctype(view: playinfoview, type: int) { view.setdisctype(type) } @bindingadapter("image_corners_uri", "image_radius", requireall = false) @jvmstatic fun imageuri(view: tech.jidouauto.component.widgets.basic.imageview, uri: any?, radius: int?) { uri?.let { val imageresource = imageresource.remote( uri, transformationtype = iimageloader.imagetransformationtype.roundcorners(radius?.dp ?: 24.dp), placeholder = com.jidouauto.mediacenter.r.drawable.icon_placeholder, error = com.jidouauto.mediacenter.r.drawable.icon_placeholder ) view.imagesource = imageresource } } @bindingadapter("image_cycle_uri", requireall = false) @jvmstatic fun cycleimageuri(view: tech.jidouauto.component.widgets.basic.imageview, uri: any?) { uri?.let { val imageresource = imageresource.remote( uri, transformationtype = iimageloader.imagetransformationtype.circlecrop, placeholder = com.jidouauto.mediacenter.r.drawable.icon_placeholder_circle, error = com.jidouauto.mediacenter.r.drawable.icon_placeholder_circle ) view.imagesource = imageresource } } @bindingadapter("player_album_image_cover", requireall = false) @jvmstatic fun playeralbumimagecover( view: tech.jidouauto.component.widgets.basic.imageview, uri: any? ) { } @bindingadapter("banner_data", requireall = false) @jvmstatic fun bindbannerviewdata( view: topbannercarouselpager, data: mainbanneritem?, ) { logger.info("bind main banner data=${data?.items}") if (data == null) return view.setadapter(topbannercarouselpager.adapter(data.items, data.itemonclick)) } @bindingadapter("show_background", requireall = false) @jvmstatic fun showbackground(view: view, isshow: boolean) { } /** * 控制lottie动画的播放状态 * @param view lottieanimationview实例 * @param isplaying 是否正在播放(true:播放,false:暂停) */ @bindingadapter("lottie_playing_state") @jvmstatic fun controllottieanimation(view: lottieanimationview, isplaying: boolean?) { isplaying?.let { if (it) { // 如果需要播放且当前未播放,则开始/恢复动画 if (!view.isanimating) { view.playanimation() } } else { // 如果需要暂停且当前正在播放,则暂停动画 if (view.isanimating) { view.pauseanimation() } } } } /** * 可选:控制lottie动画的可见性(根据播放状态) * 当播放时显示动画,暂停时隐藏 */ @bindingadapter("lottie_visible_with_playing") @jvmstatic fun setlottievisibilitywithplaying(view: lottieanimationview, isplaying: boolean?) { view.visibility = if (isplaying == true) view.visible else view.gone } } }
在这份代码中,@bindingadapter
注解就是关键。它告诉 data binding 编译器,当 xml 中使用了该注解中定义的属性时,应该调用被注解的方法。
例如:@bindingadapter("media_source_icon", "media_source_name")
:定义了两个属性,media_source_icon
和 media_source_name
。当 xml 中同时使用这两个属性时,会调用 setmediasourceinfo
方法。
这份代码覆盖了多种常见的 binding adapter 应用场景,我们可以逐一分析:
setmediasourceinfo
方法:
注解: @bindingadapter("media_source_icon", "media_source_name")
应用: 将 drawable
和 string
类型的数据绑定到 mediasourcebar
这个自定义 view 上,分别设置图标和名称。
知识点: @bindingadapter
注解可以接收多个属性名,当这些属性都在 xml 中出现时,被注解的方法会被调用。方法参数的顺序和类型必须与属性值匹配。
setmediasourcerightbutton
方法:
注解: @bindingadapter("media_source_right_button")
应用: 将一个 boolean
值绑定到 mediasourcebar
的一个按钮上,控制其旋转状态。
知识点: 展示了如何绑定单个自定义属性,将一个布尔值直接传递给 view 的方法。
bindrecyclerview
方法:
注解: @bindingadapter("data")
应用: 将一个 mutablelist<listitem>
数据列表直接绑定到 recyclerview
。
知识点: 这个方法非常实用,它通过检查 recyclerview
的 adapter
类型(确保是 recyclerviewlistadapter
),然后调用 adapter.submitlist()
来更新数据。这在 viewmodel 中处理列表数据时非常方便,只需在 xml 中设置 app:data="@{viewmodel.mylist}"
,就能实现列表的自动更新。
bindmediametadatainfo
方法:
注解: @bindingadapter(value = ["media_metadata", "media_source"], requireall = false)
应用: 将 mediametadata
和 servicebean
两个复杂的对象绑定到 playinfoview
上。
知识点:
value = [...]
:用于定义多个属性。
requireall = false
:这是一个重要的参数。它表示这些属性不必同时出现在 xml 中。如果设置为 true
(默认),只有当 xml 中同时包含了所有属性时,该方法才会被调用。设置为 false
意味着只要其中一个属性存在,方法就会被调用。
方法中的逻辑展示了如何根据 mediasource.packagename
进行条件判断,然后设置不同的 view 状态,这是 binding adapter 中处理复杂逻辑的常见方式。
imageuri
和 cycleimageuri
方法:
注解: @bindingadapter("image_corners_uri", "image_radius", requireall = false)
和 @bindingadapter("image_cycle_uri", requireall = false)
应用: 加载远程图片 url(uri),并根据不同的属性进行不同的处理。image_corners_uri
用于加载圆角图片,而 image_cycle_uri
用于加载圆形图片。
知识点: 这两个方法是 binding adapter 最常见的用途之一。它们将图片加载库(如 glide、picasso 或你代码中的 imageresource.remote
)的复杂调用逻辑封装起来,让开发者在 xml 中只需简单地提供一个 url 即可,大大简化了 view 的使用。同时,它还演示了如何处理占位符和错误图片。
bindmediametadataplayback
方法:
注解: @bindingadapter("isplaying", requireall = false)
应用: 根据 isplaying
的布尔值,改变不同 view 的状态。
知识点:方法参数中的 view
类型非常通用。
方法内部使用 if (view is playinfoview)
和 if (view is tech.jidouauto.component.widgets.basic.imageview)
进行 类型判断。这允许同一个 binding adapter 处理不同类型的 view,根据 view 的具体类型执行不同的逻辑。例如,如果是 playinfoview
就设置播放状态,如果是 imageview
就改变图标。
@jvmstatic
: 这是一个 kotlin 注解,用于标记一个方法为静态方法。在 companion object
中使用此注解后,该方法可以像 java 的静态方法一样,通过类名直接调用,这正是 data binding 编译器所要求的。
companion object
: kotlin 中的伴生对象,用于存放与类相关的静态成员。将所有 binding adapter 方法放在 companion object
中是 kotlin 的最佳实践。
outlineprovider
类是一个自定义的 viewoutlineprovider
,它的主要作用是为 view
设置圆角裁剪(clipping)效果。它接收 cornertype
和 radius
作为参数,然后根据这些参数生成一个 path
,并用它来定义 view 的轮廓(outline)。这使得开发者可以在不修改 view 背景或使用其他复杂方法的情况下,轻松地为 view 的特定角或所有角设置圆角效果。
cornertype
枚举定义了九种不同的圆角类型,包括:
all
: 所有四个角都设置圆角。top_left
, top_right
, bottom_left
, bottom_right
: 仅设置单个角。top
, bottom
, left
, right
: 设置特定边上的两个角。getoutline
方法是核心逻辑所在,它根据 cornertype
和 radius
的值创建一个 floatarray
,该数组用于 path
的 addroundrect
方法来创建带有指定圆角的矩形路径。最后,它使用 outline.setpath(path)
将这个路径应用到 view 的轮廓上。databindingadapter
类是一个包含大量静态 bindingadapter
方法的集合,这些方法用于在 xml 布局文件中实现自定义属性和数据绑定。它的核心作用是:
setimageurl
、setcircleimageurl
、setiqiyispecifiedsizeimageurl
)来使用 glide 库加载图片。这些方法支持处理不同的图片资源类型(id、drawable、uri),设置占位图、圆角半径、高斯模糊等效果。bindtablayouttabs
: 动态绑定和设置 tablayout
的 tab。setlayoutheight
, layoutmargintop
等: 设置 view 的布局参数(如高度、边距)。setqrcodestatus
: 设置 qrcodescreenview
的状态和内容。setximalayaalbumtags
: 根据媒体项的数据,动态显示喜马拉雅的vip、付费、精品等标签图标。setiqynormalmodeltag
: 根据媒体项数据,动态显示爱奇艺的vip、独家等标签图标。setoperationpanelitemactiveddisabled
: 管理操作面板项的激活和禁用状态。strikethrough
: 为 textview
的文本添加删除线效果。setpopupopenanimation
、setcancelanimation
等。bindingresourceutil
是一个工具类,包含了一系列静态方法,用于将数据模型中的原始值(如浮点数、字符串、枚举)转换为可用于数据绑定的资源对象(如 imageresource
、textresource
)。它的核心作用是:
playbackspeedicon
: 根据播放速度的浮点值返回相应的图标资源 id。definitiontext
: 根据视频清晰度的字符串返回相应的文本资源 id。ximalayadownloadicon
: 根据喜马拉雅媒体项的下载类型返回相应的下载图标资源 id。playingcoverplaceholder
会根据 mediaid
判断媒体源,并返回相应的占位图。downloaddisabledreason
根据下载状态返回相应的禁用原因文本。databindingconvertor
是一个简单的 java 类,它使用 @bindingconversion
注解定义了一些类型转换方法。它的核心作用是:
int
、charsequence
、drawable
、uri
),而无需手动将其包装成 textresource
或 imageresource
。int
类型的资源 id 绑定给一个需要 textresource
的属性时,converttextresource(int id)
方法会自动被调用,完成类型转换。qrcodetokeninfo
、orderbean
和 iqiyiorderbean
的转换方法,用于从这些数据模型中提取二维码 url。databindingnavigationutil
是一个工具类,专门用于处理与导航相关的逻辑。它的核心作用是:
openfragment
等方法,用于在不同的上下文(activity
或 fragment
)中打开新的 fragment。kuwovippayfragment
, ximalayaloginfragment
: 创建并返回特定媒体源的 vip 支付或登录 fragment。buildximalayapaydialog
: 根据喜马拉雅媒体项的购买类型,构建并返回一个定制的购买弹窗对话框。ximalayabuyfragment
: 根据登录状态和媒体项的购买类型,决定并返回正确的 fragment(登录、vip 购买或专辑购买)。showrecognizedialog
用于显示正在识别歌曲的弹窗,switchlrcfragment
用于切换歌词视图。fragmentcontextwrapper
中获取实际的 fragment
对象,以及处理上下文的层级关系。这使得导航工具类可以在不同类型的上下文中通用。提供的这五个类是为了实现一套强大、灵活且易于维护的数据绑定和ui构建框架,尤其是在处理多媒体应用中常见的复杂ui和动态数据时。这种设计模式遵循了许多软件工程的最佳实践,例如关注点分离、模块化和代码复用。
以下是详细解释为什么要这样设计的理由:
1. 关注点分离 (separation of concerns)
databindingconvertor.java
和 bindingresourceutil.kt
: 这两个类负责将后端数据模型(如 mediametadatacompat
、qrcodetokeninfo
)转换为ui所需的资源类型(如 textresource
、imageresource
)或特定的文本格式(如播放速度字符串、二维码url)。它们将数据处理和格式化的逻辑从视图层(xml布局)和视图模型层中分离出来,使得数据模型可以保持简洁,而不必包含与ui呈现相关的复杂逻辑。databindingadapter.kt
: 这个类专门负责将数据绑定到实际的ui组件上。它处理诸如图片加载(使用glide)、设置圆角、动态调整布局、添加动画等所有与视图操作相关的细节。这使得xml布局文件只专注于声明性的ui结构,而不需要包含任何实现细节。outlineprovider.kt
: 这个类将view的轮廓裁剪逻辑独立出来,专门用于为view设置不同类型的圆角。这使得圆角效果可以被复用,并且与view的背景、内容等其他属性完全解耦。2. 代码复用和模块化
databindingconvertor.java
和 bindingresourceutil.kt
: 许多ui元素(如播放速度图标、下载按钮图标)在应用的多个地方都会用到。将这些转换逻辑集中在这两个工具类中,可以避免在每个地方都重复编写相同的 when
或 if-else
语句来判断状态和选择资源。databindingadapter.kt
: 通过创建自定义的 bindingadapter
,可以在多个xml布局文件中复用相同的ui绑定逻辑。例如,setimageurlwithradius
方法可以用于应用中任何需要加载带圆角图片的 imageview
,而无需在每个布局或 fragment
中手动调用 glide。outlineprovider.kt
: 同样的,如果多个 view 需要相同的圆角效果(例如,所有专辑封面都需要四个角的圆角),只需在 xml 中通过数据绑定设置 outlineprovider
即可,无需在每个地方都创建新的 viewoutlineprovider
对象。3. 易于维护和扩展
bindingresourceutil.kt
中的 playbackspeedicon
方法即可,而无需在多个文件中寻找和修改代码。要调整图片加载逻辑,只需修改 databindingadapter.kt
中的 glide 相关方法。databindingnavigationutil.kt
将所有复杂的导航逻辑(例如,根据媒体项类型决定打开哪个支付或登录fragment)封装在一个地方。这使得在需要修改导航流程时,开发者只需关注这个工具类,而不是在不同的 activity
或 fragment
中修改分散的 startactivity
或 begintransaction
代码。bindingresourceutil.kt
中的许多方法都通过判断 mediaid
来适配不同的媒体源,并返回相应的资源,这使得应用能够轻松地处理多媒体源带来的差异性。到此这篇关于android 自定义binding adapter实战应用的文章就介绍到这了,更多相关android 自定义binding adapter内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论