仿iPhone通讯录制作小程序自定义选择组件的实现


Posted in Javascript onMay 23, 2019

前言

近期闲来无事,想着闲着也是闲着,不如给自己搞点事情做!敢想敢做,于是选择了给微信小程序做个 仿iPhone通讯录 效果的自定义组件。

先来整理一下,瞧瞧需要实现的核心功能。

仿iPhone通讯录制作小程序自定义选择组件的实现

  1. 按照第一个字的首字母排序;
  2. 实现输入搜索功能;
  3. 侧边栏字母导航;

仿iPhone通讯录制作小程序自定义选择组件的实现

基本上分为3块:

  1. 顶部的搜索区域;
  2. 内容的展示区域;
  3. 侧边字母导航栏区域;
// index.wxml
<view class="main">
 <!-- 顶部搜索区域 -->
 <view class="header">
 </view>
 <!-- 内容区域 -->
 <scroll-view class="scroll">
 </scroll-view>
 <!-- 侧边导航 -->
 <view class="sub_nav">
 </view>
</view>

【顶部的搜索区域】

仿iPhone通讯录制作小程序自定义选择组件的实现

一目了然就直接贴代码了。

<view class="header">
 // 这里或许有人要问,为啥不用小程序的label组件呢。?_?
 // 原因就是...我就不用,你还能咬我?!^(oo)^
 // 哈哈哈哈~开个玩笑,其实是小程序的label组件还没支持input!
 <view class="label"> 
  <icon></icon>
  <input type="text" placeholder="搜索" />
 </view>
</view>

【内容的展示区域】

仿iPhone通讯录制作小程序自定义选择组件的实现

再说一目了然会不会被打呢?:joy:

根据图片就可以看出来,存在2个区域。

  1. 红框包围的外框,负责圈定展示的范围;
  2. 绿框包围的范围,包含有字母标题和对应的子项。

代码如下:

<scroll-view class="scroll">
 <view class="dl">
  <view class="dt">这里是字母标题。</view>
  <view class="dd">
  <span>这里当然是展示的内容啦。</span>
  </view>
 </view>
 </scroll-view>

【侧边字母导航栏区域】

为了节省一下文章的篇幅,这里就不贴图了,很简单,就是并排下来就好了。

<view class="sub_nav">
 <view class="option">这里是输出字母。</view>
</view>

接下来是wxss的样式了。

考虑到wxss的样式较多,我就直接贴 代码链接 吧,有兴趣的童鞋可以瞧瞧。

完成之后,是时候贴个效果图了。(不许吐槽丑,宝宝会不开心的!:pensive:)

仿iPhone通讯录制作小程序自定义选择组件的实现

结构样式弄完了,也贴一下自定组件的基础文件

// index.json
{
 "component": true
}
// index.js
Component({
 properties: {}, // 组件的对外属性
 data: {},  // 组件的内部数据
 lifetimes: {}, // 生命周期
 methods: {}  // 事件
});

现在开始实现功能了!!!

按照第一个字的首字母排序

说实话,实现这块功能呢,我是没啥头绪的,所以这个时候就要求助伟大的“度娘/Google”了。

经过楼主“遍寻网络”,查找到如下页面的源码参考:

仿iPhone通讯录制作小程序自定义选择组件的实现

因楼主问题,遗忘了该网址,如有知道的童鞋,贴个链接告诉下楼主,楼主立马麻溜的加上。 源码的原理大概描述下:

收录 20902 个汉字和 375 个多音字的 Unicode 编码,然后用JS切割首字母并转换成 Unicode 进行对比,最后返回对应首字母的拼音。

// 汉字对应的Unicode编码文件
// oMultiDiff = 多音字 | firstLetterMap = 汉字
import firstStore from './firstChineseLetter'; 

// 获取首字母拼音
function getFirstLetter (val) {
 const firstVal = val.charAt(0);
 if (/.*[\u4e00-\u9fa5]+.*/.test(firstVal)) {
 // 处理中文字符
 // 转换成Unicode编码,与firstStore里面的数据进行对比,然后返回对应的参数
 const code = firstVal.charCodeAt(0); // 转换成Unicode编码
 return code in firstStore.oMultiDiff ? firstStore.oMultiDiff[code] : firstStore.firstLetterMap.charAt(code - 19968);
 } else {
 // 这里处理非中文
 // 检测是否字母,如果是就直接返回大写的字母
 // 不是的话,返回“#”
 return /^[a-zA-Z]+$/.test(firstVal) ? firstVal.toUpperCase() : '#';
 }
}

getFirstLetter('东城区');
// 输出结果:D

firstChineseLetter.js地址

获取首字母的方法有了之后,就该对数据进行处理了。

首先定义一下组件所需要的参数。

Component({
 // 组件的对外属性
 properties: {
 data: { type: Array, value: [], }, // 组件外传递进来的数据
 attr: { type: String, value: 'label' }, // 需要进行首字母处理的属性,默认是"label"
 },
 ...
})

然后,针对组件外传递进来的数据,做一次转换。

// 静态数据的存储
const Static = {
 list: []
}

Component({
 ...
 methods: {
 // 初始/重置数据
 init () {
  const { data, attr } = this.properties;

  let changeData = [], // 转换后的数据
   inChangeData = {}; // 存储转换后的数据对应字母的索引值
   
  data.map(v => {
  // 获取首字母拼音
  let firstLetter = this.getFirstLetter(v[attr]); 
  
  // 循环对比检测
  firstLetter.split('').map(str => {
   if (str in inChangeData) {
   // 有首字母相同的项,
   // 则添加入已有的项里面
   changeData[inChangeData[str]].list.push(v);
   } else {
   // 没有首字母相同的项,
   // 则在尾部追加一条新的数据,
   // 储存对应的字母值(firstLetter),
   // 同时存储该字母对应的索引
   changeData.push({ firstLetter: str, list: [v] });
   inChangeData[str] = changeData.length - 1;
   }
  });
  });
  
  // 此时转换后的数组属于乱序,
  // 需要对乱序的数组进行排序
  changeData.sort((pre, next) => pre.firstLetter < next.firstLetter ? -1 : 1);
  
  // 若存在“#”项,将位置位移至底部
  if (changeData[0].firstLetter === '#') {
  const firstArr = changeData.splice(0, 1);
  changeData = [...changeData, ...firstArr];
  }

  // 存储转换后的数据,
  // this.data.list的数据对应页面的展示数据,因为有搜索功能,数据可能会变更,
  // 在静态的数据里面,也存储1份数据,方便后续的搜索等功能。
  this.setData({ list: changeData });
  Static.list = changeData;
 },
 }
 ...
});

初始化函数有了之后呢,当然是调用它啦。

Component({
 lifetimes: {
 // 在组件实例进入页面节点树时执行初始化数据
 attached () {
  this.init();
 }
 },
 observers: {
 // 考虑到组件传递的数据存在变更的可能,
 // 在数据变更的时候,也要做一次初始化
 'data, attr, icon' (data, attr) {
  this.init();
 }
 },
})

接下来是搜索功能啦~

先给页面搜索框加个监听事件(input)

<view class="main">
 ...
 <view class="header">
 <view class="label">
  <icon></icon>
  <input type="text" placeholder="搜索" value="{{ search }}" bindinput="searchData" />
 </view>
 </view>
 ...
</view>

接着是JS的事件

const Static = {
 list: []
}

Component({
 ...
 methods: {
 searchData (e) {
  const { value } = e.detail; // 用户输入的值
  const { list } = Static; // init存储的静态数据,用来做数据对比
  const { attr } = this.properties; // 要对比的属性值
  let result = [], tem = {};
  
  // 没有搜索内容,返回全部内容
  if (value.length === 0) { this.setData({ list: Static.list }); return; }

  // 检索搜索内容
  list.map(v => {
  // 获取所有跟value匹配上的数据
  const searchList = v.list.filter(v => v[attr].indexOf(value) !== -1);
  
  if (searchList.length > 0) {
   // 此处原理类似楼上init的对比,此处不细说,
   // 反正我懒我有理(0.0)
   if (v.firstLetter in tem) {
   const _list = result[tem[v.firstLetter]].lish;
   result[tem[v.firstLetter]].lish = [..._list, ...searchList];
   } else {
   result.push({ firstLetter: v.firstLetter, list: [...searchList] });
   tem[v.firstLetter] = result.length - 1;
   }
  }
  });
  
  // 存储数据
  this.setData({ list: result, search: value });
 }
 },
 ...
});

侧边栏字母导航

(突然觉得,写文好累啊!!!)

写这块的时候呢,楼主发现了iPhone通讯录侧边导航栏有个问题, 手指在字母导航栏上滑动的时候,有时候很难确认自己滑到了哪个区域?!

然鹅这个问题呢,楼主发现了微信的通讯录,针对这块添加了手指滑动的时候,添加了个结构来帮助用户确认目前所处的区域。

楼主本着学习的精神,借(chao)鉴(xi)了这个效果,来个效果图。

仿iPhone通讯录制作小程序自定义选择组件的实现

贴一下新的wxml结构

<!-- 侧边导航 -->
 <view class="sub_nav" id="subNav" catchtouchstart="subTouchStart" catchtouchmove="subTouchMove" catchtouchend="subTouchEnd">
 <view class="option" wx:for="{{ list }}" data-firstLetter="{{ item.firstLetter }}" wx:key="firstLetter">
  {{ item.firstLetter }}
  <!-- 以下这块就是新增的结构啦 S -->
  <view 
  class="max {{ item.firstLetter === scrollIntoView && subNavHint ? 'show' : '' }}" 
  data-desc="{{ item.firstLetter }}"
  ></view>
  <!-- 以上这块就是新增的结构啦 E -->
 </view>
 </view>
const Static = {
 list: [],
 timer: null
}

Component({
 ...
 data: {
 scrollIntoView: '', // 标记当前处于哪个字母
 subNavHint: false, // 控制借(chao)鉴(xi)微信效果的元素
 },
 methods: {
 subTouchStart () {
  this.setData({ subNavHint: true, scrollIntoView: '' });
 },
 subTouchEnd () {
  this.setData({ subNavHint: false });
 },
 subTouchMove (e) {
  // 获取字母导航栏元素对应的值
  const query = this.createSelectorQuery();
  query.select('#subNav').boundingClientRect();
  query.selectViewport().scrollOffset();
  query.exec(res => {
  const { clientY } = e.touches[0]; // Y轴的位置
  const DomTop = res[0].top; // 导航元素距离顶部的位置
  const { list } = this.data;
  
  // 计算索引,
  // 或许看到这里有人会疑问,为什么是除以20?
  // 因为样式里面,我写的高度是20px,所以每个字母的区域是20px。
  let index = Math.round((clientY - DomTop) / 20); 
  index = index >= list.length ? list.length - 1 : index; // 限制索引大于0
  index = index < 0 ? 0 : index; // 限制索引小于0
  // 限制结果重复赋值
  if (list[index].firstLetter !== this.data.scrollIntoView) {
   this.setData({ scrollIntoView: list[index].firstLetter });
   // 加个抖动效果
   wx.vibrateShort(); 
  }
  });
  }
 },
 }
 ...
});

结语

文章写到这呢,基本上核心的功能都已经实现啦~ :stuck_out_tongue_closed_eyes:(终于写完了...)

通过自己封装组件,楼主还是有挺大收获的!

当然,这个组件还有很多可以继续完善的地方,有兴趣的童鞋呢,可以提出你的优化建议,楼主有时(xing)间(qu)的话,会继续完善下去。

最后,还是推一下这个组件啦,希望它能帮到有需要的童鞋。

github地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
学习YUI.Ext基础第一天
Mar 10 Javascript
Javascript匿名函数的一种应用 代码封装
Jun 27 Javascript
两种方法实现文本框输入内容提示消失
Mar 17 Javascript
css如何让浮动元素水平居中
Aug 07 Javascript
JavaScript如何实现对数字保留两位小数一位自动补零
Dec 18 Javascript
Bootstrap对话框使用实例讲解
Sep 24 Javascript
微信小程序 参数传递实例代码
Mar 20 Javascript
Angualrjs和bootstrap相结合实现数据表格table
Mar 30 Javascript
vue之数据交互实例代码
Jun 16 Javascript
Vue添加请求拦截器及vue-resource 拦截器使用
Nov 23 Javascript
Bootstrap4 gulp 配置详解
Jan 06 Javascript
借助云开发实现小程序短信验证码的发送
Jan 06 Javascript
判断js数据类型的函数实例详解
May 23 #Javascript
JS定义函数的几种常用方法小结
May 23 #Javascript
vue-test-utils初使用详解
May 23 #Javascript
了解前端理论:rscss和rsjs
May 23 #Javascript
微信小程序使用字体图标的方法
May 23 #Javascript
个人小程序接入支付解决方案
May 23 #Javascript
一篇文章介绍redux、react-redux、redux-saga总结
May 23 #Javascript
You might like
destoon利用Rewrite规则设置网站安全
2014/06/21 PHP
10个超级有用的PHP代码片段果断收藏
2015/09/23 PHP
PHP获取网页所有连接的方法(附demo源码下载)
2016/03/30 PHP
PHP模板引擎Smarty之配置文件在模板变量中的使用方法示例
2016/04/11 PHP
Ext JS添加子组件的误区探讨
2013/06/28 Javascript
JS代码同步文本框内容的实例方法
2013/07/12 Javascript
深入领悟JavaScript中的面向对象
2013/11/18 Javascript
常用的JavaScript验证正则表达式汇总
2013/11/26 Javascript
jquery实现网页查找功能示例分享
2014/02/12 Javascript
jQuery遍历对象、数组、集合实例
2014/11/08 Javascript
jquery实现鼠标点击后展开列表内容的导航栏效果
2015/09/14 Javascript
原生js实现移动端瀑布流式代码示例
2015/12/18 Javascript
JavaScript的ExtJS框架中表格的编写教程
2016/05/21 Javascript
JavaScript核心语法总结(推荐)
2016/06/02 Javascript
JavaScript中的Array 对象(数组对象)
2016/06/02 Javascript
Node.js中的不安全跳转如何防御详解
2018/10/21 Javascript
Vue利用localStorage本地缓存使页面刷新验证码不清零功能的实现
2020/09/04 Javascript
[04:26]2014DOTA2国际邀请赛-Newbee顺利进入胜者组决赛 独家专访战神7
2014/07/19 DOTA
[01:06:54]DOTA2-DPC中国联赛 正赛 SAG vs DLG BO3 第二场 2月28日
2021/03/11 DOTA
python构造icmp echo请求和实现网络探测器功能代码分享
2014/01/10 Python
Python中的FTP通信模块ftplib的用法整理
2016/07/08 Python
Python爬取网页中的图片(搜狗图片)详解
2017/03/23 Python
Python中Django 后台自定义表单控件
2017/03/28 Python
利用python打印出菱形、三角形以及矩形的方法实例
2017/08/08 Python
Tensorflow之构建自己的图片数据集TFrecords的方法
2018/02/07 Python
pygame游戏之旅 游戏中添加显示文字
2018/11/20 Python
新加坡航空官方网站:Singapore Airlines
2016/10/13 全球购物
英国男士时尚购物网站:Stuarts London
2017/10/22 全球购物
Linux如何修改文件和文件夹的权限
2012/06/27 面试题
伦敦奥运会口号
2014/06/13 职场文书
小学生三分钟演讲稿
2014/08/18 职场文书
医院护士党的群众路线教育实践活动对照检查材料思想汇报
2014/10/04 职场文书
2014年公务员转正工作总结
2014/11/07 职场文书
2014年学校体育工作总结
2014/12/08 职场文书
2015七夕情人节宣传语
2015/07/14 职场文书
nginx配置虚拟主机的详细步骤
2021/07/21 Servers