溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件

發(fā)布時間:2021-06-17 13:47:29 來源:億速云 閱讀:142 作者:小新 欄目:web開發(fā)

這篇文章主要介紹仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

前言

給微信小程序做個 仿iPhone通訊錄 效果的自定義組件。

先來整理一下,瞧瞧需要實現(xiàn)的核心功能。

仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件

  1. 按照第一個字的首字母排序;

  2. 實現(xiàn)輸入搜索功能;

  3. 側(cè)邊欄字母導航;

仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件

基本上分為3塊:

  1. 頂部的搜索區(qū)域;

  2. 內(nèi)容的展示區(qū)域;

  3. 側(cè)邊字母導航欄區(qū)域;

// index.wxml
<view class="main">
 <!-- 頂部搜索區(qū)域 -->
 <view class="header">
 </view>
 <!-- 內(nèi)容區(qū)域 -->
 <scroll-view class="scroll">
 </scroll-view>
 <!-- 側(cè)邊導航 -->
 <view class="sub_nav">
 </view>
</view>

【頂部的搜索區(qū)域】

仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件

一目了然就直接貼代碼了。

<view class="header">
 // 這里或許有人要問,為啥不用小程序的label組件呢。?_?
 // 原因就是...我就不用,你還能咬我?!^(oo)^
 // 哈哈哈哈~開個玩笑,其實是小程序的label組件還沒支持input!
 <view class="label"> 
  <icon></icon>
  <input type="text" placeholder="搜索" />
 </view>
</view>

【內(nèi)容的展示區(qū)域】

仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件

再說一目了然會不會被打呢?:joy:

根據(jù)圖片就可以看出來,存在2個區(qū)域。

  1. 紅框包圍的外框,負責圈定展示的范圍;

  2. 綠框包圍的范圍,包含有字母標題和對應的子項。

代碼如下:

<scroll-view class="scroll">
 <view class="dl">
  <view class="dt">這里是字母標題。</view>
  <view class="dd">
  <span>這里當然是展示的內(nèi)容啦。</span>
  </view>
 </view>
 </scroll-view>

【側(cè)邊字母導航欄區(qū)域】

為了節(jié)省一下文章的篇幅,這里就不貼圖了,很簡單,就是并排下來就好了。

<view class="sub_nav">
 <view class="option">這里是輸出字母。</view>
</view>

接下來是wxss的樣式了。

考慮到wxss的樣式較多,我就直接貼 代碼鏈接 吧,有興趣的童鞋可以瞧瞧。

完成之后,是時候貼個效果圖了。(不許吐槽丑,寶寶會不開心的!:pensive:)

仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件

結(jié)構樣式弄完了,也貼一下自定組件的基礎文件

// index.json
{
 "component": true
}
// index.js
Component({
 properties: {}, // 組件的對外屬性
 data: {},  // 組件的內(nèi)部數(shù)據(jù)
 lifetimes: {}, // 生命周期
 methods: {}  // 事件
});

現(xiàn)在開始實現(xiàn)功能了?。?!

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

說實話,實現(xiàn)這塊功能呢,我是沒啥頭緒的,所以這個時候就要求助偉大的“度娘/Google”了。

經(jīng)過樓主“遍尋網(wǎng)絡”,查找到如下頁面的源碼參考:

仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件

因樓主問題,遺忘了該網(wǎng)址,如有知道的童鞋,貼個鏈接告訴下樓主,樓主立馬麻溜的加上。 源碼的原理大概描述下:

收錄 20902 個漢字和 375 個多音字的 Unicode 編碼,然后用JS切割首字母并轉(zhuǎn)換成 Unicode 進行對比,最后返回對應首字母的拼音。

// 漢字對應的Unicode編碼文件
// oMultiDiff = 多音字 | firstLetterMap = 漢字
import firstStore from './firstChineseLetter'; 

// 獲取首字母拼音
function getFirstLetter (val) {
 const firstVal = val.charAt(0);
 if (/.*[\u4e00-\u9fa5]+.*/.test(firstVal)) {
 // 處理中文字符
 // 轉(zhuǎn)換成Unicode編碼,與firstStore里面的數(shù)據(jù)進行對比,然后返回對應的參數(shù)
 const code = firstVal.charCodeAt(0); // 轉(zhuǎn)換成Unicode編碼
 return code in firstStore.oMultiDiff ? firstStore.oMultiDiff[code] : firstStore.firstLetterMap.charAt(code - 19968);
 } else {
 // 這里處理非中文
 // 檢測是否字母,如果是就直接返回大寫的字母
 // 不是的話,返回“#”
 return /^[a-zA-Z]+$/.test(firstVal) ? firstVal.toUpperCase() : '#';
 }
}

getFirstLetter('東城區(qū)');
// 輸出結(jié)果:D

firstChineseLetter.js地址

獲取首字母的方法有了之后,就該對數(shù)據(jù)進行處理了。

首先定義一下組件所需要的參數(shù)。

Component({
 // 組件的對外屬性
 properties: {
 data: { type: Array, value: [], }, // 組件外傳遞進來的數(shù)據(jù)
 attr: { type: String, value: 'label' }, // 需要進行首字母處理的屬性,默認是"label"
 },
 ...
})

然后,針對組件外傳遞進來的數(shù)據(jù),做一次轉(zhuǎn)換。

// 靜態(tài)數(shù)據(jù)的存儲
const Static = {
 list: []
}

Component({
 ...
 methods: {
 // 初始/重置數(shù)據(jù)
 init () {
  const { data, attr } = this.properties;

  let changeData = [], // 轉(zhuǎn)換后的數(shù)據(jù)
   inChangeData = {}; // 存儲轉(zhuǎn)換后的數(shù)據(jù)對應字母的索引值
   
  data.map(v => {
  // 獲取首字母拼音
  let firstLetter = this.getFirstLetter(v[attr]); 
  
  // 循環(huán)對比檢測
  firstLetter.split('').map(str => {
   if (str in inChangeData) {
   // 有首字母相同的項,
   // 則添加入已有的項里面
   changeData[inChangeData[str]].list.push(v);
   } else {
   // 沒有首字母相同的項,
   // 則在尾部追加一條新的數(shù)據(jù),
   // 儲存對應的字母值(firstLetter),
   // 同時存儲該字母對應的索引
   changeData.push({ firstLetter: str, list: [v] });
   inChangeData[str] = changeData.length - 1;
   }
  });
  });
  
  // 此時轉(zhuǎn)換后的數(shù)組屬于亂序,
  // 需要對亂序的數(shù)組進行排序
  changeData.sort((pre, next) => pre.firstLetter < next.firstLetter ? -1 : 1);
  
  // 若存在“#”項,將位置位移至底部
  if (changeData[0].firstLetter === '#') {
  const firstArr = changeData.splice(0, 1);
  changeData = [...changeData, ...firstArr];
  }

  // 存儲轉(zhuǎn)換后的數(shù)據(jù),
  // this.data.list的數(shù)據(jù)對應頁面的展示數(shù)據(jù),因為有搜索功能,數(shù)據(jù)可能會變更,
  // 在靜態(tài)的數(shù)據(jù)里面,也存儲1份數(shù)據(jù),方便后續(xù)的搜索等功能。
  this.setData({ list: changeData });
  Static.list = changeData;
 },
 }
 ...
});

初始化函數(shù)有了之后呢,當然是調(diào)用它啦。

Component({
 lifetimes: {
 // 在組件實例進入頁面節(jié)點樹時執(zhí)行初始化數(shù)據(jù)
 attached () {
  this.init();
 }
 },
 observers: {
 // 考慮到組件傳遞的數(shù)據(jù)存在變更的可能,
 // 在數(shù)據(jù)變更的時候,也要做一次初始化
 'data, attr, icon' (data, attr) {
  this.init();
 }
 },
})

接下來是搜索功能啦~

先給頁面搜索框加個監(jiān)聽事件(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存儲的靜態(tài)數(shù)據(jù),用來做數(shù)據(jù)對比
  const { attr } = this.properties; // 要對比的屬性值
  let result = [], tem = {};
  
  // 沒有搜索內(nèi)容,返回全部內(nèi)容
  if (value.length === 0) { this.setData({ list: Static.list }); return; }

  // 檢索搜索內(nèi)容
  list.map(v => {
  // 獲取所有跟value匹配上的數(shù)據(jù)
  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;
   }
  }
  });
  
  // 存儲數(shù)據(jù)
  this.setData({ list: result, search: value });
 }
 },
 ...
});

側(cè)邊欄字母導航

(突然覺得,寫文好累?。。。。?/p>

寫這塊的時候呢,樓主發(fā)現(xiàn)了iPhone通訊錄側(cè)邊導航欄有個問題, 手指在字母導航欄上滑動的時候,有時候很難確認自己滑到了哪個區(qū)域?!

然鵝這個問題呢,樓主發(fā)現(xiàn)了微信的通訊錄,針對這塊添加了手指滑動的時候,添加了個結(jié)構來幫助用戶確認目前所處的區(qū)域。

樓主本著學習的精神,借(chao)鑒(xi)了這個效果,來個效果圖。

仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件

貼一下新的wxml結(jié)構

<!-- 側(cè)邊導航 -->
 <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 }}
  <!-- 以下這塊就是新增的結(jié)構啦 S -->
  <view 
  class="max {{ item.firstLetter === scrollIntoView && subNavHint ? 'show' : '' }}" 
  data-desc="{{ item.firstLetter }}"
  ></view>
  <!-- 以上這塊就是新增的結(jié)構啦 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,所以每個字母的區(qū)域是20px。
  let index = Math.round((clientY - DomTop) / 20); 
  index = index >= list.length ? list.length - 1 : index; // 限制索引大于0
  index = index < 0 ? 0 : index; // 限制索引小于0
  // 限制結(jié)果重復賦值
  if (list[index].firstLetter !== this.data.scrollIntoView) {
   this.setData({ scrollIntoView: list[index].firstLetter });
   // 加個抖動效果
   wx.vibrateShort(); 
  }
  });
  }
 },
 }
 ...
});

以上是“仿iPhone通訊錄如何實現(xiàn)小程序自定義選擇組件”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI