如何实现一个类似以下图片的九宫格?
拿到UI设计图的时候,感觉很简单,就是一个容器里面放几个子项(加上对应),然后flex布局就解决,当我写完的时候,发现好像不对,里面的每一项的边框并不符合UI图,仔细观察这个图我们可以发现每个外围的子项边框并不是完整的,需要单独处理子项才能达到和UI完全一致的效果
我们来一起分析每个子项(item)种类的边框类型:
第一种:只需要右边框和下边框(对应上图中的1, 2, 4, 5)
第二种:只需要下边框(对应上图3, 6)
第三种:只需要右边框(对应上图7, 8)
第四种:无边框(对应上图9)
有些同学就会问了---哎,为什么没有上边框和左边框?因为使用右下边框就可以模拟出来这个了,左上边框是和右下边框同理对称的我们这里使用右下边框来做分类!
子项边框分类的表格
| 子项(序号) | 上边框 | 右边框 | 下边框 | 左边框 |
|---|---|---|---|---|
| 1 | √ | √ | ||
| 2 | √ | √ | ||
| 3 | √ | |||
| 4 | √ | √ | ||
| 5 | √ | √ | ||
| 6 | √ | |||
| 7 | √ | |||
| 8 | √ | |||
| 9 | ||||
| **现在来写判断每一个子项的边框样式的方法,需要提前写好class样式,```&.border-right { |
border-right: 1upx solid #F8F8F8;
}
&.border-bottom {
border-bottom: 1upx solid #F8F8F8;
}```**
// 计算是否需要右边框和左边框
getBorderClass(index) { // index代表子项的索引值
index += 1
let className = ''
let lineNumber = this.lineNumber // 每行行显示个数
let total = this.list.length // 总个数
let line = Math.ceil(total / lineNumber) // 一共有几行
let currentLine = 0 // 当前在第几行,这里从1开始
let currentIndex = 0 // 当前在某行的第几个
currentLine = Math.ceil(index / lineNumber)
currentIndex = (index % lineNumber) === 0 ? lineNumber : index % lineNumber
// 右下边框
if (currentLine < line && currentIndex < lineNumber) {
className = 'border-bottom border-right'
}
// 右边框
if (currentLine === line && currentIndex < lineNumber) {
className = 'border-right'
}
// 下边框
if (currentLine < line && currentIndex === lineNumber) {
className = 'border-bottom'
}
// 无边框
if (currentLine === line && currentIndex === lineNumber) {
className = ''
}
return className
}
填充空白子项问题
我们拿到后台菜单数据是不一个定长的数组,假设现在就拿到了一个length = 7的菜单数组,我现在需要渲染出来九个格子,那我们就需要计算当前拿到的数组和填满9九宫格需要的另外两个空白格(不是杨宗纬的空白格)。
这里我们要考虑到每行显示的个数问题,有可能每行只显示3个也有可能4个...,那我们就把每行显示多少个写成一个变量参数lineNumber = 3,初始化需要填充的空白格为fillNumber = 0, 菜单数组为list,list长度未知,为后台返回,从这里我们就可以写出计算空白格的fn
// 计算空白格
getFillNumber(length, lineNumber = 3) {
let remainder = length % lineNumber
return remainder === 0 ? 0 : lineNumber - remainder // 这里需要注意如果是整除之后就不需要空白格了
},
完整代码
<template>
<view class="app-card-content">
<view
:style="'width: ' + (100/lineNumber - 0.5) + '%'"
:class="['app-card-content_item', getBorderClass(index)]"
v-for="(item, index) in list"
@click="itemClick(item)"
:key='index'>
<view v-if="item.imgSrc">
<image :src="item.imgSrc" mode=""></image>
</view>
<text v-if="item.title">
{{item.title}}
</text>
</view>
</view>
</template>
<script>
import List from '@/config/application.js'
export default {
data() {
return {
list: [],
fillNumber: 0, // 填充空白格的数量
lineNumber: 4
}
},
mounted() {
// 接口调用后再调用以下方法
this.fillNumber = this.getFillNumber(this.list.length)
this.list = this.fillArray(this.list, this.lineNumber)
},
methods: {
// 计算多余的填充框
getFillNumber(length, lineNumber = 3) {
let remainder = length % lineNumber
return remainder === 0 ? 0 : lineNumber - remainder
},
// 不满整除的数组需要进行填充满
fillArray(arr, lineNumber = 3) {
// 余数
let remainder = this.getFillNumber(arr.length, this.lineNumber)
if (remainder === 0) return arr
for (let i = 0; i < remainder; i++) {
// 填充空对象
arr.push({})
}
return arr
},
// 计算是否需要右边框和左边框
getBorderClass(index) {
index += 1
let className = ''
let lineNumber = this.lineNumber // 每行行显示个数
let total = this.list.length // 总个数
let line = Math.ceil(total / lineNumber) // 一共有几行
let currentLine = 0 // 当前在第几行,这里从1开始
let currentIndex = 0 // 当前在某行的第几个
currentLine = Math.ceil(index / lineNumber)
currentIndex = (index % lineNumber) === 0 ? lineNumber : index % lineNumber
// 右下边框
if (currentLine < line && currentIndex < lineNumber) {
className = 'border-bottom border-right'
}
// 右边框
if (currentLine === line && currentIndex < lineNumber) {
className = 'border-right'
}
// 下边框
if (currentLine < line && currentIndex === lineNumber) {
className = 'border-bottom'
}
// 无边框
if (currentLine === line && currentIndex === lineNumber) {
className = ''
}
return className
}
}
}
</script>
<style lang="scss">
.app-card-content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
background-color: #fff;
.app-card-content_item {
/* width: 25%; */
height: 200upx;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
&.border-right {
border-right: 1upx solid #F8F8F8;
}
&.border-bottom {
border-bottom: 1upx solid #F8F8F8;
}
view {
margin-bottom: 10upx;
image {
width: 50upx;
height: 50upx;
}
}
}
}
</style>
因为需求改动,最后改成了每行显示四个,最终效果图如下