仿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 相关文章推荐
JavaScript的parseInt 进制问题
May 07 Javascript
Javascript常考语句107条收集
Mar 09 Javascript
jQuery lazyload 的重复加载错误以及修复方法
Nov 19 Javascript
基于JavaScript实现 获取鼠标点击位置坐标的方法
Apr 12 Javascript
javascript表单验证使用示例(javascript验证邮箱)
Jan 07 Javascript
ext前台接收action传过来的json数据示例
Jun 17 Javascript
AngularJS表单提交实例详解
Feb 18 Javascript
详解Node.js串行化流程控制
May 04 Javascript
vue高德地图之玩转周边
Jun 16 Javascript
Webpack打包css后z-index被重新计算的解决方法
Jun 18 Javascript
使用命令行工具npm新创建一个vue项目的方法
Dec 27 Javascript
node.JS路径解析之PATH模块使用方法详解
Feb 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
DC游戏Steam周三特惠 《蝙蝠侠》阿卡姆系列平史低
2020/04/09 欧美动漫
Apache 配置详解(最好的APACHE配置教程)
2010/07/04 PHP
php小技巧 把数组的键和值交换形成了新的数组,查找值取得键
2011/06/02 PHP
用JavaScript实现仿Windows关机效果
2007/03/10 Javascript
同域jQuery(跨)iframe操作DOM(实例讲解)
2013/12/19 Javascript
JavaScript在IE和FF下的兼容性问题
2014/05/19 Javascript
jquery列表拖动排列(由项目提取相当好用)
2014/06/17 Javascript
js命名空间写法示例
2015/12/18 Javascript
Javascript中的对象和原型(二)
2016/08/12 Javascript
JS给Array添加是否包含字符串的简单方法
2016/10/29 Javascript
js滚轮事件兼容性问题需要注意哪些
2016/11/15 Javascript
ES6新特性之Symbol类型用法分析
2017/03/31 Javascript
详解vue2 $watch要注意的问题
2017/09/08 Javascript
axios取消请求的实践记录分享
2018/09/26 Javascript
[02:31]2014DOTA2国际邀请赛2009专访:干爹表现出乎意料 看好DK杀回决赛
2014/07/20 DOTA
python with statement 进行文件操作指南
2014/08/22 Python
python编程培训 python培训靠谱吗
2018/01/17 Python
Pytoch之torchvision.transforms图像变换实例
2019/12/30 Python
PyTorch加载自己的数据集实例详解
2020/03/18 Python
一文读懂Python 枚举
2020/08/25 Python
python实现网页录音效果
2020/10/26 Python
python3中TQDM库安装及使用详解
2020/11/18 Python
解决tensorflow模型压缩的问题_踩坑无数,总算搞定
2021/03/02 Python
漫威玩具服装及周边商品官方购物网站:Marvel Shop
2019/05/11 全球购物
最畅销的视频游戏享受高达90%的折扣:CDKeys
2020/02/10 全球购物
编写一个类体现构造,公有,私有方法,静态,私有变量
2013/08/10 面试题
教师评优的个人自我评价分享
2013/09/19 职场文书
优秀生推荐信范文
2013/11/28 职场文书
工作的心得体会
2013/12/31 职场文书
致800米运动员广播稿
2014/02/16 职场文书
七匹狼男装广告词
2014/03/21 职场文书
党员一句话承诺大全
2014/03/28 职场文书
业务员自荐信范文
2014/04/20 职场文书
python3.9之你应该知道的新特性详解
2021/04/29 Python
基于JavaScript实现年月日三级联动
2021/06/22 Javascript
能让Python提速超40倍的神器Cython详解
2021/06/24 Python