们每天都会接触到各式各样的表单页面,其中包括登录账号、填写单据、购买产品、发布信息等。它作为所有产品里最常见也最普通的设计元素,往往最容易被忽略它们该有的体验细节,而引起用户使用中的挫败感。本篇文章主要通过业务场景和用户行为的角度来分析,如何打造体验友好的表单界面。
一、交互设计下的业务场景与用户行为
二、基于业务场景选择适合的布局模式
三、基于用户信任行为的设计原则
四、总结
交互设计的本质即人与机器在一定的业务场景下的信息传递、相互作用,我们设计产品可以理解为是在设计或者说培养对应场景下的『机器能力』,良好的『机器能力』可以更好的根据『用户行为/心理』来交流与反馈,使得系统更懂用户,更符合自然人际之间的交互行为。
那么如何打造更友好的表单界面,让它更好的与用户相互交流,我们需要从业务场景和用户行为出发做分析,反推机器能力的设计目标。
针对不同的场景需求下的操作路径,需要有相对合适的布局模式来支持高效操作和录入,相应的有以下四种类型:
多列流式布局的表单,较多运用于需要大批量数据录入的场景,此时的多列流式布局可以保证在各类终端上的空间最大化利用,同时该布局节省业务设计与开发成本。
▽下图为高级表单,是多列流式布局的一种拓展设计,运用场景更广。
在多列流式布局表单中,用户需要使用Z型路径进行扫描和阅读,在节省空间的同时也牺牲了用户的浏览与理解速度;但是对于单据字段数量较少且内容层级与类型较单一的情况,如果一列展示的话,清晰的浏览线可以提供快速且明确的浏览路线,提高操作效率。
▽在人员类基础资料界面的维护场景中,少量的字段更加适合单列布局模式。
上述的多列流式布局或者单列布局,都是针对所有表单的通用模式,这两种模式通用且开发设计成本较低,但是除此之外,细分场景下我们不应该忽视很多特殊表单的设计需求与用户习惯,这类特殊表单在线下有着固化的实体样式,此时采用卡片(拟物)化模式可以让用户快速找到阅读习惯。
▽凭证类的表单,较多直接采用拟物化的布局模式,迎合财务人员的阅读习惯,提高处理效率。
而对于普通用户日常生活的使用场景中也特别常见将表单(或其中部分内容)设计成卡片(拟物)模式,例如很多实体票据中的电影票、高铁票、飞机票等,习惯性的阅读模式带来空间利用与阅读效率的更佳。
▽飞猪中机票订单填写界面,将部分内容拟物化设计,方便展示且节省空间。
另外一类布局模式就是步骤向导型布局,主要运用于以下三种情况:
当你在设计一个超长的复杂表单或者是一个无法简化的流程任务时,此时步骤型向导布局可以降低复杂性,而从提高可用性可易用性。
另外很多时候,复杂表单内容中具有前后依赖关系,在一张表单中,用户需要严格先执行前面任务,才能允许填写后续任务,而步骤向导恰好可以通过细分的操作流让用户分步执行,降低学习曲成本与操作风险。
▽在GitHub的注册操作中,采用步骤向导型布局,分步完成注册任务。
对于涉及款项转账、账户安全等此类的重要表单,此时的填写成本与风险较高,即使内容较少也推荐试用步骤向导型布局,避免后续出错所带来的不必要麻烦。
▽在富途证劵中,在转入转出业务中,细分操作流,每一步关注各自的重要信息,降低填写表单风险。
以下需要谈论的是用户行为下的信任特征所带来的设计准则。
从用户行为/心理的角度看,信任是人与人之间交往的先决条件,只有获得彼此的信任,两个人之间的对话才能自然和愉悦。
那么对于用户与产品之间,信任的行为准则依然存在,信任意味着更多耐心和理解,同时带来了更多的探索欲与试错接受度。用户足够信任产品才能让表单的录入变得更加流程与愉悦。
当设计者拿到一份需求文档时,面对众多的录入字段,首要事项就是明确用户填写该表单的目的,需要确保终端用户看到的表单上都是真正需要的字段,因为每增加一个额外的非必要字段都会影响填写效率/转化率。要时刻思考我们需要从用户那里得到哪些必要信息以及怎么进行高效地数据利用。
而针对非必填但是又有用的字段内容,可以采取的策略是在初次交互时并不强制填写,而让用户选填以及在后续合适的场景中重新唤起该内容项的信息收集,适时且自然,保证初始交互简单可控。
▽Google 日历中,当创建待办事项时,只弹出弹窗显示必要字段,用户可以自行选择直接保存或者填写更多内容项,该设计极大提高快速创建待办的效率。
很多时候的表单内容多且繁杂,其中的信息项如果只是常规罗列,很容易显得混乱和难以阅读,易用性较差,使用户产生抵触情绪,久而久之就厌恶使用该产品。
关联信息、合理组织内容,利用区块、间隔来进行布局区分;按照内容的类别、层次、顺序等进行组的划分,提高表单的清晰性与可读性。
▽DELTA中乘客的基本信息和联系信息为两项独立的重要项,分组展示,同时对于每个组内的细分信息也进行分行布局,例如姓名类信息独行,飞行计划与号码、性别与年月日、编号各自独占一行,清晰易填。
相关阅读:《设计法则: Fitts’ Law / 菲茨定律(费茨法则)》
表单填写长久以来被诟病的一点即是其繁琐性,减少用户操作以帮助用户提高输入效率,从而提高转化率/效率。
预判默认值
针对一些常规内容,表单场景可以通过获取用户资料/个人信息/历史记录等来预判默认值。
最为典型的即是当我填写一张请假单时,系统应该自动代入我的部门、姓名、工号等信息,该类元素的继承性较强,用户很少情况会去变更。同样在性别、地址、转账人等都可以使用『预判默认值』的方法来减少用户操作成本。
软件应该保持智能的模式,在适当的风险点内,帮使用者选择决策。
硬件设备自动获取
在人机交互层面,除了不断思考软件能给予用户的体验外还需要探索,硬件设备可以提供哪些帮助?
该情况最常见例子即”系统定位”,任何表单或者场景,只要涉及地址选择,基本现在所以产品都会设置系统快速定位用户设备所在位置,自动录入。
而较高级的做法则是更深度挖掘机器的能力,也就是大家常说的『语音识别』与『图像识别』,当用户需要录入一张发票时,系统提前告知用户拍摄实体发票/二维码,进行图像识别后,批量代入数据,极大减少用户思考与操作成本。
自动生成
另外一些字段,系统可以根据规则来自动生成相应内容,例如编码、序号等,此时一般不允许用户进行更改,而针对另外一些用户自主性更强的字段,可以采用自动生成+建议录入的方式来帮助用户。
▽Safari浏览器下,密码字段自动生成强密码,若用户使用,则浏览器会自动记录,下次自动填充。
在用户面对繁杂的表单填写时,难免会遇到困惑,此时为了防止用户出错或者中途流失,我们需要适时给予用户帮助和导引。
其内容的编写上可以有以下指导方针:
1. 告诉用户该项指什么,适用于术语类字段;
2. 告诉用户为什么要填写该项,填写了有什么好处;
此类多见于需要收集用户信息的场景,例如需要登记用户手机(非必录项),此时告知用户”填写手机号方便找回密码”,自然而然的用户会有更高的意愿去填写。
3. 告诉用户要怎么填写该项,即填写规则。
在形式上,可以选择:
1. 灵活利用字段掩码进行填写提醒;
▽大韩航空KOREAN AIR中,录入框里的灰字即字段掩码,其中有填写帮助和示例。
字段掩码可以很好地节省空间,同时可以快速的帮助用户降低思考成本。
2. 标签Tips的有效示意;
▽Trip平台中,填写姓名项时,出现Tips气泡浮窗,出现护照卡片图例示意,形象易懂。
3. 全局性说明规范整体填写
当表单中多个字段需要进行关联性说明时,此时需要跳出单个字段的引导,启用全局性说明进行填写帮助。
▽Expedia智游网的订单填写页面中,顶部提醒栏对多个字段进行说明示意。
1. 更加贴心;
2. 更加放心(安全性);
▽Google Pay中添加付款方式,特别注释说明不会泄露用户的付款信息。
3. 适时的惊喜与成就感。
遵循业务场景和用户行为分析来反推机器能力的设计方法论不仅适用与表单的设计中,任何交互元素下的设计方式制定都可以从以上两点出发,来指导设计者打造更加友好的产品体验。
本文由 @小伟同学 原创发布于人人都是产品经理。未经许可,禁止转载
题图来自Unsplash,基于CC0协议
位好,在前面有一期作品中,我们曾经在Excel中实现了万年历的制作,当时也有很多网友看过我的那期头条文章或头条视频,附带有评价和收藏,在此向你们表示感谢。
另外,有个名叫“斑斓虎zcy”的粉丝评论说我做的万年历如果含有农历就完美了,对这位粉丝的提议我欣然接受,另外,虽然我没采用“斑斓虎zcy”粉丝提供的公历转农历的关键技术模版,但对“斑斓虎zcy”粉丝的热心表示深深的谢意!
刚刚上一期,我为大家分享了自己弄的公历<---->农历互转的技术与方法,也有很多网友看了这期头条文章或视频,说明大家对这个方法也很认可的,谢谢各位啦!接下来,我准备用两种公历<---->农历互转的方法实现带农历的万年历设计吧!为了区分起见,我们暂定本期的题目为“头条文章--Excel中带农历的万年历设计方法一”、下期作品的题目为“头条文章--Excel中带农历的万年历设计方法二”。
本期,我们先来用第一种方法实现吧。
一、Excel前端带农历万年历界面设计
关于界面的设计,这里和上次那一期万年历的界面一样,这里不做过多描述,这里就只以截图直接呈现给各位吧。如下图所示
图1 带农历的万年历界面
二、用方法一实现带农历万年历的功能代码
模块1中代码如下:
'强势自定义“公历”<---->“农历”互转函数
'原创:互联网
'修正:号作者“跟我学Office高级办公应用” 2019/10/12
'---农历数据定义---
'先以 Hexadecimal_To_Binary 函数还原成长度为 18 的字符串,其定义如下:
'前12个字节代表1-12月:1为大月,0为小月;压缩成十六进制(1-3位)
'第13位为闰月的情况,1为大月30天,0为小月29天;(4位)
'第14位为闰月的月份,如果不是闰月为0,否则给出月份(5位)
'最后4位为当年农历新年的公历日期,如0131代表1月31日;当作数值转十六进制(6-7位)
'定义如下农历(阴历)日期常量(1899~2100,共202年,但是事实上我们只需要用到1900~2100这201年即可)
Private Const ylData = "AB500D2,4BD0883," _
& "4AE00DB,A5700D0,54D0581,D2600D8,D9500CC,655147D,56A00D5,9AD00CA,55D027A,4AE00D2," _
& "A5B0682,A4D00DA,D2500CE,D25157E,B5500D6,56A00CC,ADA027B,95B00D3,49717C9,49B00DC," _
& "A4B00D0,B4B0580,6A500D8,6D400CD,AB5147C,2B600D5,95700CA,52F027B,49700D2,6560682," _
& "D4A00D9,EA500CE,6A9157E,5AD00D6,2B600CC,86E137C,92E00D3,C8D1783,C9500DB,D4A00D0," _
& "D8A167F,B5500D7,56A00CD,A5B147D,25D00D5,92D00CA,D2B027A,A9500D2,B550781,6CA00D9," _
& "B5500CE,535157F,4DA00D6,A5B00CB,457037C,52B00D4,A9A0883,E9500DA,6AA00D0,AEA0680," _
& "AB500D7,4B600CD,AAE047D,A5700D5,52600CA,F260379,D9500D1,5B50782,56A00D9,96D00CE," _
& "4DD057F,4AD00D7,A4D00CB,D4D047B,D2500D3,D550883,B5400DA,B6A00CF,95A1680,95B00D8," _
& "49B00CD,A97047D,A4B00D5,B270ACA,6A500DC,6D400D1,AF40681,AB600D9,93700CE,4AF057F," _
& "49700D7,64B00CC,74A037B,EA500D2,6B50883,5AC00DB,AB600CF,96D0580,92E00D8,C9600CD," _
& "D95047C,D4A00D4,DA500C9,755027A,56A00D1,ABB0781,25D00DA,92D00CF,CAB057E,A9500D6," _
& "B4A00CB,BAA047B,B5500D2,55D0983,4BA00DB,A5B00D0,5171680,52B00D8,A9300CD,795047D," _
& "6AA00D4,AD500C9,5B5027A,4B600D2,96E0681,A4E00D9,D2600CE,EA6057E,D5300D5,5AA00CB," _
& "76A037B,96D00D3,4AB0B83,4AD00DB,A4D00D0,D0B1680,D2500D7,D5200CC,DD4057C,B5A00D4," _
& "56D00C9,55B027A,49B00D2,A570782,A4B00D9,AA500CE,B25157E,6D200D6,ADA00CA,4B6137B," _
& "93700D3,49F08C9,49700DB,64B00D0,68A1680,EA500D7,6AA00CC,A6C147C,AAE00D4,92E00CA," _
& "D2E0379,C9600D1,D550781,D4A00D9,DA400CD,5D5057E,56A00D6,A6C00CB,55D047B,52D00D3," _
& "A9B0883,A9500DB,B4A00CF,B6A067F,AD500D7,55A00CD,ABA047C,A5A00D4,52B00CA,B27037A," _
& "69300D1,7330781,6AA00D9,AD500CE,4B5157E,4B600D6,A5700CB,54E047C,D1600D2,E960882," _
& "D5200DA,DAA00CF,6AA167F,56D00D7,4AE00CD,A9D047D,A2D00D4,D1500C9,F250279,D5200D1"
'定义农历 (阴历)每月的汉字大写日期“天”
Private Const ylMd0 = "初一初二初三初四初五初六初七初八初九初十十一十二十三十四十五" _
& "十六十七十八十九二十廿一廿二廿三廿四廿五廿六廿七廿八廿九三十 "
'定义农历 (阴历)一年中的汉字大写日期“月”
Private Const ylMn0 = "正二三四五六七八九十冬腊"
'定义农历 (阴历)年中的“天干”(如:甲乙丙丁......等)
Private Const ylTianGan0 = "甲乙丙丁戊已庚辛壬癸"
'定义农历 (阴历)年中的“地支”(如:子丑寅卯辰......等)
Private Const ylDiZhi0 = "子丑寅卯辰巳午未申酉戌亥"
'定义农历 (阴历)年中的“属相”(如:鼠牛虎兔龙......等)
Private Const ylShu0 = "鼠牛虎兔龙蛇马羊猴鸡狗猪"
Public shp_year_select As Shape, y '定义公有全局变量年份选择组合框shp_year_select和用于存储选择的年份变量y,以便所有的过程都可以调用和回传数据
Sub Run_Fill_Calender() '运行填充日历
[b4].Select
n = shp_year_select.ControlFormat.Value
y = shp_year_select.ControlFormat.List(n)
[O1] = y & " 年历" & "[" & Mid(GetYLDate(y & "-6-1"), 4, 6) & "]"
Fill_Calender_Datas '调用“填充日历数据”过程
[a65535] = y '将选择过的年份存储在单元格"A65535"中
End Sub
Sub Fill_Calender_Datas() '填充日历数据
Dim rg(1 To 12) As Range '定义12个元素的的范围区域对象数组
'为区域对象数组的每个区域对象元素对象指派这12个区域对象具体的实体
Set rg(1) = [b5:h10]: Set rg(2) = [j5:p10]: Set rg(3) = [r5:x10]: Set rg(4) = [z5:af10]
Set rg(5) = [b15:h20]: Set rg(6) = [j15:p20]: Set rg(7) = [r15:x20]: Set rg(8) = [z15:af20]
Set rg(9) = [b25:h30]: Set rg(10) = [j25:p30]: Set rg(11) = [r25:x30]: Set rg(12) = [z25:af30]
For i = 1 To 12
Select Case i
Case 1, 3, 5, 7, 8, 10, 12: days_31 y, i, rg(i)
Case 4, 6, 9, 11: days_30 y, i, rg(i)
Case 2: days_29_Or_28 y, i, rg(i)
End Select
Next
End Sub
Sub Erse_Calender_Datas() '清空日历数据
Dim rg As Range
Set rg = [5:10,15:20,25:30]
[b4].Select
rg.ClearContents
[O1] = "---- 年历[-----年]"
yr = Year(Date)
'以下是定位当今日期的年份在表单组合框中显示
For i = 1 To shp_year_select.ControlFormat.ListCount
If yr = Val(shp_year_select.ControlFormat.List(i)) Then
n = i
Exit For
End If
Next
shp_year_select.ControlFormat.ListIndex = n
End Sub
Sub days_31(y, m, r As Range) '月大--31天
Dim da As Date, d
r.ClearContents
week_str = "日一二三四五六"
d = 1
da = CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期
ws = Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中
First_Day_Pos_In_Week_Area = InStr(week_str, ws) '每月初始的1号在日历星期区域的定位位置
For d = 1 To 31
da = CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期
ws = Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中
Other_Day_Pos_In_Week_Area = InStr(week_str, ws)
'实际的每月的号数应该加上每月初始的1号在日历星期区域的定位位置减去1“”d + (First_Day_Pos_In_Week_Area - 1),为了在第7个位置仍然将该号 _
数放在该行,所以还得再减去1“d + (First_Day_Pos_In_Week_Area - 1) - 1”,然后再除7取整,同时乘以7后加上该号数在日历中星期区域的实际列数 _
位置,即可得到该号数在日历区域的设计位置
p = Int((d + (First_Day_Pos_In_Week_Area - 1) - 1) / 7) * 7 + Other_Day_Pos_In_Week_Area
yl_md = Right(GetYLDate(da), 4) '调用转农历(阴历)函数,取后四个汉字月日日期字符
yl_m = Left(yl_md, 2) '拆解阴历月日中的月份
yl_d = Right(yl_md, 2) '拆解阴历月日中的日子
If yl_d = "初一" Then yl_d = yl_m '若拆解的日子是“初一”,则即刻用该月的月份替代该阴历月份的首个日子
r(p) = d & Chr(10) & yl_d '将公历日期和对应的农历日期合在一起填入到p处正确位置
If da = Date Then r(p).Select '若选择年份后不断瞬时生成的日期da和现在的日期匹配,则将当前填充的日期单元格选择成活动状态
Next
End Sub
Sub days_30(y, m, r As Range) '月小--30天
Dim da As Date, d
r.ClearContents
week_str = "日一二三四五六"
d = 1
da = CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期
ws = Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中
First_Day_Pos_In_Week_Area = InStr(week_str, ws) '每月初始的1号在日历星期区域的定位位置
For d = 1 To 30
da = CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期
ws = Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中
Other_Day_Pos_In_Week_Area = InStr(week_str, ws)
'实际的每月的号数应该加上每月初始的1号在日历星期区域的定位位置减去1“”d + (First_Day_Pos_In_Week_Area - 1),为了在第7个位置仍然将该号 _
数放在该行,所以还得再减去1“d + (First_Day_Pos_In_Week_Area - 1) - 1”,然后再除7取整,同时乘以7后加上该号数在日历中星期区域的实际列数 _
位置,即可得到该号数在日历区域的设计位置
p = Int((d + (First_Day_Pos_In_Week_Area - 1) - 1) / 7) * 7 + Other_Day_Pos_In_Week_Area
yl_md = Right(GetYLDate(da), 4) '调用转农历(阴历)函数,取后四个汉字月日日期字符
yl_m = Left(yl_md, 2) '拆解阴历月日中的月份
yl_d = Right(yl_md, 2) '拆解阴历月日中的日子
If yl_d = "初一" Then yl_d = yl_m '若拆解的日子是“初一”,则即刻用该月的月份替代该阴历月份的首个日子
r(p) = d & Chr(10) & yl_d '将公历日期和对应的农历日期合在一起填入到p处正确位置
If da = Date Then r(p).Select '若选择年份后不断瞬时生成的日期da和现在的日期匹配,则将当前填充的日期单元格选择成活动状态
Next
End Sub
Sub days_29_Or_28(y, m, r As Range) '闰年2月份29天,平年2月份28天(例如2020年就是闰年)
Dim da As Date, d
r.ClearContents
week_str = "日一二三四五六"
d = 1
da = CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期
ws = Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中
First_Day_Pos_In_Week_Area = InStr(week_str, ws) '每月初始的1号在日历星期区域的定位位置
If Is_LeepYear(y) Then '闰年2月份天数
For d = 1 To 29
da = CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期
ws = Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中
Other_Day_Pos_In_Week_Area = InStr(week_str, ws)
'实际的每月的号数应该加上每月初始的1号在日历星期区域的定位位置减去1“”d + (First_Day_Pos_In_Week_Area - 1),为了在第7个位置仍然将该 _
号数放在该行,所以还得再减去1“d + (First_Day_Pos_In_Week_Area - 1) - 1”,然后再除7取整,同时乘以7后加上该号数在日历中星期区域的实 _
际列数位置,即可得到该号数在日历区域的设计位置
p = Int((d + (First_Day_Pos_In_Week_Area - 1) - 1) / 7) * 7 + Other_Day_Pos_In_Week_Area
yl_md = Right(GetYLDate(da), 4) '调用转农历(阴历)函数,取后四个汉字月日日期字符
yl_m = Left(yl_md, 2) '拆解阴历月日中的月份
yl_d = Right(yl_md, 2) '拆解阴历月日中的日子
If yl_d = "初一" Then yl_d = yl_m '若拆解的日子是“初一”,则即刻用该月的月份替代该阴历月份的首个日子
r(p) = d & Chr(10) & yl_d '将公历日期和对应的农历日期合在一起填入到p处正确位置
If da = Date Then r(p).Select '若选择年份后不断瞬时生成的日期da和现在的日期匹配,则将当前填充的日期单元格选择成活动状态
Next
Else '平年2月份天数
For d = 1 To 28
da = CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期
ws = Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中
Other_Day_Pos_In_Week_Area = InStr(week_str, ws)
'实际的每月的号数应该加上每月初始的1号在日历星期区域的定位位置减去1“”d + (First_Day_Pos_In_Week_Area - 1),为了在第7个位置仍然将该 _
号数放在该行,所以还得再减去1“d + (First_Day_Pos_In_Week_Area - 1) - 1”,然后再除7取整,同时乘以7后加上该号数在日历中星期区域的实 _
际列数位置,即可得到该号数在日历区域的设计位置
p = Int((d + (First_Day_Pos_In_Week_Area - 1) - 1) / 7) * 7 + Other_Day_Pos_In_Week_Area
yl_md = Right(GetYLDate(da), 4) '调用转农历(阴历)函数,取后四个汉字月日日期字符
yl_m = Left(yl_md, 2) '拆解阴历月日中的月份
yl_d = Right(yl_md, 2) '拆解阴历月日中的日子
If yl_d = "初一" Then yl_d = yl_m '若拆解的日子是“初一”,则即刻用该月的月份替代该阴历月份的首个日子
r(p) = d & Chr(10) & yl_d '将公历日期和对应的农历日期合在一起填入到p处正确位置
If da = Date Then r(p).Select '若选择年份后不断瞬时生成的日期da和现在的日期匹配,则将当前填充的日期单元格选择成活动状态
Next
End If
End Sub
Function Is_LeepYear(y) As Boolean '给定的年份是否为闰年LeepYear的判断
If (y Mod 400 = 0) Or (y Mod 100 <> 0 And y Mod 4 = 0) Then
Is_LeepYear = True
Else
Is_LeepYear = False
End If
End Function
'自定义“公历转农历”日期函数
Function GetYLDate(ByVal strDate As String) As String
On Error GoTo ExitFunction_Label
If Not IsDate(strDate) Then Exit Function '如果参数strDate非日期的无效字符串,则退出本函数工作
'定义setDate--设置的未来日期,tYear--未来日期的本年份,tMonth--本月份,tDay--本日子
Dim setDate As Date, tYear As Integer, tMonth As Integer, tDay As Integer
setDate = CDate(strDate) '为该GetYLDate()函数参数的字符串转换后的日期赋予设定的日期
tYear = Year(setDate): tMonth = Month(setDate): tDay = Day(setDate) '年、月、日分别取值
'如果不是有效有日期,退出
If tYear > 2100 Or tYear < 1900 Then Exit Function
'定义daList()--是元素为18位日期二进制字符串数组,conDate--农历新年日期,thisMonths--本年的二进制 _
月份信息(可能包含闰月)
Dim daList() As String * 18, conDate As Date, thisMonths As String
'定义AddYear--是相对1900年递增的年,AddMonth--月份增量,AddDay--天数增量,getDay--农历新年和设 _
之日期相差天数
Dim AddYear As Integer, AddMonth As Integer, AddDay As Integer, getDay As Integer
'定义YLyear--农历(阴历)年的字符串,YLShuXing--农历(阴历)年的属相
Dim YLyear As String, YLShuXing As String
'定义dd0--农历(阴历)年的阴历日子,mm0--农历(阴历)年的阴历月,ganzhi()--每个元素为2个字符的天干地 _
支数组
Dim dd0 As String, mm0 As String, ganzhi(0 To 59) As String * 2
'定义RunYue--农历(阴历)年是否闰月的布尔型标志,RunYue1--农历(阴历)年闰月月份
Dim RunYue As Boolean, RunYue1 As Integer, mDays As Integer, i As Integer
'加载2年内的农历数据
ReDim daList(tYear - 1 To tYear)
daList(tYear - 1) = Hexadecimal_To_Binary(Mid(ylData, (tYear - 1900) * 8 + 1, 7))
daList(tYear) = Hexadecimal_To_Binary(Mid(ylData, (tYear - 1900 + 1) * 8 + 1, 7))
AddYear = tYear
initYL:
AddMonth = CInt(Mid(daList(AddYear), 15, 2))
AddDay = CInt(Mid(daList(AddYear), 17, 2))
conDate = DateSerial(AddYear, AddMonth, AddDay) '农历新年日期
getDay = DateDiff("d", conDate, setDate) + 1 '相差天数
If getDay < 1 Then AddYear = AddYear - 1: GoTo initYL
thisMonths = Left(daList(AddYear), 14) '前14位为本年的二进制月份信息(可能有闰月)存于thisMonths中
RunYue1 = Val("&H" & Right(thisMonths, 1)) '闰月月份
If RunYue1 > 0 Then '如果有闰月,则立即修正本年的二进制月份信息thisMonths,形成真正有效的二进制序 _
列信息
thisMonths = Left(thisMonths, RunYue1) & Mid(thisMonths, 13, 1) & Mid(thisMonths, RunYue1 + 1)
End If
thisMonths = Left(thisMonths, 13) '最后一次修正本年的二进制月份信息thisMonths,直接取13个月的情况
For i = 1 To 13 '遍历1~13个月,找到并计算含闰月的有效天数,同时退出循环
mDays = 29 + CInt(Mid(thisMonths, i, 1))
If getDay > mDays Then
getDay = getDay - mDays
Else
If RunYue1 > 0 Then '如果有闰月,则进一步根据i的值情况做如下处理
If i = RunYue1 + 1 Then RunYue = True '若i确系为闰月,则将闰月标志置为真
If i > RunYue1 Then i = i - 1 '若i大于闰月月份,则将将i回退修正
End If
AddMonth = i '最终记录下i作为真正的增量月份存入AddMonth
AddDay = getDay '同时,将得到的天数差作为增量天数
Exit For
End If
Next
dd0 = Mid(ylMd0, (AddDay - 1) * 2 + 1, 2) '用查找表的形式定位当前日期对应的农历(阴历)日子
mm0 = Mid(ylMn0, AddMonth, 1) + "月" '用查找表的形式定位当前日期对应的农历(阴历)月份
For i = 0 To 59 '0~59表示60年一个甲子,表示以60年一个轮回的形式,通过查找表精准定位每年的天干地支
ganzhi(i) = Mid(ylTianGan0, (i Mod 10) + 1, 1) + Mid(ylDiZhi0, (i Mod 12) + 1, 1)
Next
YLyear = ganzhi((AddYear - 4) Mod 60) '通过查找表形式得出阴历年的天干地支表示形式
YLShuXing = Mid(ylShu0, ((AddYear - 4) Mod 12) + 1, 1) '通过查找表形式得出阴历年的属相表示形式
If RunYue Then mm0 = "闰" & mm0 '如果某阴历月份有闰月,特别加上“闰X月”的形式
GetYLDate = "农历:" & YLyear & "(" & YLShuXing & ")年" & mm0 & dd0 '拼接当前日期的完整农历信息
ExitFunction_Label:
End Function
'将压缩的阴历字符还原
Private Function Hexadecimal_To_Binary(ByVal strHex As String) As String '十六进制转二进制
Dim i As Integer, i1 As Integer, tmpV As String
Const hStr = "0123456789ABCDEF"
Const bStr = "0000000100100011010001010110011110001001101010111100110111101111"
tmpV = UCase(Left(strHex, 3))
'以下是十六进制转二进制的具体操作
For i = 1 To Len(tmpV)
i1 = InStr(hStr, Mid(tmpV, i, 1))
Hexadecimal_To_Binary = Hexadecimal_To_Binary & Mid(bStr, (i1 - 1) * 4 + 1, 4)
Next
Hexadecimal_To_Binary = Hexadecimal_To_Binary & Mid(strHex, 4, 2)
'十六进制转十进制
Hexadecimal_To_Binary = Hexadecimal_To_Binary & "0" & CStr(Val("&H" & Right(strHex, 2)))
End Function
ThisWorkbook中代码如下:
Private Sub Workbook_Open() '工作簿一打开即刻初始化表单组合框数据并且在组合框中显示之前选择过的年份
Set shp_year_select = Sheets(1).Shapes("年份选择")
shp_year_select.ControlFormat.RemoveAllItems
'万年历的年份范围初步设定为“1900~2100”
For i = 1900 To 2100
shp_year_select.ControlFormat.AddItem i
Next
'以下是重新还原表单组合框控件之前选定过的年份显示
yr = [a65535]
For i = 1 To shp_year_select.ControlFormat.ListCount
If yr = Val(shp_year_select.ControlFormat.List(i)) Then
n = i '遍历整个表单组合框所有元素,查找与yr是否相匹配的元素,若找到即刻记下该编号并存于n中
Exit For
End If
Next
shp_year_select.ControlFormat.ListIndex = n '让表单组合框显示找到的之前选择过的年份
End Sub
三、用方法一实现带农历万年历运行效果测试
(一)选择年份,呈待生成带农历万年历状态。如下图所示
图2 选择年份准备生成带农历万年历
(二)点击选择的年份,生成实实在在的带农历的万年历。如下图所示
图3 生成带农历万年历效果
(三)压下<清除日历数据>按钮,准备进行带农历的万年历数据清除。如下图所示
图4 准备清除带农历万年历数据
(四)压下状态下的<清除日历数据>按钮情况下点击该按钮,完成带农历万年历数据的清除,并将年份组合框内的显示提示年份置为最新当前时间的年份。如下图所示
图5 清除带农历万年历数据结果
四、技术亮点小结
(一)充分利用寻找农历闰月方法和压缩的农历字符还原方法完成公历转农历
(二)在定位Excel的万年历数据填充单元格时,用字符串处理函数处理农历生成的数据
(三)存储记忆上次打开万年历的数据
好了,本期我们就分享到这里吧,希望大家喜欢和收藏哦!
最后,还是感谢大家的持续关注(头条号:跟我学Office高级办公)、推广、点评哦!谢谢大家继续关注下期第二中方法实现带农历的万年历设计!
历是一项深入我们生活的设计,最早的日历是什么样的?它的设计经历了哪些改变?本文将会带领大家一起探索日历的演化进程。
我们被那些具有历史感、跨越几个世纪的老物件包围着,有时侯我们却完全忽略了它们。
当你想到日历的时候,脑海里第一个形象会是什么?可能是一个7列5行(或6行,视第一天而定)的数字表格,用以显示一个月的日期。无论是在你的手机上还是挂在墙上,我们每天都会用到它。直到今日,日历的样式无论在什么媒介上都是雷同的。
我们日常生活中有许多具有历史感的东西,有时它们已经存在了好几个世纪,然而却被我们完全忽略了。每当我看到那些已经深入我们生活的设计时,我都想知道是谁做出了这样的设计决策——是谁让这个东西变成了今天的样子。网格样式的日历就是其中之一。
我有一个假设,当设计模式经历时间的考验时,它们可能已经达到了最大的改进点,这意味着用户已经对某个问题的解决方案感到满意,不需要进一步的迭代来满足他们的需求。
没有人会阻止设计师不断地迭代,设计师会持续添加或优化设计作品的某些特性,例如,日历的可读性就是一直处于被优化的状态(见图1)。
产品设计曲线图表
通常,当一个产品未达到设计临界点时,它很可能就会被新产品超越,新产品解决了同样的问题,但使用不同的方法和途径,相比之下之前的产品就显得已经过时了。但是我们今天所看到日历样式已经到处都被使用,似乎没有人需要一个新的样式它可能已经处于设计临界点了,就像我们如今普遍使用的计算器和手机输入界面一样。
那么,谁是这个伟大设计的创作者呢?在本文中,由于那些可以说明日历起源保存至今的文献较少,所以我只能对它复杂的演化发展做出一些简要的说明。
但在探索日历演化进程之前,让我们先快速浏览一下它背后的概念。
以七天作为周期似乎有一个复杂的演化过程,并且在世界各地同步起源,很难追溯和证明谁是最初的发明者:自公元前6世纪以来,七天为一周的日历一直在犹太教中使用,数字7对巴比伦人来说有一种神秘的意义。它与七个天体有关;太阳、月亮、火星、水星、木星、金星和土星。早在古代中国和日本,人们就以这些天体为基础,制定了每个周期都是七天。
但是,“七日工作周”的流行——以及它在现代日历中的突出地位——可以追溯到罗马人采用它的时候:君士坦丁大帝在公元321年正式采用了“七日工作周”,这意味着这种方法我们已经使用了1700年了。
Fasti Antiates Maiores的修复品,七月和八月的名称和现在的不同(约公元前60年)。
儒略历,公元前46年由凯撒大帝引入。迄今为止想要找到那个最初的发明者是很难的。所以我们需要说明的是它有什么特点而受到大家的欢迎。
大家认为是从罗马人开始将每月定义为30天或31天。当然,他们计算也不是全部正确的,因为他们将一年仅仅划分为8个月。但由于罗马在西方文化中的强势地位,这一错误的划分方式一直持续到了格列高利改革后才被修正。
即使罗马人给了我们所知道的日历,但仍然不清楚是谁推广了当下这种网格样式。网格似乎是一种自然的选择,因为它是显示系列元素最直观和高效的方式——埃及人已经在他们的日历中使用了网格样式。似乎它根本不需要一个发明者,就像我们从来不会问是谁发明了文字。
之后日历一直遵循儒略历,直到16世纪到17世纪,月和周的视觉样式有了改变。
17世纪以前,用圆形图表示年、月和周是很流行的,因为这是表示周期最好的方法。日历被认为是每一年都会重复的,而不是用来只预示未来一年的日期,也没有显示日期名称和时间之间的任何关联——我们所能知道的就是一周有七天。
从卡洛林王朝的手稿中可以看出一天和一周的划分(来源:Wikimedia Commons)
中世纪折叠年历,15世纪(来源:Wikimedia Commons)
在接下来的几十年里,人们一直使用这种重复的万年历,它可以计算出未来某一天是星期几——而不是像今天的日历一样可以显示出一年的具体日期。直到工业革命后印刷变得非常便宜,商家开始在广告上印出整年整年的日历,供普罗大众使用。
塞缪尔·莫兰的万年历d 1650年 (由http://www.britishmuseum.org提供)
现代历法与其他年代的历法一样都在演进,它们也采用网格式的表单收集宗教、文化、气象、天文和星象等信息。但是在15世纪后期,日历的样式发生了重大的变化,从之前的圆周永续的样式,改为了表单排列的形式。
不仅仅是因为在1582年进行了的日历的修订,而且还可能是因为约翰内斯·古登堡(Johannes Gutenberg)引入了印刷术。
我没有历史事实来支持我的假设,但可以看出标准化表单网格系统在样式的灵活和固有的印刷属性之间存在着必然联系。任何特殊的设计都需要印刷商聘请艺术家制作单独的印刷模版。
模板通常是木质的,而不是我们日常使用中看起来的样子。所以可能是印刷技术在很大程度上使得网格日历的样式成为更实用的标准,尤其是他可以很容易地将一年中的月份排列出来。
威廉·帕森斯 17世纪欧洲年表
塞缪尔·莫兰(Samuel Morland)写于1650年的《万年历》(perpetual almanac)是一个早期使用这种网格表单样式的例子。有趣的是,其中没有提到工作日的名字,只用字母表的前7个字母表示,这种格式在17世纪早期威廉·帕森斯(William Parsons)的手稿上也能看到。
在1773年,作为出版商的罗伯特·艾特肯(Robert Aitken)发明了他所声称的美国第一个日程表,为客户提供一个已经排好的板式,与现在看到的还是有所不同。有史以来第一次,美国人民可以在一个表单里看到自己一天要做的
事情,就像今天我们使用的to-do list(任务计划表),这已经超出了原本使用年历来预示日期的常规用法。
伊丽莎白·诺尔斯(ElizabethKnowles)的刺绣万年历,1787年。(由V&A收藏提供)
除了它们的实际用途之外,日历已经成为一种日常装饰品。18世纪后期和第二次工业革命期间,广告日历已经成为商家在用户家庭中传播品牌特征的一种流行方式。酒店、加油站、银行、大小品牌都会提供带有广告的免费日历,以此提高他们的曝光率。
日历品牌赞助 1888
直到20世纪60年代回归到了极简主义风格,开始把图形设计和字体设计作为优化的对象,也通过引入黑体字体样式和现代主义的方式来促进设计的改良。
在那时最著名的和最让人兴奋的产品恰好是两位意大利人同一年各自创作的经典之作:Vignelli著名的大型挂历和Enzo Mari的永久年历,两者仍然都在家庭中使用。
日历达到最大可用性了吗?也许是这样,但是在Android(安卓)或iOS(苹果)上出现的最新数字版本证明:只要人们有意愿尝试新颖的设计形式,毫无疑问日历的设计仍然存在着提升空间。
一些设计师试图打破惯例。其中最著名的是约翰·梅达(John Maeda)。他设计了新颖的日历界面体验-Line Calendar(线性日历https://www.youtube.com/watch?v=NbIhng27qTk)已经被MoMA(纽约现代艺术博物馆)永久收藏。
最近的是一家加拿大的设计机构Teehan+Lax(现在已经被Facebook收购了),玩出了一个新的范例,你可以在这里看它的原型(https://vimeo.com/69623560)。
当然,对于许多日历来说,没有什么比我们所知道的简单网格更好了。
《斯坦迪格历法》(Stendig Calendar),马西莫·维涅尼尼(MassimoVigne11i)著,1966年
Android(安卓)日历app 2017
321:君士坦丁大帝颁布了一周七天工作制
900-1500:这一时期的文物显示,日历的样式都是圆形
1500:古腾堡印刷机和宽边印刷出现
1582年:格利高里公历修订
1650年:萨缪尔·莫兰年鉴-万年历
1650-1800:万年历以网格形式出现
1860年及以后:出现了带有网格日历的海报 1966年:马西莫·维格内利,《斯坦迪格日历》
1966年:恩佐·马里,东帝汶万年历
1995:约翰·梅达 2000年设计的线性日历
原文作者:Francesco Bertell(弗朗西斯科·贝尔泰利)
本文由@one点设计 翻译发布于人人都是产品经理。未经许可,禁止转载
题图来自Unsplash,基于CC0协议
*请认真填写需求信息,我们会在24小时内与您取得联系。