充电的时间到了,为了让你萌技能日有所张,按照惯例,继续分享《30天,App开发从0到1》的内容。本篇为大家精选的章节是第二部分第八章第一小节——自定义样式的日期选择器。敲黑板!!!感兴趣的小伙伴带上小板凳上课啦!
主要内容 示例1和示例2讲的是“融合”,示范了APICloud模块与HTML5代码混合开发的使用技巧; 示例 3 讲的是“基础特性”,帮助开发者理解与掌握 APICloud 的基础特性,这样需求实现 就会变得简单; 示例 4 ~示例 6 讲的是 CSS 的使用技巧,APICloud 采用 HTML、CSS 和 JavaScript 为主开 发语言,可以完美实现各种 CSS 样式效果。
学习目标 (1)实现自定义样式的日期选择器。 (2)实现自定义样式的三级联动城市选择器。 (3)实现固定不动的下拉筛选菜单。 (4)实现滑动页面时,动态改变导航条颜色。 (5)实现背景图片的高斯模糊效果。 (6)实现 0.5 px 的细线。
以下示例讲解仅选择部分核心代码进行详细说明,读者可在 GitHub 本书的资源范例中获取示例的完整代码。
8.1 自定义样式的日期选择器
APICloud 模块因其易用性、高效性,在 APICloud 应用开发中会被频繁地使用。UI 类的APICloud 模块,可以修改颜色、字体、背景色等样式,形成不同的风格与外观样式,但模块的整体布局结构是无法改变的。本示例提供一种思路,将 HTML 页面与模块混合搭配,利用HTML 快速布局形成不同的页面格局,形成另类的视觉体验。
下面采用 HTML 页面与 APICloud 模块混合嵌套的方式,实现一个不一样的日期选择器,如图 8-1 所示。
图 8-1
8.1.1 使用模块UICustomPicker
UICustomPicker 模块是一个自定义内容选择器,可自定义模块位置、内容取值范围、内容标签、设置选中内容,还可用于实现固定取值范围的内容选择器;多项内容之间没有级联关系。
8.1.2 开发流程及要点概述
本示例的实现思路是先用 HTML 代码创建一个背景页面,然后将模块打开在这个背景层上面,从而从视觉上实现既定的目标样式。
(1)实现 HTML 静态页面开发
为相关页面添加如下 HTML 代码,使用了弹性盒子布局。篇幅所限,CSS 样式部分就不在这里列出,具体可从示例源码中获取查看。注意页面中的 onclick 点击事件使用了 tapmode 属性去消除 300 ms 的点击延迟。
/* HTML页面部分代码 */
<body class="fl ex-box fl ex-column"> <div class="fl ex-1"></div> <div class="sheet"> <div class="fl ex-box sheet-header"> <div class="Btn"></div> <div id="title" class="fl ex-1">请选择日期</div> <div class="Btn" tapmode="touched">完成</div> </div> <div class="sheet-body"> <div class="title fl ex-box"> <span class="fl ex-1">年</span> <span class="fl ex-1">月</span> <span class="fl ex-1">日</span> </div> <div id="picker-container" class="fl ex-box fl ex-column"> <div class="fl ex-1"></div> <div class="cell"></div> <div class="fl ex-1"></div> </div> </div> <div class="cancel" tapmode="touched">取消</div> </div> </body>
(2)创建日期选择器 创建模块实例,在 open 方法中定义了模块的位置、大小尺寸和颜色样式、可选的时间范围等参数。在 open 方法的回调中记录了模块的 ID 值,用于后续操作模块的逻辑方法时使用。同时加入了设置模块初始化显示的默认值方法和防止选择错误日期(如 2 月 30 日)的方法。
为相关页面添加如下代码: // JavaScript 部分代码 var UICustomPicker; //模块对象 var vPickerId; // 记录当前模块ID的变量 function fnOpenPicker() { // 创建联动选择器 UICustomPicker = api.require('UICustomPicker'); // 引入模块 // 定义模块初始化需要的参数 // 根据页面HTML布局,定义模块所在位置参数 var tY = api.winHeight - 184 - 10; // 定义模块rect中的Y,起始高度数值 var tW = api.frameWidth - 40; // 定义模块rect中的w,宽度数值 // 定义模块可选择的时间范围参数 // 获取当前年份 var tNow = new Date(); var tYear = tNow.getFullYear(); // 获取当前年份 var tMonth = tNow.getMonth(); // 获取当前月份 var tDate = tNow.getDate(); // 获取当前日期 var tMinYear = tYear - 100; // 可选最小时间,100年前 var tMaxYear = tYear + 100; // 可选最大时间,100年后 UICustomPicker.open({ rect: { x: 20, y: tY, w: tW, h: 135 }, styles: { bg: 'rgba(61,61,61,0.0)', normalColor: 'rgba(61,61,61,0.5)', selectedColor: '#3d3d3d', selectedSize: 28, tagColor: '#3685dd', tagSize: 16 }, data: [{ scope: tMinYear + '-' + tMaxYear }, { scope: '1-12' }, { scope: '1-31' }], autoHide: false, loop: true, rows: 3, fi xedOn: api.frameName, fi xed: true }, function(ret, err) { if (ret) { if('number' == typeof ret.id) { vPickerId = ret.id; // 记录当前模块的ID } if('show' === ret.eventType) { // 设置当前时间为默认值 var tDefault = [tYear,tMonth+1,tDate]; fnSetSelectedValue(tDefault); } if('selected' === ret.eventType) { //判断选择值的合法性 fnCheckSelectedValue(ret.data); } } }); }
(3)加入时间校验逻辑
因为现实时间存在闰年,并且每个月的天数不同,所以需要完善日期选择器,加上补充逻辑,以避免出现选择了 ×××× 年 2 月 31 日的错误发生。
/** * 闰年判断 * @param {Number} pYear 4位数字组成的年份值 * @constructor */ Date.prototype.isLeapYear = function(pYear) { var self = this; var tYear = 'number' === typeof pYear ? pYear:self.getFullYear(); return (tYear % 4 == 0) && (tYear % 100 != 0 || tYear % 400 == 0); } var oSelectedData; // 选择的时间数组 /** * 判断选择值的合法性 * @param {Array} pData 日期选择器选择后的回调数据 * @return {void} */ function fnCheckSelectedValue(pData) { if('[object Array]' !== Object.prototype.toString.call(pData)) { return; } //判断特殊日期 //获取月份进行判断 var tData = pData; switch (tData[1]) { case '2': //判断是否为闰年 var tNum = '28'; if(new Date().isLeapYear(tData[0])){ tNum = '29'; } if( parseInt(tData[2]) > parseInt(tNum) ){ tData[2] = tNum; fnSetSelectedValue(tData); } else { oSelectedData = tData; } break; case '4': case '6': case '9': case '11': if( tData[2] == '31') { tData[2] = '30'; fnSetSelectedValue(tData); } else { oSelectedData = tData; } break; default: oSelectedData = tData;
} } /** * 主动设置选择器的选择值 * @param {Array} pData 日期选择器选择后的回调数据 * @return {void} */ function fnSetSelectedValue(pData) { if('[object Array]' !== Object.prototype.toString.call(pData)) { return; } UICustomPicker.setValue({ id: vPickerId, data: pData }); oSelectedData = pData; }
(4)加入 HTML 页面按钮点击事件
点击事件是实现模块和其他页面的交互逻辑的。
fnCancelBtnTouched() 函数方法中使用了 api.pageParam 这个 api 的属性,其中 cb_win( 表示回调的 win 窗口名称 ) 和 cb_frm(表示回调的 frame 窗口名称),具体对应的值是上一级打开本页面的窗口传送过来的,这样的好处是方便本页面封装成一个通用的公共页面,更加灵活。
为相关页面添加如下 JS 代码: //取消按钮点击事件 function fnCancelBtnTouched() { api.execScript({ // 调用上级页面方法来关闭选择器 name: api.pageParam.cb_win, frameName: api.pageParam.cb_frm, script: 'fnCloseSheetFrame();' }); } fnCompleteBtnTouched() 完成按钮的点击事件,将关闭页面的方法放在了上级页面,使用 api.execScript 方法去调用执行。这样处理是为了避免页面关闭的执行过快,后续的逻辑代码还没来得及执行或没有执行完,从而产生错误异常。 //完成按钮点击 function fnCompleteBtnTouched() { if(!oSelectedData || oSelectedData.length == 0) { api.toast({ msg: '请选择日期!', duration: 2000, location: 'bottom' }); return; } else { /* 执行完成后续业务逻辑 */ // console.log('选择数据:'+JSON.stringify(oSelectedData)); api.execScript({ //执行选择后的回调方法 name: api.pageParam.cb_win, frameName: api.pageParam.cb_frm, script: api.pageParam.cb_fun+'('+JSON.stringify(oSelectedData)+');' }); } }
本示例的点击事件中使用的 api.pageParam 对象是由上级页面传递的,目的是将页面选择的数据回传给调用的上级页面。 |