xmlplus 组件设计系列之六 - 下拉刷新
“下拉刷新”由著名设计师 Loren Brichter 设计,并应用于 Twitter 第三方应用 Tweetie 中。2010年4月,Twitter 收购 Tweetie 开发商 Atebits 后,该专利归 Twitter 所有。这一章我们就来看看如何实现一个简单的下拉刷新组件。
目标组件分析
和前面在设计组件时的做法一样,我们先想想看最终的成品组件是如何使用的,这需要点想像力。下拉刷新组件看成一个容器组件是合理的,用户可以对容器的内容进行下拉操作。如果用户完成了完整的下拉触发操作,该组件应该会有下拉完成的事件反馈,假定这个事件名为 ready
。根据以上的分析,我们很有可能得到下面的一个该组件的应用示例。
Index: {
xml: `<PullRefresh id='example'>
<h1>Twitter</h1>
<h2>Loren Brichter</h2>
</PullRefresh>`,
fun: function (sys, items, opts) {
sys.example.on("ready", () => console.log("ready"));
}
}
示例中的使用方式是非常简洁的,但我们还漏了一点。当刷新完毕,数据返回后,还要告知组件对象给出刷新成功的提示并且返回初始状态。好了,下面给出的是加入新接口的应用示例。
// 06-01
Index: {
xml: `<PullRefresh id='example'>
<h1>Twitter</h1>
<h2>Loren Brichter</h2>
<button id='refresh'>click</button>
</PullRefresh>`,
fun: function (sys, items, opts) {
sys.example.on("ready", () => {
setTimeout(() => sys.example.trigger("complete"), 3000);
});
}
}
该示例通过定时器模拟了下拉刷新完成后给出刷新成功的提示并且返回初始状态。
布局
现在让我们把目光转移到下拉刷新组件的内部,看看该如何去实现。观察文章开始部分的大图,很自然地我们可以将整个组件划分为三个子组件,如下面的 XML 文档所示。
<div id="refresh">
<Status id="status"/>
<div id="content"></div>
</div>
但为了方便控制,下面的布局可能会好一些。其中组件 page 代表视口,它与其父级 refresh 有相同的宽高尺寸。另外,内容组件 content 与视口组件 page 也具有相同的宽高尺寸。未定义的状态条组件 Status 的高度为 40px
,这样在初始状态下,状态条组件与内容组件需要向上便宜 40 个像素。
// 06-01
PullRefresh: {
css: `#refresh { position: relative; height: 100%; cursor: pointer; overflow-y: hidden; }
#page { height: 100%; transform: translateY(0); }
#status, #content { transform: translateY(-40px); } #content { height: 100%; }`,
xml: `<div id='refresh' xmlns:i='pullrefresh'>
<div id='page'>
<i:Status id='status'/>
<div id='content'></div>
</div>
</div>`,
map: { "appendTo": "content" }
}
状态条的实现
暂且放下 PullRefresh 组件,我们先看看如何实现状态指示条。状态指示条用于显示“下拉刷新”、“松开刷新”、“加载中...”以及“刷新成功”四个状态提示,并且每一时刻仅显示一个状态。对于状态的切换,这里会先用到我们下一章将讲到的路由组件 ViewStack,这里仅需要了解如何使用即可。组件 ViewStack 对外只显示子级的一个子组件,同时侦听一个 switch
事件,该事件的派发者携带了一个切换到的目标对象的名称,也就是 ID。该组件根据这个 ID 来切换到目标视图。下面是状态条组件的完整实现。
// 06-01
Status: {
css: "#statusbar { height: 2.5em; line-height: 2.5em; text-align: center; }",
xml: <ViewStack id="statusbar">
<span id="pull">Pull to refresh...</span>
<span id="release">Release to refresh...</span>
<span id="loading">Loading...</span>
<span id="success">Loading success</span>
</ViewStack>,
fun: function (sys, items, opts) {
var stat = "pull";
function getValue() {
return stat;
}
function setValue(value) {
sys.statusbar.trigger("switch", stat = value);
}
return Object.defineProperty({}, "value", { get: getValue, set: setValue });
}
}
该组件提供一个 value 接口用户设置与获取组件的显示状态。父级组件可根据不同的时机调用该接口。
事件响应
现在让我们来考虑下拉刷新组件操作实现的具体细节。我们需要考虑的事件主要有三个:stouchstart
、touchmove
以及 touchend
。下面是一个实现框架:
// 06-01
PullRefresh: {
fun: function (sys, items, opts) {
var startY, translateY;
sys.page.on("touchstart", function(e) {
// 1 记录下当前触点的坐标以及 page 的偏移
// 2 侦听 touchmove 和 touchend事件
});
function touchmove(e) {
// 1 计算出垂直方向上的偏移
// 2 处理状态条与内容内面跟随触点移动
// 3 根据触点移动的距离显示相当的状态条内容
}
function touchend(e) {
// 1 移除 touchmove 和 touchend 事件
// 2 根据触点移动的距离决定返回原始状态或者进入刷新状态并派发事件
}
}
}
现在我们一个个地来实现上面的三个侦听器。首先是 touchstart
侦听器:
// 06-01
sys.page.on("touchstart", function (e) {
startY = e.targetTouches[0].pageY;
translateY = parseInt(sys.page.css("transform").match(/\d+/)[0]);
sys.page.on("touchmove", touchmove).on("touchend", touchend).css("transition", "");
});
下拉刷新过程中会涉及到动画,对于动画目前一般有两种选择,可以使用 JQuery 动画函数,也可以是 css3,这需要看各人喜好了。这里我们选择使用 css3 来实现。如上所示在下拉开始时需要把动画给禁用掉,否则会对后续造成干扰。
其次是 touchmove
侦听器。该侦听器必需判断出偏移的正负值,当偏移为正时才允许移动页面。
// 06-01
function touchmove(e) {
var offset = e.targetTouches[0].pageY - startY;
if ( offset > 0 ) {
sys.page.css("transform", "translateY(" + (offset + translateY) + "px)");
if (items.status.value != "loading")
items.status.value = offset > 40 ? "release" : "pull";
}
}
最后是 touchend
侦听器。该处理器需要处理三种情况。情况一,如果状态条处理等待数据返回状态,则回弹页面使状态条还处于该状态。情况二,如果用户下拉幅度未超过 40px,则回弹页面使状态条处于隐藏状态。情况三,如果用户下拉幅度超过 40px,则派发一个 ready 事件,并切换状态条至等待数据返回状态。
// 06-01
function touchend(e) {
var offset = e.changedTouches[0].pageY - startY;
sys.page.off("touchmove").off("touchend").css("transition", "all 0.3s ease-in 0s");
if ( items.status.value == "release" ) {
sys.page.css("transform", "translateY(40px)");
} else if ( offset < 40 ) {
sys.page.css("transform", "translateY(0)");
} else {
release();
}
}
由于情况三的处理较复杂,所以独立封装成一个函数处理。请看下面的 release 函数。
// 06-01
function release() {
items.status.value = "release";
sys.refresh.once("complete", () => {
items.status.value = "message";
setTimeout(e => {
sys.page.css("transform", "translateY(0)").once("webkitTransitionEnd", e => items.status.value = "pull");
}, 300);
});
sys.page.css("transform", "translateY(40px)").trigger("ready");
}
此函数主要完成两件事,其一是派发 ready 事件,提醒上级组件发送数据请求,其二是侦听 complete 事件,一旦接收到来自上级派发的 complete 事件则显示完成数据请求的提示并返回初始状态。
状态条的改进
上面我们实现的状态条是纯文字的,这一节让我们把 加载中...
替换成一个动画,从而给用户带来更好的体验。下面实现的动画组件 Release 包含一个旋转的类似菊花一样的东西,同时还包含文本。
// 06-02
Release: {
css: `#loader { display: inline-block; position: relative; height: 2.5em; line-height: 2.5em; }
#spinner { width: 1.2em; height: 1.2em; position: absolute; top: .7em; }
#label { display: inline-block; font-size: 0.75em; margin: 0 0 0 2em; }`,
xml: `<div id='loader'>
<Spinner id='spinner'/><span id='label'/>
</div>`,
map: { appendTo: "label" }
},
Spinner: {
css: `#loader { width: 1.5em; height: 1.5em; animation: spin 1s linear infinite;... }
@keyframes $spin { 0% {transform: rotate(0deg);} 100% {transform: rotate(360deg); } }
@-webkit-keyframes $spin {0% {-webkit-transform: rotate(0deg);}... }`,
xml: `<svg id='loader' width='48' height='48' viewBox='0 0 1024 1024'>
<path d='M512.151961 3.978614l-0.308015 0c-21.655206 0-39.162952...'/>
...
</svg>`
}
你只需要在状态条组件 Status 中把名为 release 的组件替换成上面新实现的 Release,其余地方不用改,示例就能很好的工作了。
// 06-02
Status: {
css: "#statusbar { height: 2.5em; line-height: 2.5em; text-align: center; }",
xml: `<ViewStack id='statusbar'>
<span id='pull'>Pull to refresh...</span>
<span id='release'>Release to refresh...</span>
<Release id='loading'>Loading...</Release>
<span id='success'>Loading success</span>
</ViewStack>`,
fun: function (sys, items, opts) {
var status = "pull";
function getValue() {
return status;
}
function setValue(value) {
sys.statusbar.trigger("switch", status = value);
}
return Object.defineProperty({}, "value", {get: getValue, set: setValue});
}
}
xmlplus 组件设计系列之六 - 下拉刷新的更多相关文章
- xmlplus 组件设计系列之零 - xmlplus 简介
xmlplus 是什么 xmlplus 是博主写的一个 JavaScript 框架,用于快速开发前后端项目. xmlplus 基于组件设计,组件是基本的构造块.评价组件设计好坏的一个重要标准是封装度. ...
- mui 动态加载数据出现的问题处理 (silder轮播组件 indexedList索引列表 下拉刷新不能继续加载数据)
mui-slider 问题:动态给mui的图片轮播添加图片,轮播不滚动. 解决:最后把滚动轮播图片的mui(".mui-slider").slider({interval: 300 ...
- xmlplus 组件设计系列之十 - 网格(DataGrid)
这一章我们要实现是一个网格组件,该组件除了最基本的数据展示功能外,还提供排序以及数据过滤功能. 数据源 为了测试我们即将编写好网格组件,我们采用如下格式的数据源.此数据源包含两部分的内容,分别是表头数 ...
- xmlplus 组件设计系列之八 - 分隔框(DividedBox)
分隔框(DividedBox)是一种布局类组件,可以分为两类,其中一类叫水平分隔框(HDividedBox),另一类叫垂直分隔框(VDividedBox).水平分隔框会将其子级分为两列,而垂直分隔框则 ...
- xmlplus 组件设计系列之一 - 图标
网页上使用的图标分可为三种:文件图标.字体图标和 SVG 图标.对于文件图标,下面仅以 PNG 格式来说明. PNG 图标 对于 PNG 图标的引用,有两种方式.一种是直接由 HTML 元素 img ...
- xmlplus 组件设计系列之五 - 选项卡
这一章将设计一个选项卡组件,选项卡组件在手持设备上用的比较多,下面是一个示意图: 选项卡组件的分解 在具体实现之前,想像一下目标组件是如何使用的,对于设计会有莫大的帮助.通过观察,可以将选项卡组件分为 ...
- xmlplus 组件设计系列之三 - 文本框
文本框是页面中最常用的输入组件,它的默认使用方式如下: <input type='text'/> 当然,这里的 `type='text' 可以略去不写.大部分情况下,使用默认的文本框作为输 ...
- xmlplus 组件设计系列之二 - 按钮
除了图标以外,按钮也许是最简单的组件了,现在来看看如何定义按钮组件. 使用原生按钮组件 在 xmlplus 中,HTML 元素也以组件的方式存在.所以,你可以直接通过使用 button 标签或者 in ...
- xmlplus 组件设计系列之七 - 路由
在浏览器端,对路由的理解一般是根据不同的 URL 完成页面的切换.在服务器端,则是根据不同的 URL 请求回馈相关的页面.在本章,我们讲述的是根据接收到的不同命令,路由组件呈现出不同的页面,这算是广义 ...
随机推荐
- GUID的获取
全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符. GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx ...
- java关于map用来筛选的用法
我有一个实体 PropTemplateItem{id,名称,父节点,模版id},父节点为root是定义为根节点. 例如数据: 001,颜色,root,123 002,白色,001,123 003,红色 ...
- js手写图片查看器(图片的缩放、旋转、拖拽)
在做一次代码编辑任务中,要查看图片器.在时间允许的条件下,放弃了已经封装好的图片jq插件,现在自己手写js实现图片的缩放.旋转.推拽功能! 具体代码如下: <!DOCTYPE html> ...
- TCP协议设计原理
TCP协议设计原理 最近去了解TCP协议,发现这是一个特别值得深思的协议.在本篇博客中,不会长篇大论的给大家介绍TCP协议特点.包头格式以及TCP的连接和断开等基本原理,而是会带大家深入理解为什么要这 ...
- boost.asio源码阅读(1) - 从chat_server开始
1. 关于示例代码 chat 先从简单的入手, 在如下路径:boost_1_63_0/libs/asio/example/cpp11/chat中找到chat_server.cpp 查看其成员, pri ...
- POP3是收邮件的协议,SMTP是发邮件的协议,IMAP是一种邮箱通信协议。
我也是第一次接触这种服务,是因为我自己在做一个小小的自动推送天气情况到自己邮箱.所以才碰到这个的/ 看一下标题,我们可以先这样理解. POP3(Post Office Protocol - Versi ...
- win32 htmlayout点击按钮创建新窗口,以及按钮图片样式
最近在做一个C++ win32的桌面图形程序,我不是C++程序员,做这个只是因为最近没什么java的活. windows api,之前接触的时候,还是大学,那时用这个开发打飞机游戏纯粹是娱乐.现在基本 ...
- 深入React组件生命周期
上篇博文使用React开发的一些注意要点对React开发的一些重点进行了简单的罗列总结,虽然也提到了React生命周期,但只略微小结,在此单独写篇React生命周期的总结. 在组件的整个生命周期中,随 ...
- Windows Mobile 常用键值VK对应表
#define VK_TSOFT2 VK_F2 // Softkey 2 #define VK_TTALK VK_F3 // Talk ...
- python 基础知识(待补充)
数据结构: python数据结构共包含四大类: 列表 列表是可变元素,支持增删改查操作 字典 字典是遍历,key:value 格式. 元组 元组数据不可变元素,仅仅有两个操作 index, coun ...