-
非常简单的js双向数据绑定框架(三):js model黑科技
2015-06-11 10:52:45想要改变model数据,直接vm.property = xxx就好,不用去model.set(‘property’,xxx)了! 实现 今次主要借鉴avalon“劫持”setter,getter的方法,链接: avalon简化版解读 虽说是简化过的avalon,还是挺...初衷
之前我们要在js域更新model,需要这样:
model.set('name', 'sub');
这实在太土了。。。
我们希望像angularjs一样,直接:$scope.name = 'sub';
然后bong, 视图就会更新!这样的黑科技必定是极好的。
目标
- 完成model更新黑科技
- 200行以内完成
我们希望html长成这样:
<head> <title>avalon like mvvm framework</title> <script src="frame.js"></script> <script src="app.js"></script> </head> <body isi-controller="MainController"> <h1> avalon lik mvvm framework </h1> <p id="my-p">{{width}}</p> </body>
{{width}}即绑到p里面,又绑到isi-css-width里面,又绑了个isi-click
不用写data-bind=xxx属性了,很像angularjs有没有!
js我们希望长这样:var model = MVVM.define("MainController", function(vm) { vm.width = 150; vm.click = function() { vm.width = parseFloat(vm.w) + 10; }; }); MVVM.scanTag();
想要改变model数据,直接vm.property = xxx就好,不用去model.set(‘property’,xxx)了!
实现
今次主要借鉴avalon“劫持”setter,getter的方法,链接:avalon简化版解读
虽说是简化过的avalon,还是挺难读的。
整理下思路,主要两大点:
1. vm对象 –> vModel对象,劫持vm各个属性的set,get方法
2. scanTag() –> 遍历dom树找关键字,去vModel找求值函数,注册到订阅者列表其中去vModel这一点不是很清楚,这几天一定要搞清,自己mock一个出来
第一版,劫持getter, setter
之前提到,关键步骤有两个,
1. 劫持vm中各个属性的getter, setter
2. scanTag,在这个过程中对vm对象求值,对vm对象handler(一般是去改视图)贴一下关键代码和注释:
/*----------- define ------------------*/ var MVVM = function() {}; MVVM.define = function(name, factory) { var scope = {}; factory(scope); var model = modelFactory(scope); factory(model); model.$id = name; return vModels[name] = model; } /* @param scope {object}: vm工厂方法生成的对象 * @return vModel {object}: scope各property的set,get方法被劫持了后的对象。 */ function modelFactory(scope) { var vModel = {}, originalModel = {}, accessingProperties = {}; // originalModel保存vm的属性, accessingProperties保存属性的accessor(及属性的订阅者) for(var prop in scope) { resolveAccess(prop, scope[prop], originalModel, accessingProperties); } // 关键!劫持需要access的属性的set,get方法! vModel = Object.defineProperties(vModel,withValue(accessingProperties)); // vModel.$id = generateId(); vModel.$accessors = accessingProperties; vModel.$originalModel = originalModel; vModel[SUB_NAME] = []; return vModel; }
MVVM.scanTag = function(element, vModel) { // 假使通过parse, 取到了包含{{}}的元素<p> var myP = document.getElementById('my-p'); executeBindings([{ filters: undefined, element: myP, nodeType: 3, type: "text", value: "width" }], vModels["MainController"]); }; /* @param bindings {array} : bindings * @param vModel {object} : vModel * @func: exec bindingHandlers of text */ function executeBindings(bindings, vModel) { bindings.forEach( function(data){ bindingHandlers[data.type](data,vModel); //bindingHandlers往下较长,就不贴了 //主要功能是生成binding object的求值函数,handler函数,注册事件 }); }
第二版
第一版有问题,没有事件啊,click不支持的话就太土了。
所以第二版引入事件注册。
简单的说,给每个vm的属性的setter,getter里,都搞一个subscriber数组,记录set这个属性时得通知哪些人,同时,在get这个属性时,收集订阅者。
这个过程有点绕,我也是一步步设断点才知道怎么运行的:
scanTag的时候,要生成每个属性的求值函数和handler函数,生成完之后,做注册。
注册的时候,是对整个binding object操作,首先去调用求值函数,这样有两个作用:
1. 调用了get方法,从而把这个binding object加到这个属性的subscribers数组里了
2. get到值之后去set, 从而在scanTag的过程中把vm里的property搞到各个html里去理清这些思路后,我们来重构avalon的代码,因为原码命名实在太乱了,词不达意,导致读码的时候思路也很乱。 代码写完我会上传到github
代码写完了,主要是以binding object和accessor为中心,accessor保有有关的bindings,setter,getter里用到binding.evaluter与binding.handler来处理view更新。binding.evalutor, handler有scanTag()过程获取
-
long mode 分页_GitHub - skyloma/BRV: Android最强RecyclerView库, 不仅仅是简洁的双向数据绑定框架, ...
2021-01-13 05:53:20特性全网最少的代码Kotlin的特性通用适配器, 无需继承快速实现常见需求刷新还是添加数据都无闪屏需求用最少的代码实现开发过程中的常见需求:多类型单一数据模型一对多多数据模型添加头布局和脚布局点击/长按事件过滤...介绍
代码量最少, 使用便捷, 非侵入式, 满足常见需求, 同时方便扩展和自定义.
特性
全网最少的代码
Kotlin的特性
通用适配器, 无需继承
快速实现常见需求
刷新还是添加数据都无闪屏
需求
用最少的代码实现开发过程中的常见需求:
多类型
单一数据模型一对多
多数据模型
添加头布局和脚布局
点击/长按事件
过滤重复点击
分组
展开折叠
顶部附着
支持所有的LayoutManager的分割线/均布间隔
切换模式
选择模式
全选/取消全选/反选
单选
监听选择事件
拖拽移动
侧滑删除
下拉刷新 | 上拉加载 (PageRefreshLayout)
多状态缺省页 (PageRefreshLayout)
自动分页加载 (PageRefreshLayout)
扩展
伸缩布局 (FlexboxLayoutManager)
自动化网络请求 (KalleExtension)
该网络请求基于Kalle|RxJava 实现自动化的网络请求
未来将支持:
无限滚动
安装
project 的 build.gradle
allprojects {
repositories {
// ...
maven { url 'https://jitpack.io' }
}
}
module 的 build.gradle
implementation 'com.github.liangjingkanji:BRV:1.2.14'
预览
初始化
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
BindingAdapter.modelId = BR.m // 推荐在Application中进行初始化或使用之前即可
}
}
多类型
不同数据模型
rv.linear().setup {
addType(R.layout.item_1)
addType(R.layout.item_2)
}.models = data
一对多数据模型
开发中常常遇到一个列表, 列表每个数据对应的一个字段来判断itemType
addType{
// 使用年龄来作为判断返回不同的布局
when (age) {
23 -> {
R.layout.item_1
}
else -> {
R.layout.item_2
}
}
}
分割线
快速实现分割线
fun RecyclerView.divider(
@DrawableRes drawable: Int,
@RecyclerView.Orientation orientation: Int = RecyclerView.VERTICAL,
block: ((Rect, View, RecyclerView, RecyclerView.State) -> Boolean)? = null
)
监听事件
监听事件需要先添加想要监听的view
fun addClickable(@IdRes vararg id: Int)
// 过滤500毫秒内重复点击
fun addFastClickable(@IdRes vararg id: Int)
// 区别于上面函数不过滤重复点击事件
fun addLongClickable(@IdRes vararg id: Int)
// 长按事件
事件回调
onClick{
}
// 点击事件
onLongClick{
}
// 长按点击事件
onBind {
false
}
onBind属于onBindViewHolder事件监听.
如果你需要自己去设置数据或绑定事件就使用该函数. 返回值决定是否用框架内部默认的Databinding绑定布局. false表示不绑定.
更简单的写法示例
rv.linear().setup {
addType{
R.layout.item_1
}
onClick(R.id.item, R.id.user_portrait){
}
onLongClick(R.id.item){
}
}.models = data
头布局/脚布局
头布局和脚布局在rv中算作一个item, 所以计算position的时候应当考虑其中.
操作头布局
fun addHeader(view: View)
fun removeHeader(view: View)
fun clearHeader()
fun isHeader(@IntRange(from = 0) position: Int): Boolean
操作脚布局
fun addFooter(view: View)
fun removeFooter(view: View)
fun clearFooter()
fun isFooter(@IntRange(from = 0) position: Int): Boolean
获取数据
val headerCount: Int
val footerCount: Int
切换模式
切换模式相当于会提供一个回调函数遍历所有的item, 你可以在这个回调函数里面依次刷新他们.
常用于切换选择模式.
fun toggle()
// 切换模式
fun getToggleMode(): Boolean
// 范围当前出何种切换模式
fun setToggleMode(toggleMode: Boolean)
// 设置切换模式, 如果设置的是当前正处于的模式不会触发回调
回调函数
onToggle { type, position, toggleMode -> // 类型 位置 切换布尔值
// 在这里可以给item刷新成选择模式
}
// 切换完成
onToggleEnd {
// 例如切换工具栏为选择模式
}
选择模式
fun allChecked()
// 全选
fun allChecked(isAllChecked: Boolean)
// 全选或者全部取消全选
fun clearChecked()
// 取消全选
fun reverseChecked()
// 反选
fun setChecked(@IntRange(from = 0) position: Int, checked: Boolean)
// 设置某个item的选择状态
fun toggleChecked(@IntRange(from = 0) position: Int)
// 切换某个item的选择状态
fun getCheckedModels(): List
fun setCheckableType(@LayoutRes vararg checkableItemType: Int)
// 设置哪些type允许进入选择状态
val checkedCount: Int
拖拽/侧滑
只支持拖拽移动和侧滑删除
步骤:
开启ItemTouchHelper支持
数据模型继承ItemModel
自定义扩展
BindingAdapter提供一个字段来开启ItemTouchHelper支持
var touchEnable = false // 默认关闭
然后数据模型要求继承ItemModel, 根据需求重写函数.
示例:
data class Model(val name: String) : ItemModel() {
override fun getDrag(): Int {
return UP or DOWN
}
override fun getSwipe(): Int {
return RIGHT or LEFT
}
}
RIGHT or LEFT是控制拖拽和侧滑的方向(侧滑不支持UP/DOWN)的常量.
拖拽移动item会自动改变数据模型在数据集合中的位置.
扩展功能
如果想要扩展ItemTouchHelper可以给BindingAdapter的变量itemTouchHelper赋值
rv.linear().setup {
addType(R.layout.item)
touchEnable = true
itemTouchHelper = ItemTouchHelper(object : DefaultItemTouchCallback(this) {
// 这里可以重写函数
})
}.models = data
通过给view打上tag标签 swipe 可以控制侧滑将会移动的视图.
android:layout_width="wrap_content"
android:layout_height="80dp"
android:orientation="horizontal"
android:tag="swipe"/>
缺省页
内部依赖 StateLayout, 该库支持任意 Activity | Fragment | View 实现缺省页功能.
主要特点
全局配置
单例配置
生命周期(可以加载动画或者处理事件)
支持activity/fragment/view替换
支持代码或者XML实现
无网络情况下showLoading显示错误布局, 有网则显示加载中布局
本库已经依赖, 无需再次依赖. 使用方法见GitHub.
PageRefreshLayout
该布局扩展自 SmartRefreshLayout(该库强烈推荐使用), 支持其所有特性. 本框架拥有最完美的缺省页功能
本库已引入SmartRefreshLayout 'com.scwang.smart:refresh-layout-kernel:2.0.0-alpha-1', 无需再次引入.
针对需要的请求头可以去其开源地址分包引入.
可选配置刷新头布局和脚布局
implementation 'com.scwang.smart:refresh-footer-classics:2.0.0-alpha-1' //经典加载
implementation 'com.scwang.smart:refresh-header-material:2.0.0-alpha-1' //谷歌刷新头
// 以上两种已内置
implementation 'com.scwang.smart:refresh-header-classics:2.0.0-alpha-1' //经典刷新头
implementation 'com.scwang.smart:refresh-header-radar:2.0.0-alpha-1' //雷达刷新头
implementation 'com.scwang.smart:refresh-header-falsify:2.0.0-alpha-1' //虚拟刷新头
implementation 'com.scwang.smart:refresh-header-two-level:2.0.0-alpha-1' //二级刷新头
implementation 'com.scwang.smart:refresh-footer-ball:2.0.0-alpha-1' //球脉冲加载
implementation 'com.scwang.smartrefresh:SmartRefreshHorizontal:1.0.0-andx-1' // 水平
刷新布局要求必须先初始化, 推荐在Application中
SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout -> ClassicsHeader(this) }
SmartRefreshLayout.setDefaultRefreshFooterCreator { context, layout -> ClassicsFooter(this) }
在此基础上增加特性
缺省页
自动分页加载
支持KT特性
代码替换
创建方式
// 1. 代码创建, 通过扩展函数
val page = rv.page()
// 2. 布局包裹
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/page"
app:stateEnabled="true"
android:layout_height="match_parent"
tools:context="com.drake.brv.sample.fragment.RefreshFragment">
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
监听状态
// 下拉刷新
page.onRefresh {
// 这里可以进行网络请求等异步操作
}
// 上拉加载
page.onLoadMore {
// 这里可以进行网络请求等异步操作
}
如果onLoadMore 不调用则上拉加载同样也会回调onRefresh函数, 因为下拉刷新和上拉加载可能都是走的同一个网络请求.
缺省页
触发刷新状态(两者都会回调函数onRefresh)
autoRefesh 这是触发的下拉刷新
showLoading 这是触发的加载缺省页, 当然得先设置loadingLayout(或者读取全局缺省页配置)
refresh 静默刷新, 不会触发任何动画
第2种方式一般用于初次进入页面时候加载数据(产品经理可能有这个要求), 这三种方式都会导致索引index=startIndex重置.
缺省页状态控制
showEmpty()
showError()
showContent
showLoading()
配置全局缺省页
这块代码和StateLayout公用
/**
* 推荐在Application中进行全局配置缺省页, 当然同样每个页面可以单独指定缺省页.
* 具体查看 https://github.com/liangjingkanji/StateLayout
*/
StateConfig.apply {
emptyLayout = R.layout.layout_empty
errorLayout = R.layout.layout_error
loadingLayout = R.layout.layout_loading
onLoading {
// 此生命周期可以拿到LoadingLayout创建的视图对象, 可以进行动画设置或点击事件.
}
}
单例缺省页配置支持两种方式
XML
.....
app:error_layout="@layout/layout_error"
app:empty_layout="@layout/layout_empty"
app:loading_layout="@layout/layout_loading">
代码
page.apply {
loadingLayout = R.layout.layout_loading
emptyLayout = R.layout.layout_empty
errorLayout = R.layout.layout_error
}
默认会使用缺省页, 如果你已经设置了全局缺省页但是此刻不想使用. 可以使用属性|函数: stateEnabled
想要使用缺省页又要求缺省页不遮盖头布局, 头布局请使用CoordinatorLayout实现. 注意使用NestedScrollView会导致rv一次性加载完item内存消耗大.
刷新数据
前面提到 PageRefreshLayout 支持自动分页加载
// 设置分页加载第一页的索引, 默认=1, 触发刷新会重置索引. 如果需要修改在Application设置一次即可
// PageRefreshLayout.startIndex = 1
pageLayout.onRefresh {
// 下拉刷新和上拉加载都会执行这个网络请求, 除非另外设置onLoadMore
get("/path") {
param("key", "value")
param("page", pageLayout.index) // 这里使用框架提供的属性
}.page(this) {
// 该回调函数参数返回一个布尔类型用于判断是否存在下一页, 决定上拉加载的状态. 以及当前属于刷新还是加载更多条目
addData(data){ adapter.itemCount < data.count // 这里是判断是否由更多页, 具体逻辑根据接口定义 }
}
}
这里的网络请求使用的是我开源的另一个项目Net, 支持扩展BRV. GitHub: Net.
扩展
LayoutManager
框架还提供快速创建布局管理器的扩展函数, 上面使用示例
rv.linear().setup {
}
函数
fun RecyclerView.linear(
@RecyclerView.Orientation orientation: Int = VERTICAL,
reverseLayout: Boolean = false
)
fun RecyclerView.grid(
spanCount: Int,
@RecyclerView.Orientation orientation: Int = VERTICAL,
reverseLayout: Boolean = false
)
fun RecyclerView.staggered(
spanCount: Int,
@RecyclerView.Orientation orientation: Int = VERTICAL
)
分隔物
框架提供快速设置分隔物扩展函数
fun RecyclerView.divider(
@DrawableRes drawable: Int, // 分隔物Drawable
@RecyclerView.Orientation orientation: Int = RecyclerView.VERTICAL, // LayoutManager的方向
block: ((Rect, View, RecyclerView, RecyclerView.State) -> Boolean)? = null // getItemOffset回调用于设置间隔
)
示例
rv.linear().divider(R.drawable.divider_horizontal_padding_15dp).setup {
}
对话框
通过扩展函数快速给对话框创建列表
Dialog(context).setAdapter(bindingAdapter)
-
Android中的数据绑定框架DataBinding(对比AngularJS双向数据绑定很好理解)
2016-10-16 18:48:12今天来了解一下Android最新给我们带来的数据绑定...数据绑定框架给我们带来了更大的方便性,以前我们可能需要在Activity里写很多的findViewById,烦人的代码也增加了我们代码的耦合性,现在我们马上就可以抛弃转自:http://blog.csdn.net/qibin0506/article/details/47393725
今天来了解一下Android最新给我们带来的数据绑定框架——Data Binding Library。数据绑定框架给我们带来了更大的方便性,以前我们可能需要在
Activity
里写很多的findViewById
,烦人的代码也增加了我们代码的耦合性,现在我们马上就可以抛弃那么多的findViewById
。说到这里,有人可能会有个疑问:我使用一些注解框架也可以不用findViewById
啊,是的,但是注解注定要拖慢我们代码的速度,Data Binding则不会,官网文档说还会提高解析XML的速度,最主要的Data Binding并不是单单减少了我们的findViewById
,更多好处请往下看文章。一、环境
在开始使用新东西之前,我们需要稍微的配置一下环境,这里要求你的Android Studio版本是1.3+,使用eclipse的同学暂时还没有办法使用该框架,请换用Android Studio。还有,在开始之前,请更新你的Support repository
到最新的版本。
万事俱备,那我们就开始搭配环境!新建一个
project
,在dependencies
中添加以下依赖新建
module
,并且在module
的build.gradle文件中添加ok,到现在为止,我们的环境就准备完毕了,下面我们就开始Data Binding的学习啦。
二、Data Binding尝试
在代码开始,我们并不直接进入新东西的讲解,而且以一段代码展现Data Binding的魅力。
首先我们需要一个Java bean
,很简单,一个学生类。
再来看看我们布局文件怎么写:
可以看到我们的xml布局和以前还有有一定的差别的,但是差别也不是很大。
最后来看看Activity
怎么写。
Activity
的代码非常简单,就添加了两行代码,而且,值得注意的是:我们并没有findViewById
然后再去setText
。
这段小代码运行的结果大家可能已经猜到了,就是在界面上显示loader
和山东莱芜
两句话。)
在看完小实例后,大家是不是感觉棒棒哒? 没有了之前的find控件,没有了setText,
Activity
代码更加简洁明了!
下面开始,我们进入Data Binding的学习!三、 初始Data Binding
上面的代码算是带领我们进入了Data Binding的世界,那我们先从布局文件开始入手Data Binding吧。再来看看上面的布局文件。
我们的根节点变成了layout
,在layout
的子节点中分成两部分,第一部分是data
节点,第二部分才是我们之前的根节点,在data
节点下我们又定义了一个variable
,
从名称上看,这应该是一个变量,变量的名称是stu
,类型是org.loader.androiddatabinding.Student
,这类似我们在java文件中这么定义:
Student
完整的包名,一个还好,如果这里我们需要多个Student
呢?要累死? NO,NO,NO,我们还可以向写java文件那样导入包。
这样写,就类似于java的import org.loader.app2.Student;...Student stu;...
既然变量我们定义好了,那该怎么使用呢?还是看上面的xml文件。
恩,注意看两个TextView
的android:text
,它的值是一个以@
开始,以{}包裹的形式出现,而内容呢?是stu.name
。stu就是我们上面定义的variable
,
name还记得吗?是我们Student
类中的一个变量。其实这里就会去调用stu.getName()
方法。
好了,很快,我们就入门了Data Binding,下面让我们来多定义几个变量试试看。
来看看定义的变量,多了好几个,有一个String
类型的变量我们并没有导包,这里说明一下,和在java里一样,java.lang
包里的类,我们是可以不用导包的,再往下,一个boolean
和int
类型的变量,都是java基本类型的,所以说嘛,在这里定义变量,你就想成是在java里定义就ok。
再来看看这几个TextView
,第二个,我们直接使用@{str}
来为android:text
设置成上面定义个str
的值,继续往下要注意了,我们使用了android:text="@{String.valueOf(num)}"
来设置了一个
int
类型的变量,大家都知道我们在给android:text
设置int
类型的值时一定要转化为String
类型,要不它就认为是资源文件了,这里我们还学到了一点,在xml中,我们不仅可以使用变量,而且还可以调用方法!四、 变量定义的高级部分
在上面,我们学会了如何去在xml中定义变量,但是不知道你发现没?我们没有定义像List
、Map
等这样的集合变量。那到底能不能定义呢?答案肯定是可以的,而且定义的方式和我们上面的基本一致,区别就在于我们还需要为它定义key的变量,例如:
这段代码比较长,但是我们仅关心那几个集合和数组,可以看到我们定义集合和定义普通变量一样,只不过这里我们还指定了一些的泛型,例如:
ArrayList<String>
。
下面我们还为下面使用这些集合准备了几个key,也都是变量。
继续看看怎么使用,和我们在java中使用不同,这里都是以:集合变量名[key]的形式使用,如果你的key是一个字面字符串可以使用反引号,也可以使用转义后的双引号。恩,这里也没有什么可以说的了,大家多看几遍就掌握了,都是概念性的东西,记住就ok。五、在java代码中使用
前面定义了这么多变量,但是我们还没有给他们赋值!在哪赋值呢?肯定是在java代码中使用了,大部分情况我们还是在Activity
中去使用它,以前我们都是在onCreate
方法中通过setContentView
去设置布局,但现在不一样了,现在我们是用过DataBindingUtil
类的一个静态方法setContentView
设置布局,同时该方法会返回一个对象,什么对象?这个对象有点特殊,它是一个自动生成的类的对象,看下面:
看到ActivityMainBinding
了吗?就是它!那自动生成有什么规则了没?当然有了,记好了:将我们布局文件的首字母大写,并且去掉下划线,将下划线后面的字母大写,加上Binding组成。
看看上面的类,是不是符合这个规则。继续看看这个对象哪来的,是通过
返回的,DataBindingUtil.setContentView的两个参数分别是当前Activity
和布局文件。那接下来,就是我们关心的给变量赋值了。
一连串的binding.setXXX,这个XXX是什么呢?就是我们在xml中定义的那些变量首字母大写了!也没好好说的吧,多看几遍。
六、 表达式
短暂的幸福时光,我们还是要告别java代码了,继续回到xml中,这一块,我们来学习一下表达式,什么?这玩意在xml中还支持表达式!
还记得上面我们定义了一个boolean的变量没有用到,这里我们就用到了,看好android:text
,这里是一个三元表达式,如果error是true,则text就是error,否则是ok。这里还支持null合并操作,什么是null合并,相信看一眼你就知道了
简单解释一下,如果str是null,text的值就是str本身,否则就是”not null”。
它还支持一下表达式:- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <=
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- Ternary operator ?:
但是它不支持一下表达式:
- this
- super
- new
- Explicit generic invocation
七、 其他遗漏点
说到这里,xml中的事情基本算完了,但是还有几个小地方没有说,顺便说一下。
1. 设置别名
假如我们import了两个相同名称的类咋办?别怕,别名来拯救你!例如:
- 自定义Binding名称
还记得系统为我们生成好的那个binding类名吗?如果只能使用那样的是不是有点太暴力了?好在google对我们还算友好了,允许我们自定义binding名称,定制名称也很简单,就是给data一个class字段就ok。
例如:
那么:DataBindingUtils.setContentView返回的binding类就是:
你的应用包名.Custom
。八、事件绑定
大家都知道,在xml中我们可以给button
设置一个onClick
来达到事件的绑定,现在DataBinding也提供了事件绑定,而且不仅仅是button
。
来看一下:
定义了一个EventHandlers
类型的handlers
变量,并在onClick的时候执行EventHandlers
的handleClick
方法。
继续看看EventHandlers是怎么写的。
很简单,就是简单的
Toast
了一下,这里要注意的是,handlerClick
方法需要一个View
的参数。九、 数据对象
我们学会了通过binding为我们的变量设置数据,但是不知道你有没有发现一个问题,当我们数据改变的时候会怎样?数据是跟随着改变呢?还是原来的数据呢?这里告诉你答案:很不幸,显示的还是原来的数据?那有没有办法让数据源发生变化后显示的数据也随之发生变化?先来想想ListView
是怎么做的,ListView
的数据是通过Adapter
提供的,当数据发生改变时,我们通过notifyDatasetChanged
通过UI去改变数据,这里面的原理其实就是内容观察者,庆幸的是DataBinding也支持内容观察者,而且使用起来也相当方便!BaseObservable
我们可以通过Observable的方式去通知UI数据已经改变了,当然了,官方为我们提供了更加简便的方式BaseObservable
,我们的实体类只需要继承该类,稍做几个操作,就能轻松实现数据变化的通知。如何使用呢? 首先我们的实体类要继承BaseObservale
类,第二步在Getter
上使用注解@Bindable
,第三步,在Setter
里调用方法notifyPropertyChanged
,第四步,完成。就是这么简单,下面我们来实际操作一下。
首先定义一个实体类,并继承BaseObservable
观察getName方法,我们使用了@Bindable
注解,观察setName,我们调用了notifyPropertyChanged
方法,这个方法还需要一个参数,这里参数类似于R.java
,保存了我们所有变量的引用地址,这里我们使用了name。
再来看看布局文件。
不多说了,我们给TextView
设置了文本,还有点击事件。Activity,
这段代码,首先显示的是loader,当我们点击
TextView
时,界面换成qibin。ObservableFields家族
上面使用BaseObservable
已经非常容易了,但是google工程师还不满足,继续给我们封装了一系列的ObservableFields
,这里有ObservableField
,ObservableBoolean
,ObservableByte
,ObservableChar
,ObservableShort
,ObservableInt
,ObservableLong
,ObservableFloat
,ObservableDouble
,ObservableParcelable
ObservableFields的使用方法就更加简单了,例如下面代码,
很简单,只有三个ObservableField变量,并且没有getter和setter,因为我们不需要getter和setter。
在xml中怎么使用呢?
也很简单,直接使用变量,那怎么赋值和取值呢?这些ObservableField都会有一对get
和set
方法,所以使用起来也很方便了:
也不多说了。
Observable Collections
既然普通的变量我们有了ObservableFields的分装,那集合呢?当然也有啦,来看着两个:ObservableArrayMap
,ObservableArrayList
。使用和普通的Map、List基本相同,直接看代码:
在布局中,使用方式和普通的集合一样,如果看不太懂,可以往上翻博客,看上面的集合是怎么使用的。
在来看java文件,怎么设置数据,
太简单了,简直和List
、Map
使用方法一模一样!!!
十、inflate
不知道大家注意没有,上面的代码我们都是在activity中通过DataBindingUtil.setContentView
来加载的布局的,现在有个问题了,如果我们是在Fragment
中使用呢?Fragment
没有setContentView
怎么办?不要着急,Data Binding也提供了inflate
的支持!
使用方法如下,大家肯定会觉得非常眼熟。
接下来,我们就尝试着在Fragment
中使用一下Data Binding吧。
首先还是那个学生类,Student
继续,activity的布局
activity的代码,
在
onCreateView
中,不同于在Activity中,这里我们使用了DataBindingUtil.inflate方法,接受4个参数,第一个参数是一个LayoutInflater对象,正好,我们这里可以使用onCreateView的第一个参数,第二个参数是我们的布局文件,第三个参数是一个ViewGroup,第四个参数是一个boolean类型的,和在LayoutInflater.inflate
一样,后两个参数决定了是否想Container
中添加我们加载进来的布局。
下面的代码和我们之前写的并无差别,但是有一点,onCreateView
方法需要返回一个View对象,我们从哪获取呢?ViewDataBinding
有一个方法getRoot
可以获取我们加载的布局,是不是很简单?
来看一下效果:十一、 Data Binding VS RecyclerView
有了上面的思路,大家是不是也会在ListView和RecyclerView中使用了?我们仅以一个RecyclerView来学习一下。
首先来看看item的布局,
可以看到,还是用了那个Student实体,这样得代码,相信你也已经看烦了吧。
那我们来看看activity的。
这里给RecyclerView
设置了一个Adapter,相信最主要的代码就在这个Adapter里。
果然,这个adapter的写法和我们之前的写法不太一样,首先看看ViewHolder,在这个holder里,我们保存了一个ViewDataBinding
对象,并给它提供了Getter
和Setter
方法, 这个ViewDataBinding
是干嘛的?我们稍后去讲。继续看看onCreateViewHolder
,在这里面,我们首先调用DataBindingUtil.inflate
方法返回了一个ViewDataBinding
的对象,这个ViewDataBinding
是个啥?我们以前没见过啊,这里告诉大家我们之前返回的那些都是ViewDataBinding
的子类!继续看代码,我们new了一个holder,参数是肯定是我们的item布局了,继续看,接着我们又把binding设置给了holder,最后返回holder。这时候,我们的holder里就保存了刚刚返回的ViewDataBinding
对象,干嘛用呢?继续看onBindViewHolder
就知道了。
只有两行代码,但是都是我们没有见过的,首先第一行,我们以前都是使用类似binding.setStu
这样方法去设置变量,那这个setVariable
呢? 为什么没有setStu
,这里要记住,ViewDataBinding
是我们之前用的那些binding的父类,只有自动生成的那些子类才会有setXXX
方法,那现在我们需要在ViewDataBinding
中设置变量咋办?这个类为我们提供了setVariable
去设置变量,第一个参数是我们的变量名的引用,第二个是我们要设置的值。第二行代码,executePendingBindings
的作用是干嘛的?官方的回答是:
当数据改变时,binding会在下一帧去改变数据,如果我们需要立即改变,就去调用executePendingBindings
方法。所以这里的作用就是去让数据的改变立即执行。
ok,现在看起来,我们的代码更加简洁了,而且不需要保存控件的实例,是不是很爽? 来看看效果:十二、 View with ID
在使用Data Binding的过程中,我们发现并没有保存View的实例,但是现在我们有需求需要这个View的实例咋办?难道走老路findViewById
?当然不是啦,当我们需要某个view的实例时,我们只要给该view一个id,然后Data Binding框架就会给我们自动生成该view的实例,放哪了?当然是ViewDataBinding
里面。
上代码:
xml中代码没有什么好说的,都是之前的代码,如果在这有点迷糊,建议你还是回头看看上篇博客。需要注意的是,
我们给TextView
了一个id-textView
。
activity,
通过
ViewDataBinding
类的实例直接去获取的。只要我们给了view一个id,那么框架就会在ViewDataBinding中自动帮我们保存这个view的实例,变量名就是我们设置的id。
十三、 自定义setter
想想这样的一种情景,一个ImageView
需要通过网络去加载图片,那我们怎么办?看似好像使用DataBinding不行,恩,我们上面所学到东西确实不能够解决这个问题,但是DataBinding框架给我们提供了很好的扩展,允许我们自定义setter,那该怎么做呢?这里就要引出另一个知识点——BindingAdapter
,这是一个注解,参数是一个数组,数组中存放的是我们自定义的’属性’。接下来就以一个例子学习一下BindingAdapter
的使用。
这里我们增加了一个命名空间app
,并且注意ImageView的app:image
属性,这里和我们自定义view时自定义的属性一样,但是这里并不需要我们去重写ImageView,这条属性的值是我们上面定义的String类型的imageUrl,从名称中看到这里我们可能会塞给他一个url。
activity,
果然在这里我们set了一个url,那图片怎么加载呢?这里就要使用到我们刚才说的BindingAdapter注解了。
我们定义了一个
Utils
类,这个类你可以随便起名,该类中只有一个静态的方法imageLoader,该方法有两个参数,一个是需要设置数据的view,
一个是我们需要的url。值得注意的是那个BindingAdapter
注解,看看他的参数,是一个数组,内容只有一个bind:image
,仅仅几行代码,我们不需要
手工调用Utils.imageLoader,也不需要知道imageLoader方法定义到哪了,一个网络图片加载就搞定了,是不是很神奇,这里面起关键作用的就是BindingAdapter
注解了,来看看它的参数怎么定义的吧,难道是乱写?当然不是,这里要遵循一定的规则,以bind:开头,接着书写你在控件中使用的自定义属性名称。
这里就是
image
了,不信来看。
看看运行结果:
十四、 Converters
Converter是什么呢?举个例子吧:假如你的控件需要一个格式化好的时间,但是你只有一个Date
类型额变量咋办?肯定有人会说这个简单,转化完成后在设置,恩,这也是一种办法,但是DataBinding还给我们提供了另外一种方式,虽然原理一样,但是这种方式使用的场景更多,那就是——Converter。和上面的BindingAdapter
使用方法一样,这也是一个注解。下面还是以一段代码的形式进行学习。
看TextView的text属性,我们需要一个String类型的值,但是这里确给了一个Date类型的,这就需要我们去定义Converter去转换它,
activity,
去给这个Date类型的变量设置值。怎么去定义Converter呢? 看代码:
和上面一样,我们不需要关心这个convertDate在哪个类中,重要的是他的
@BindingConversion
注解,这个方法接受一个Date类型的变量,正好我们的android:text设置的就是一个Date类型的值,在方法内部我们将这个Date类型的变量转换成String类型的日期并且返回。这样UI上就显示出我们转化好的字符串。
看看效果:好了,到这里DataBinding的知识我们就算学习完了,在学完之后发现这东西也没什么难度,学会使用就ok了,而且android官网也有非常详细的文档,
这两篇博客只是系统的去讲解了DataBinding的使用,大家在以后使用的过程中发现忘记怎么用了,可以再来翻看博客或者直接去官方查看。
ok, 那就到这里吧,下次见。参考链接:https://developer.android.com/tools/data-binding/guide.html
-
JS框架双向数据绑定原理及思考
2018-12-27 18:19:06双向数据绑定原理(vue) 如何设计搭建自己的框架 代码暂略,详见,github [地址]https://github.com/yyccmmkk/Bi-directionalDataBindingDemo 什么是单向什么是双向? 单向指是数据从model流向view 双向是在...本文章信息点
- 双向数据绑定原理(vue)
- 如何设计搭建自己的框架
- 代码暂略,详见,github [地址]https://github.com/yyccmmkk/Bi-directionalDataBindingDemo
什么是单向什么是双向?
单向指是数据从model流向view
双向是在单向的基础上数据再从view 流向model。
双向数据绑定原理
相信很多人看过很多双向数据绑定原理相关文章,多半看的一头雾水,看完仍然写不出一个简单的demo 框架,根本不知道为什么要这样做?主要原因是因为没有整理清楚双向数据绑定的需求,因为所有的代码逻辑实现都是围绕需求展开,所以下面会围绕需求展开,再细解如何实现这些需求,从整体逻辑上打通每一环最终实现双向数据绑定框架。所谓万剑归宗、殊途同归,需求是一样的,所以最终的实现也都将大同小异,原理自然而然显现。
简单的说是这样的,
初次模板与数据渲染时,监听数据每个属性的每次调用(通过getter实现),并给其属性添加观察者。监听数据的每个属性变更(通过setter实现),并通知与其相关的观察者,观察者调用自身的更新方法更新view。扫描模板时,找到双向绑定指令,并绑上指定事件,事件触发后更新数据,数据更新触发单向绑定逻辑 view 更新。
说的太简单很多人对细节不清楚,说的细了就是后面的长篇大论了。
相信很多人的疑问有像下面这些的:
观察者只是抽象概念,
- 它具体实现是什么?
- 为什么同一个属性不只一个观察者?
- 又在什么时候添加的?
- 观察者添加到哪了?
- 每个观察者的更新方法又是怎么确定的?
如何设计实现
单向、双向数据绑定是如何实现的呢?假如现在要写一个双向绑定框架要如何设计呢?
单向数据绑定需求可以简单的抽象为:将数据和模板进行某种绑定,数据中的某个属性值发生变化时,属性对应的模板和新值进行处理更新UI(移除旧的UI插入新的UI)。
双向数据绑定需求可以简单的抽象为:在单向的基础上,给特定元素(input 、textarea等)绑定指定事件,并将新值更新到数据,数据更改触发单向数据绑定逻辑,更新UI中相关属性绑定。
通过上面的简单分析,就可以发现"模板"相关需求需要用节点操作来实现,因为需要给特定元素绑定事件,要处理元素中的指令,必定要对模板中每一个节点进行遍历,并对每个节点的属性进行遍历。
下面会逐步分析需求,再将每个需求再细化就可以得到完整的需求,最后实现这些需求。
需求点分析
- 将数据的每一个属性与相对应的模板建立联系,数据属性值变更时view进行更新
- 监听数据所有属性的修改包括值的属性的修改
- 如何找到属性对应的模板
- 如何将数据属性与其对应模板建立链接
- 将数据新值更新到view
- 将特定元素的指定事件回馈到数据,数据被修改,同时与些数据相关view更新
- 监听指定元素的指定事件,并将新值赋值给数据,数据被修改触发view更新逻辑
【1.1解决思路】
数据属性的读写可以通过设置getter setter实现,通过Object.defineProperty()方法设置。 写一个方法递归的处理数据的每个属性不是什么难事。代码暂略,详见,github [地址]https://github.com/yyccmmkk/Bi-directionalDataBindingDemo
【1.2解决思路】
我们需要一个compiler 对模板的每一个节点进行扫描分析,分板当前节点是否有指令(插值表达式、单向数据绑定、双向数据绑定,事件绑定等)。如果有指令,找到当前指令相关的数据属性,当前节点即为'模板'(ps:数据更新时更样的节点)。
【1.3解决思路】
当compiler 对节点分析时如果找到指令,则给该指令相关的属性的更新列表,添加一个watcher实例,该实例包含渲染相关‘模板’(ps:当前扫描到的节点)
【1.4解决思路】
在1.1中对所有数据属性进行监听时,为每一个属性挂载一个对象(实际上是闭包),该对象拥有一个notify方法,还拥有一个值为数组的list 属性,list 中存放的是当前属性相关的 watcher实例,该实例在1.3(模板解析) 时生成并添加进来,该实例拥有一个update 方法,用来更新view; 当属性值变化时,调用相关必包中的notify 方法,该方法遍历list 中的每一个watcher 实例,并调用该实例的update 方法,upadate方法会在1.3(模板解析)时根据指令生成相关update 方法。本demo 中闭包对象是watcher 类来构造,也可以分开。
以上只是粗枝大叶的整个逻辑分析,细节还有很多,比如:指令解析,管道符解析,指令表达式解析,插槽(用以标记更新时插入点),需要销毁节点等,后续整理出来,计划搞一个系列文章,比如模板指令解析如何进行等,本demo 也有点粗造有时间吧整理一下,以后像虚拟dom的可以搞一搞,像生命周期钩子有了大框架要加也不是很困难,等以后细化再更新。
因精力有限。。。今日到此(未完待续)
-
双向数据绑定
2019-07-30 22:57:00双向数据绑定基于MVVM框架,vue属于MVVM框架 MVVM:M等于model,V等于view,即model改变影响view,view改变影响model 1.双向数据绑定 <!-- 双向数据绑定 --> #必须在使用在表单里面 #使用v-model绑定数据,... -
vue中不能去获取data里双向绑定的数据(其他双向数据绑定的框架也是一样)
2017-03-25 20:59:00结果耗费大量时间才意识到这一点,直接将获取双向数据绑定的数据代码删了,一切就都ok了,也可以下班了。。。 苦笑。 最终解释:这种情况是在比较特殊的情况下才会受到影响,一般情况下是可以得到dat... -
JavaScript中双向数据绑定详解
2020-12-11 13:29:49双向数据绑定指的是将对象...但是这并不意味着从零开始实现双向数据绑定就很困难,同样的当我们需要双向数据绑定时并不是只能够选择这些框架其中的一个。双向数据绑定底层的思想非常的基本,它可以被压缩成为三个步骤: -
JavaScript框架之AngularJS学习——双向数据绑定
2017-09-23 17:24:23AngaularJS学习——双向数据绑定 数据绑定是AngularJS框架最优秀的特性之一,能够帮助Web前端开发人员在很大程度上减少对DOM的操作。 数据绑定是AngularJS框架在视图(DOM元素)与作用域之间佳妮的数据同步机制。... -
AngularJS框架中的双向数据绑定机制详解【减少需要重复的开发代码量】
2020-10-20 14:08:51主要介绍了AngularJS框架中的双向数据绑定机制,结合实例形式分析了AngularJS双向数据绑定机制的原理及实现方法,以及减少需要重复开发代码量的优势,需要的朋友可以参考下 -
Vue 框架之键盘事件、健值修饰符、双向数据绑定
2020-10-17 17:58:22主要介绍了Vue 框架之键盘事件、健值修饰符、双向数据绑定问题,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下 -
vue双向数据绑定
2020-08-01 19:31:40什么是双向数据绑定: Vue.js是一个MV VM框架, 即数据双向绑定, 即当数据发生变化的时候, 视图也就发生变化, 当视图发生变化的时候,数据也会跟着同步变化。这也算是Vue.js的精髓之处了。 值得注意的是,... -
数据双向绑定_VUE2.x实现双向数据绑定的核心API
2021-01-06 01:30:53VUE2.x实现双向数据绑定的核心API作者: 郭政鸿 2020/8/1前言: 前端有三大框架Vue、React、Angular, 本文知识由Vue引出, Vue的一个十分重要的特性就是双向数据绑定, 在Vue的1.x 和 2.x版本中, 双向数据绑定依赖的是... -
MVVM 模型是一种实现双向数据绑定的框架设计原理。
2019-03-22 16:02:15MVVM 即是 模型-视图-视图模型,它是一种实现数据双向绑定的模式,是一种框架(VUE)的设计原理。 【模型】指的是后端传递的数据。 【试图】指的是看到的画面。 【视图模型】是 mvvm 模式的核心,它是连接 view和... -
数据双向绑定_MVVM 视图模型双向数据绑定之核心原理
2021-01-12 17:29:30Model-View-ViewModel3 双向数据绑定4 监听 DOM 改变5 监听模型数据变化,数据劫持6 观察者模式,Observer1 概述JS 几个流行的框架 Vuejs、Ember.js、AngularJS 都使用 MVVM 模式,该模式叫做视图模型双向数据绑定,... -
双向数据绑定原理
2019-07-11 15:20:00目前几种主流的mvc(vm)框架都实现了单项数据绑定,而我所理解的双向数据绑定,无非就是在单项数据绑定的基础上给输入元素input textare等添加了change(input)事件,来动态修改model和view,并没有多高深。... -
数据双向绑定_AngularJS学习--双向数据绑定及相关指令
2021-01-12 17:29:311 概念介绍1.1 双向数据绑定数据绑定是AngularJS框架最优秀的特性之一,可以帮助前端开发人员在很大程度上减少对DOM的操作,是一种在视图(DOM元素)与作用域之间建立的数据同步机制。双向数据绑定,即指在界面的操作... -
数据双向绑定_如何用javascript实现双向数据绑定
2021-01-12 17:29:27ext.js等框架逐步过渡到当前的mvvm模式,让前端开发者将注意力从dom操作逐渐解脱出来,专注于逻辑的实现,个人认为开发效率至少提升了1倍,mvvm模式的一个核心便是数据的双向绑定。什么是数据的双向绑定?上面说的是... -
Vue双向数据绑定
2019-04-23 16:43:50目前几种主流的mvc(vm)框架都实现了单向数据绑定,而我所理解的双向数据绑定无非就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,来动态修改model和 view,并没有多高深。... -
js实现双向数据绑定
2017-10-10 17:55:46需求现在的框架都讲究什么...- 双向数据绑定:数据模型(Module)和视图(View)之间的双向绑定。就是我不管修改数据模型的相关数据,还是视图上的数据,相对应的数据也会跟着更新。实现原理主要的就是事件的绑定。 - -
更新 绑定数据_MVVM 双向数据绑定之核心原理
2021-01-12 17:29:29Model-View-ViewModel3 双向数据绑定4 监听 DOM 改变5 监听模型数据变化,数据劫持6 观察者模式,Observer1 概述JS 几个流行的框架 Vuejs、Ember.js、AngularJS 都使用 MVVM 模式,该模式叫做视图模型双向数据绑定,...