最近做在做一个小程序项目,需要用到全国城市列表的选择,然后通过城市的首字母快速定位到要选择的城市,那么今天就记录下实现的过程,最终的效果如图:
整体结构是,左侧是城市的首字母和与首字母开头的所有城市的列表,右侧是一个城市首字母的快速定位列表,中间显示的白色阴影框中的大字母即为当前选中的字母。
实现步骤:
一、获取到全国城市的数据
1、通过腾讯地图接口获取数据 https://lbs.qq.com/qqmap_wx_jssdk/method-getcitylist.html
2、直接下载本地使用:https://raw.githubusercontent.com/749264345/wechat-miniapp-map/master/libs/city.js
本人就是采用下载本地js文件使用,具体看个人需求
二、页面布局:
页面布局相信大家都会了,直接上代码
<view class="city-page">
<scroll-view class="city-scrollView" scroll-y="true" scroll-into-view="{{scrollTopId}}"
scroll-with-animation="true" enable-back-to-top="true" bindscroll="handleScroll">
<view class="city-item" id="current">
<view class="city-sub-item city-text v-flex city-sub-location" bindtap="handleClickLocation">
<image src="{{images.location}}" mode="widthFix" class="location-image"></image>
当前位置
<text class="city-sub-text">{{locationCity}}</text>
</view>
</view>
<view class="city-item listGroup" wx:for="{{cityList}}" wx:for-index="idx" wx:for-item="group" wx:key="key">
<view class="city-sub-item city-py" id="{{idx}}">{{idx}}</view>
<view class="city-sub-item city-text" wx:for="{{group}}" wx:key="key"
data-fullname="{{item.fullname}}" data-lat="{{item.location.lat}}"
data-lng="{{item.location.lng}}" bindtap="selectCity">{{item.fullname}}</view>
</view>
</scroll-view>
<!-- 右侧字母表 -->
<view class="city-py-label">
<view wx:for="{{cityPy}}" wx:key="index" data-id="{{item}}" data-index="{{index}}"
bindtouchstart="handleTouchStart" bindtouchend="handleTouchEnd" bindtouchmove="handleTouchMove"
class="{{currentIndex==index?'text-primary':''}}">{{item}}</view>
</view>
<!-- 当前触摸的字母 -->
<view class="v-flex v-shadow city-single-py" wx:if="{{hidden}}">
<view>{{scrollTopId}}</view>
</view>
</view>
因为需要滚动列表,所以必须用小程序的标签scroll-view布局,
scroll-y="true":表示y轴方向上滚动
scroll-into-view="{{scrollTopId}}" : 设置滚动到某个元素
scroll-with-animation="true" : 在设置滚动条位置时使用动画过渡
具体其他属性值可参考微信小程序官方文档
https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html
page,.city-page{
width: 100%;
height: 100%;
overflow: hidden;
}
.city-scrollView{
width: 100%;
height: 100%;
}
.city-item{
width: 100%;
}
.city-sub-location{
align-items: center;
}
.city-sub-item{
width: 100%;
height: 80rpx;
line-height: 80rpx;
padding: 0 30rpx;
box-sizing: border-box;
border-bottom: 1px solid #f5f4f3;
}
.location-image{
width: 38rpx;
height: 38rpx;
margin-right: 20rpx;
}
.city-sub-text{
color: #333;
font-weight: bold;
display: block;
margin-left: 36rpx;
}
.city-text{
background: #fff;
}
.city-py{
/* background: #e8e8e8; */
}
.city-py-label{
width: 100rpx;
height: 100%;
position: absolute;
display: flex;
flex-direction: column;
justify-content: center;
top: 0;
right: 0;
}
.city-py-label>view{
text-align: center;
height: 20px;
line-height: 20px;
}
.city-single-py{
position: absolute;
width: 145rpx;
height: 145rpx;
justify-content: center;
align-items: center;
z-index: 2;
top: 50%;
left: 50%;
transform: translate3d(-50%,-50%,0);
}
.city-single-py>view{
width: 140rpx;
height: 140rpx;
border-radius: 5rpx;
background: #fff;
text-align: center;
line-height: 140rpx;
color: #333;
font-weight: bold;
font-size: 40rpx;
border: 1px solid #f5f4f3;
}
三、js实现逻辑
引入全国城市列表数据,并在data初始化,在页面渲染数据
const city = require('./../../../utils/city')
Page({
/**
* 页面的初始数据
*/
data: {
images: {
location: app.globalData.imagePath + "icon_location.png"
},
cityList: city.all, //所有城市列表
cityPy: '', //右侧首字母列表
currentIndex: 0, //当前显示的字母index
hidden: false, //是否隐藏当前显示中间大字母提示
scrollTopId: 'current', //滚动到当前的字母城市
locationCity: ''
}
})
在生命周期函数onShow遍历cityList得到所有首字母数据并存放早cityPy数据里,在页面渲染,以供后面使用
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
let cityPy = []
// 遍历城市数据,把字母放到cityPy数据里
for(var key in this.data.cityList){
cityPy.push(key)
}
this.setData({
cityPy: cityPy
})
}
接下来当点击右侧字母表的时候滚动到当前所点击对应首字母的城市,
点击字母,将当前的index序号和对应的首字母传过来,并通过设置scroll-into-view的值就可以滚动到对应字母城市,(并记录当前触摸的clientY的值,接下来会用到)
// 触摸开始
handleTouchStart(e) {
// 首次获取到clientY的值并记录为y1
var y = e.touches[0].clientY
this.touches.y1 = y
// 获取到当前的index,并记录在touches里
this.touches.anchorIndex = e.currentTarget.dataset.index
// 把当前点击的字母显示在屏幕中央
this.setData({
hidden: true,
scrollTopId: e.currentTarget.dataset.id,
currentIndex: e.currentTarget.dataset.index
})
},
滑动时获取到当前的clientY的值存放到touches里,这时候要用到handleTouchStart时存放在touches里的y1了,从开始触摸到滑动一段距离,那么y2-y1就是滑动的距离,然后右侧字母列表的高度是我们设置的,比如我设置了20px的高度,那么(y2-y1)/2再取整就是当前所滑动的个数了,即滑动了多少个字母的高度,再加上touchstart触摸时的index即为当前所滑动目标的字母的序号anchorIndex ,再由这个anchorIndex 在cityPy数组中找到对应序号的字母,从而滚动到该位置的城市。
// 滑动触摸
handleTouchMove(e){
// 滑动过程中获取当前的clientY值并记录为y2
var y = e.touches[0].clientY
this.touches.y2 = y
// 根据y2-y1得到所滑动的距离除以每个字母的高度20得到字母的个数,
// 加上第一次获取的anchorindex得到当前的序号index
const delt = (this.touches.y2 - this.touches.y1) / 20 | 0
let anchorIndex = this.touches.anchorIndex + delt
// 由当前的序号index在字母表数组中找到字母并显示在屏幕中
this.setData({
hidden: true,
scrollTopId: this.data.cityPy[anchorIndex],
currentIndex: anchorIndex
})
},
// 触摸结束
handleTouchEnd() {
setTimeout(() =>{
this.setData({
hidden: false,
})
}, 0)
},
通过上的计算我们的功能已经实现了一大半了,但是一个完整的功能应该是当你点击字母列表或者触摸滑动字母列表的时候选中的当前字母应该高亮,同样当你滑动或者滚动左侧城市列表的时候,滚动到哪个区域应该高亮显示对应的首字母,那么我们就来实现这个功能:
<view class="city-item listGroup" wx:for="{{cityList}}" wx:for-index="idx" wx:for-item="group" wx:key="key">
<view class="city-sub-item city-py" id="{{idx}}">{{idx}}</view>
<view class="city-sub-item city-text" wx:for="{{group}}" wx:key="key"
data-fullname="{{item.fullname}}" data-lat="{{item.location.lat}}"
data-lng="{{item.location.lng}}" bindtap="selectCity">{{item.fullname}}</view>
</view>
首先页面加载的时候执行_calculateHeight()方法,必须要在城市渲染完在页面里才能执行,因为这是计算dom节点高度的方法。通过这个方法可以获取到所有listGroup距离容器顶部的距离,并保存在listHeight数组
然后滑动左侧城市列表时遍历listHeight数组,看当前滚动的长度scrollTop(scrollTop是由小程序bindscroll方法提供的数值)是否落在listHeight数组某个数据区域高度内,然后得到对应的序号index
// 计算listGroup高度
_calculateHeight() {
const that = this
this.listHeight = []
let height = 0
this.listHeight.push(height)
wx.createSelectorQuery().selectAll('.listGroup').boundingClientRect(function(rects){
rects.forEach(function(rect){
height += rect.height
that.listHeight.push(height)
})
}).exec()
},
// 滚动时触发
handleScroll(e) {
let scrollTop = e.detail.scrollTop
const listHeight = this.listHeight
// 遍历listHeight数据,如果当前的scrollTop大于height1小于height2时
// 说明当前滚到到这个字母城市区域,获取到当前的索引i值
for(var i=0;i<listHeight.length;i++){
let height1 = listHeight[i]
let height2 = listHeight[i + 1]
if(scrollTop > height1 && scrollTop < height2){
this.setData({
currentIndex: i
})
}
}
},
到这里我们的代码就实现完了,最后贴上完整的js代码
// pages/index/cityList/city.js
const city = require('./../../../utils/city')
const app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
images: {
location: app.globalData.imagePath + "icon_location.png"
},
cityList: city.all, //所有城市列表
cityPy: '', //右侧首字母列表
currentIndex: 0, //当前显示的字母index
hidden: false, //是否隐藏当前显示中间大字母提示
scrollTopId: 'current', //滚动到当前的字母城市
locationCity: ''
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.listHeight = []
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
// 触摸左侧字母表所用的数据
this.touches = {}
let cityPy = []
// 遍历城市数据,把字母放到cityPy数据里
for(var key in this.data.cityList){
cityPy.push(key)
}
this.setData({
cityPy: cityPy
})
// 获取当前定位城市
this.setData({
locationCity: app.globalData.locationInfo.fullname
})
// 页面显示后计算listGroup的高度
this._calculateHeight()
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
},
/**
* 自定义事件
*/
// 点击选择城市
selectCity(e) {
const dataset = e.currentTarget.dataset
app.globalData.selectCityInfo = {
fullname: dataset.fullname,
lat: dataset.lat,
lng: dataset.lng
}
wx.navigateBack()
},
// 触摸开始
handleTouchStart(e) {
// 首次获取到clientY的值并记录为y1
var y = e.touches[0].clientY
this.touches.y1 = y
// 获取到当前的index,并记录在touches里
this.touches.anchorIndex = e.currentTarget.dataset.index
// 把当前点击的字母显示在屏幕中央
this.setData({
hidden: true,
scrollTopId: e.currentTarget.dataset.id,
currentIndex: e.currentTarget.dataset.index
})
},
// 滑动触摸
handleTouchMove(e){
// 滑动过程中获取当前的clientY值并记录为y2
var y = e.touches[0].clientY
this.touches.y2 = y
// 根据y2-y1得到所滑动的距离除以每个字母的高度20得到字母的个数,
// 加上第一次获取的anchorindex得到当前的序号index
const delt = (this.touches.y2 - this.touches.y1) / 20 | 0
let anchorIndex = this.touches.anchorIndex + delt
// 由当前的序号index在字母表数组中找到字母并显示在屏幕中
this.setData({
hidden: true,
scrollTopId: this.data.cityPy[anchorIndex],
currentIndex: anchorIndex
})
},
// 触摸结束
handleTouchEnd() {
setTimeout(() =>{
this.setData({
hidden: false,
})
}, 0)
},
// 获取定位地址
handleClickLocation() {
app.getLocation(()=>{
wx.navigateBack()
})
},
// 计算listGroup高度
_calculateHeight() {
const that = this
this.listHeight = []
let height = 0
this.listHeight.push(height)
wx.createSelectorQuery().selectAll('.listGroup').boundingClientRect(function(rects){
rects.forEach(function(rect){
height += rect.height
that.listHeight.push(height)
})
}).exec()
},
// 滚动时触发
handleScroll(e) {
let scrollTop = e.detail.scrollTop
const listHeight = this.listHeight
// 遍历listHeight数据,如果当前的scrollTop大于height1小于height2时
// 说明当前滚到到这个字母城市区域,获取到当前的索引i值
for(var i=0;i<listHeight.length;i++){
let height1 = listHeight[i]
let height2 = listHeight[i + 1]
if(scrollTop > height1 && scrollTop < height2){
this.setData({
currentIndex: i
})
}
}
},
})