-
Notifications
You must be signed in to change notification settings - Fork 208
TableView构建列表 Step2
完整Demo:TableViewDemo.lua
TableView是MomoLua中对列表布局的封装,它的使用包括View和Adapter两个部分。其中TableView决定整个列表的显示位置,大小。Adapter说明数据(Data)和单元视图(ItemView)的绑定关系,以及每个单元视图(ItemView)的渲染方式。
TableView继承自View,布局方式和View相同,此处主要介绍TableView的部分常用方法。
--tableview初始化
tableView = TableView(true, true)
:width(MeasurementType.MATCH_PARENT):height(MeasurementType.MATCH_PARENT);
--下拉刷新事件回调
tableView:setRefreshingCallback(
function()
print("开始刷新")
System:setTimeOut(function()
--2秒后结束刷新
print("结束刷新了")
tableView:stopRefreshing()
end, 2)
end)
--上拉加载事件回调
tableView:setLoadingCallback(function()
print("开始加载")
System:setTimeOut(function()
--2秒后结束加载
print("结束加载")
tableView:stopLoading()
--已加载全部
tableView:noMoreData()
end, 2)
end)
local adapter = initAdapter();--adapter初始化方法,具体实现稍后会介绍
tableView:adapter(adapter);
window:addView(tableView);
在以上代码中,我们新建一个TableView,并使其充满整个window,设置了其在下拉刷新和上滑加载更多的回调方法。
在以上新建TableView的构造方法中,两个true控制下拉刷新和加载更多开关打开。打开后在交互中如果触发相关操作,会直接回调上边绑定的回调方法。
setRefreshingCallback(func callback) | 设置下拉刷新回调 |
setLoadingCallback(func callback) | 设置加载更多回调 |
stopRefreshing | 停止刷新动画 |
stopLoading | 停止加载动画 |
noMoreData | 没有更多数据,之后上拉不会再加载 |
adapter(Adapter adapter) | 绑定适配器 |
列表视图(TableView)可以看做是一组View的集合,但是TableView并不自己控制这些子View的创建以及每个子View中数据的显示,因此,它需要一个中间类来协助他完成对于子View的创建和控制,这个类就是Adapter(适配器)。
我们尝试来逆向思考,如果Adapter作为TableView管理子View的"管家",TableView将会向Adapter"索取"什么功能。首先肯定是要获知一共有多少个子View需要显示,其次,每个子View是什么样的,最后,每个子View中的业务数据如何显示。
围绕这三个要素,我们看以下代码:
-- item类型枚举
local TYPE_CELL = {
TEXT = "TYPE_CELL_TEXT",
IMG = "TYPE_CELL_IMG"
}
--初始化适配器
local function initAdapter()
-----------------------TableViewAutoFitAdapter------------------------------
--adapter = TableViewAutoFitAdapter();--根据布局高度自适应
-----------------------TableViewAdapter-------------------------------------
adapter = TableViewAdapter();
---------TableViewAdapter需自行计算item高度,并在heightForCell方法中返回---------
adapter:heightForCell(function(section, row)
return 120
end)
-- 组数,一维list返回1
adapter:sectionCount(function()
return 1;
end);
------------------------------------ 子View个数 ------------------------------
-- 返回tableview中子View的个数,一维时行数取决于datas大小,和section无关
adapter:rowCount(function(section)
if datas == nill or #datas == 0 then
return 0;
else
return #datas;
end
end);
------------------------------------ 子View类型 ------------------------------
-- 返回当前位置子View的类型标识,一维时取决于position对应的data,和section无关
adapter:reuseId(function(section, position)
local theme = datas[position].theme;
local type = nil;
if theme then
if theme == 101 then
-- 布局一:显示文本
type = TYPE_CELL.TEXT;
elseif theme == 201 then
-- 布局二:显示图片
type = TYPE_CELL.IMG;
end
end
return type;
end);
------------------------------------ 创建子View ------------------------------
-- 初始化指定类型:TYPE_CELL.TEXT的子View(仅描述View,不描述业务数据的绑定关系)
adapter:initCellByReuseId(TYPE_CELL.TEXT, function(cell)
cell.rowContainer = View():width(MeasurementType.MATCH_PARENT)
:height(MeasurementType.WRAP_CONTENT)
:setGravity(Gravity.CENTER);
cell.tv = Label():fontSize(16)
:textAlign(TextAlign.CENTER)
:setGravity(Gravity.CENTER);
cell.rowContainer:addView(cell.tv);
cell.contentView:addView(cell.rowContainer);
end);
-- 初始化指定类型:TYPE_CELL.IMG的子View(仅描述View,不描述业务数据的绑定关系)
adapter:initCellByReuseId(TYPE_CELL.IMG, function(cell)
cell.rowContainer = View():width(MeasurementType.MATCH_PARENT)
:height(MeasurementType.WRAP_CONTENT)
:setGravity(Gravity.CENTER);
cell.iv = ImageView():width(60):height(60)
:cornerRadius(45)
:contentMode(ContentMode.SCALE_TO_FILL)
:setGravity(Gravity.CENTER);
cell.rowContainer:addView(cell.iv);
cell.contentView:addView(cell.rowContainer);
end);
-------------------------------- 绑定子View与业务值 --------------------------
-- 描述指定类型:TYPE_CELL.TEXT的子View在指定位置上与业务数据的绑定关系
adapter:fillCellDataByReuseId(TYPE_CELL.TEXT, function(cell, section, row)
cell.tv:text(datas[row].desc);
end);
-- 描述指定类型:TYPE_CELL.IMG的子View在指定位置上与业务数据的绑定关系
adapter:fillCellDataByReuseId(TYPE_CELL.IMG, function(cell, section, row)
cell.iv:image(datas[row].img_url);
end);
-------------------------------- 绑定子View点击事件 --------------------------
-- 设置指定类型:TYPE_CELL.TEXT的子View在点击时的回调
adapter:selectedRowByReuseId(TYPE_CELL.TEXT, function(cell, section, row)
print("点击了:" .. TYPE_CELL.TEXT .. "-" .. tostring(row));
end);
-- 设置指定类型:TYPE_CELL.IMG的子View在点击时的回调
adapter:selectedRowByReuseId(TYPE_CELL.IMG, function(cell, section, row)
print("点击了:" .. TYPE_CELL.IMG .. "-" .. tostring(row));
end);
return adapter;
end
【注】 上述代码中我们会经常看见一个参数section以及对section的初始化函数sectionCount(func),但在各个方法中却没有使用section参与运算,其实section在sdk中的设计初衷是描述一个二维列表,即section代表组,每个组下又有属于这个组的子列表。但在实际业务中,以一维列表居多,部分非一维列表也可以转换成一维列表开发,因此多数情况下sectionCount的回调将固定回1,且出现在其他方法中时无实意。
围绕Adapter需要提供的功能,我们看到以下核心方法:
rowCount(function(section) callback) | 子View个数,在回调callback中返回 |
reuseId(function(number section, number row) callback) | 子View的样式类型,部分业务中可能不同位置子View样式不同 |
initCellByReuseId(string reuseId, function(table cell) callback) | 根据子View样式类型初始化子View |
fillCellDataByReuseId(string reuseId, function(table cell, number section, number row)) | 根据子View样式类型以及指定的在列表中的位置,描述view与业务数据的绑定关系 |
selectedRowByReuseId(string reuseId, function(table cell, number section, number row)) | 设置子View点击回调 |
adapter:reuseId(function(section, position)
local theme = datas[position].theme;
local type = nil;
if theme then
if theme == 101 then
-- 布局一:显示文本
type = TYPE_CELL.TEXT;
elseif theme == 201 then
-- 布局二:显示图片
type = TYPE_CELL.IMG;
end
end
return type;
end);
------------------------------------ 创建子View ------------------------------
adapter:initCellByReuseId(TYPE_CELL.TEXT, function(cell)
end);
adapter:initCellByReuseId(TYPE_CELL.IMG, function(cell)
end);
当我们在reuseId(func)方法中返回多少种reuseId,与reuseId相关的方法就各自要多少种"重载",例如上述代码中返回两种reuseId:TYPE_CELL.TEXT和TYPE_CELL.IMG,那么用来创建子view的方法initCellByReuseId就有两个"重载"(其实不是重载,sdk的底层用委托的方式实现调用)。在每个方法中,我们已经只需要初始化参数表第一个常量reuseId所代表类型的view就行了。可以看做我们从方法层实现了业务的拆分,类似的fillCellDataByReuseId等方法也是如此。
同时,在initCellByReuseId中,sdk在参数表中传入了cell变量,这个cell是table类型,其中已经提供了View类型的属性contentView,我们在这个方法中自己创建的view最后要添加到这个原始的cell.contentView中去,sdk最终显示的时候会直接显示cell的contentView,我们的view作为contentView的子View显示。需要注意的是,在绑定view与业务数据的时候,fillCellDataByReuseId的回调中再次返回了cell用于让我们为view设置值,此时我们能获取到contentView,但是无法获取到我们要操作的自定义view了,所以在initCellByReuseId的时候,我们不仅要把自己创建的view添加到cell.contentView,也要把自己稍后要操作的view同样委妥到cell,例如上述initCellByReuseId中创建的Label委托给cell.tv,然后再fillCellDataByReuseId中用cell.tv取出后再进行text()赋值操作。
如果我们的业务中,列表中的所有view都是同一种样式,也就是reuseId(func)只有一种返回值时,我们有相关的一系列方法用来简化上述的代码:
-- 初始化适配器
local function initAdapter()
adapter = TableViewAdapter();
-- 组数,一维list返回1
adapter:sectionCount(function ()
return 1;
end);
------------------------------------ 子View个数 ------------------------------
-- 返回tableview中子View的个数,一维时行数取决于datas大小,和section无关
adapter:rowCount(function(section)
if datas == nill or #datas == 0 then
return 0;
else
return #datas;
end
end);
------------------------------------ 创建子View ------------------------------
adapter:initCell(function(cell)
cell.rowContainer = View():width(MeasurementType.MATCH_PARENT):height(120);
cell.tv = Label():fontSize(16)
:textAlign(TextAlign.CENTER)
:setGravity(Gravity.CENTER);
cell.rowContainer:addView(cell.tv);
cell.contentView:addView(cell.rowContainer);
end);
-------------------------------- 绑定子View与业务值 --------------------------
adapter:fillCellData(function(cell, section, row)
cell.tv:text(datas[row].desc);
end);
-------------------------------- 绑定子View点击事件 --------------------------
adapter:selectedRow(function(cell, section, row)
print("点击了:" .. tostring(row));
end);
return adapter;
end
也就是说,我们的adapter可以根据业务不同,使用一下两组方法进行设置
Item多种类型 | Item单一类型 |
reuseId(function(number section, number row) callback) | |
initCellByReuseId(string reuseId, function(table cell) callback) | initCell(function(table cell) callback) |
fillCellDataByReuseId(string reuseId, function(table cell, number section, number row)) | fillCellData(function(table cell, number section, number row)) |
selectedRowByReuseId(string reuseId, function(table cell, number section, number row)) | selectedRow(function(table cell, number section, number row)) |
上述例子datas的数据格式:
datas = {
{
theme = 101,
desc = "Apple"
},
{
theme = 101,
desc = "Pear"
},
{
theme = 201,
img_url = "http://img0.imgtn.bdimg.com/it/u=383546810,2079334210&fm=26&gp=0.jpg"
},
{
theme = 201,
img_url = "http://img0.imgtn.bdimg.com/it/u=383546810,2079334210&fm=26&gp=0.jpg"
},
{
theme = 101,
desc = "Orange"
},
{
theme = 201,
img_url = "http://img0.imgtn.bdimg.com/it/u=383546810,2079334210&fm=26&gp=0.jpg"
}
}