最近学习了微信小程序开发,创建了一个过程条组件作为入门小练习。
实现的效果如下:
实现思路
首先捋一下怎样实现。
如图,将实现内容按图中蓝线从上到下分为三大部分,过程说明部分(A),代表步骤及过程的线(B),以及下面的步骤说明部分(C)。每个大的部分由多个相同的小组件组成。将A往下移到合适的位置就能实现一个简单的过程条了。
- 组成A部分的每个小组件(a)包含说明文本和小圆点。
- 组成B部分的每个小组件(b)包含左线条,右线条,中间的圆点以及圆点中的数字。
- 组成C部分的每个小组件只是简单的文本。
- 我设置a、b、c小组件的宽度都是固定的170rpx,这样便于A、B、C的对齐(目前没有考虑小组件宽度不固定的情况)。
- 通过传入的属性来控制每个小组件的样式。
- 通过一个整体的状态值来控制整个组件的展示情况。例如状态值为1的时候就是走完第一步没有走完第二步,组件应该展示为本文第一幅图的那样。
rpx是微信小程序支持的尺寸单位,使用这个单位就不用我们自己做屏幕自适应了:
rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
开发过程
创建项目
打开开发者工具,使用测试号创建一个项目:
创建组件
在根目录下,创建一个components文件夹,components文件夹中有steps组件,steps组件包含代表a组件(是上文的A的子组件)和b组件(是上文B的子组件)两个文件夹。创建好之后文件结构如下:
processDescription文件夹和step文件夹中也包含了相应的四个文件。在js文件中用Component定义好组件,在json文件中设置component属性值为true。在wxml文件中添加组件内容。在index页面引入组件。
项目代码
添加完内容后各个文件的代码如下:
首页代码
index.json
{
"usingComponents": {
"steps": "/components/steps/steps"
}
}
在json文件中通过usingComponents属性引入过程条组件。
index.wxml
<view class="container">
<steps state="{{stepsState}}"/>
</view>
在wxml中引入组件,设置组件的state属性,用于从父组件传值到子组件。在steps组件中定义组件的properties属性中的state属性,就能获取到从父组件传来的值。可以看下文steps.js中的代码。
index.js
Page({
data: {
// 状态,0表示没开始,1表示走完第一步,没走完第二步,2表示走完第二步,没走完第三步,依此类推
stepsState: 3
}
})
在js文件中通过Page注册页面,并定义过程条的状态值。这个状态值会控制整个进度条展示成什么样子。
过程条组件代码
steps.json
{
"component": true,
"usingComponents": {
"process-description": "./components/processDescription/processDescription",
"step-component": "./components/step/step"
}
}
通过设置component属性为true表明当前模块为一个组件,通过usingComponents定义当前模块引用的子组件。
steps.wxml
<view>
<view class="description">
<block wx:for="{{description}}" wx:key="index" wx:for-item="item">
<process-description hide="{{item.hide}}" text="{{item.text}}"/>
</block>
</view>
<view class="steps-line">
<block wx:for="{{steps}}" wx:key="index" wx:for-item="item" >
<step-component type="{{item.type}}" state="{{item.state}}" stepText="{{item.text}}"/>
</block>
</view>
<view class="steps-names">
<view class="step-name" wx:for="{{stepsNames}}" wx:for-item="item" wx:key="index">{{item}}</view>
</view>
</view>
整个过程条组件按照之前的思路分为了三大部分,每个部分的展示通过相应的数组数据渲染。这个过程中用到了wx:for,wx:key和wx:for-item。wx:for里面是一个数组,代表用于渲染列表的数组数据。wx:key是用于提高渲染效率的,
当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。
wx:for-item按照目前的写法来说是不需要的,因为微信小程序默认用item代表数组的元素,如果要使用item以外的值,就必须要用wx:for-item,例如:
<block wx:for="{{description}}" wx:key="index" wx:for-item="item1">
<process-description hide="{{item1.hide}}" text="{{item1.text}}"/>
</block>
block不是一个像view那样的组件,他只是用来包装内容的,不会在页面中渲染。
这部分的知识点: 微信小程序列表渲染。
steps.js
Component({
properties: {
state: Number
},
data: {
description: [],
steps: [],
stepsNames: ['测试测试测试1', '2', '3']
},
lifetimes: {
attached: function () {
this.diffStateData()
}
},
methods: {
getStepType: function (index, stepsData) {
let lastIndex = stepsData.length - 1
switch (true) {
case index === 0:
return 0
case index === lastIndex:
return 2
default:
return 1
}
},
initStepsData: function (stepsData, stateArr) {
let arr = stepsData.map((item, index) => {
let type = this.getStepType(index, stepsData)
return {
type,
state: stateArr[index],
text: item
}
})
this.setData({
steps: arr
})
},
initDescriptionData: function (description, hideArr) {
let descArr = description.map((item, index) => {
return {
hide: hideArr[index],
text: item
}
})
this.setData({
description: descArr
})
},
// 根据不同的状态使用不同的数据,目前假设有三个步骤
diffStateData: function () {
let stepsData = [1, 2, 3]
let description = ['过程1内容比较长的时候呢过程1内容比较长的时候呢', '过程2']
let state = this.properties.state
let stateArr,
hideArr
switch (state) {
case 0:
stateArr = [0, 0, 0]
hideArr = [true, true]
break
case 1:
stateArr = [2, 0, 0]
hideArr = [false, true]
break
case 2:
stateArr = [2, 2, 0]
hideArr = [false, false]
break
case 3:
stateArr = [2, 2, 2]
hideArr = [false, false]
break
}
this.initStepsData(stepsData, stateArr)
this.initDescriptionData(description, hideArr)
}
}
})
- 首先使用Component定义组件。
- properties用于获取组件的属性值。
- data用于定义组件的数据。
- lifetimes是组件的生命周期。当组件已经初始化完毕后,就会触发attached生命周期函数,会执行里面的代码,也就是会调用diffStateData函数。
- methods里面定义组件需要使用到的函数。
- 函数说明:
- diffStateData函数,用于初始化过程条的数据。首先可以自定义步骤的数据和描述的数据,再根据从index页面传来的属性值
this.properties.state,设置好子组件的状态数组。 - initStepsData函数,根据数据数组和状态数组,生成步骤线条实际用到的数据。
- initDescriptionData函数,根据数据数组和状态数组,生成过程说明部分实际要用到的数据。
- getStepType函数,用于判断组成步骤线条的小组件是第一个(只有右线条)、最后一个(只有左线条)、或者是中间部分(有左右线条)。
- diffStateData函数,用于初始化过程条的数据。首先可以自定义步骤的数据和描述的数据,再根据从index页面传来的属性值
steps.wxss
.steps-line {
display: flex;
position: relative;
flex-direction: row;
justify-content: center;
z-index: 1;
}
.description {
display: flex;
position: relative;
flex-direction: row;
align-items: stretch;
justify-content: center;
margin-bottom: -34rpx;
z-index: 10;
min-height: 120rpx;
}
.steps-names {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 20rpx;
}
.step-name {
width: 170rpx;
overflow: hidden;
font-size: 26rpx;
text-align: center;
}
下面部分为组成过程条的两个小组件的代码:
(1)过程描述组件
processDescription.json
{
"component": true
}
processDescription.wxml
<view class="{{hide ? 'hide-description description': 'description'}}">
<view class="content">
<text class="description-text">{{text}}</text>
</view>
<view class="triangle"></view>
<view class="dot"></view>
</view>
wxml使用{{}}来进行数据绑定,里面的内容可以是一个表达式,所以可以使用?:来进行判断。
processDescription.js
Component({
properties: {
/**
* hide表示是否隐藏描述标签
*/
hide: {
type: Boolean,
value: true
},
/**
* text表示描述文本
*/
text: {
type: String,
value: '测试'
}
}
})
过程描述小组件接受两个属性值,hide和text,type表示数据类型,value表示默认值。
processDescription.wxss
.description {
display: flex;
justify-content: center;
flex-direction: column;
align-self: stretch;
height: 100%;
width: 170rpx;
box-sizing: border-box;
padding: 0 10rpx;
}
.content {
background-color: green;
color: #fff;
font-size: 28rpx;
width: 100%;
border-radius: 4rpx;
flex: 1;
padding: 6rpx;
}
.triangle {
align-self: center;
margin-top: -10rpx;
width: 0;
height: 0;
border: 10rpx solid green;
border-color: transparent green green transparent;
transform: rotate(45deg);
}
.dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background-color: green;
margin-top: 30rpx;
align-self: center;
}
.description-text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.hide-description {
visibility: hidden;
}
(2)步骤组件 step.json
{
"component": true
}
step.wxml
<view class="step {{stateClass}}">
<view class="left-line" wx:if="{{type!==0}}"></view>
<view class="center">
<text class="center-text">{{stepText}}</text>
</view>
<view class="right-line" wx:if="{{type!==2}}"></view>
</view>
可以使用wx:if和hidden来控制内容的显示隐藏。一般来说当需要频繁切换显示隐藏的时候用hidden,只判断一次就确定展示内容时用v-if。这里不需要频繁地切换,因为传入小组件的数据是在一开始就确定,所以这里我使用了v-if。
因为 wx:if 之中的模板也可能包含数据绑定,所以当 wx:if 的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。 同时 wx:if 也是惰性的,如果在初始渲染条件为 false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。 相比之下,hidden 就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏。
一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好。
举个例子说明hidden的使用方式:
<view class="left-line" hidden="{{type===0}}"></view>
step.js
Component({
properties: {
/**
* type有三个值
* 0 表示是第一个步骤(只有右边的线)
* 1 表示是中间步骤(有左右两边的线)
* 2 表示是最后一个步骤(只有左边的步骤)
*/
type: {
type: Number,
value: 1
},
/**
* state有三个值
* 0 表示左右的线都为灰色
* 1 表示左边的线为完成态的颜色,右边的线为灰色
* 2 表示左边的线和右边的线都为完成态的颜色
*/
state: {
type: Number,
value: 0
},
stepText: {
type: String,
value: '1'
}
},
data: {
stateClass: ''
},
lifetimes: {
attached: function () {
let state = this.properties.state
let stateClass
switch (true) {
case state === 1:
stateClass = 'state1'
break
case state === 2:
stateClass = 'state2'
break
default:
stateClass = ''
}
this.setData({
stateClass
})
}
}
})
通过属性获取到type和state如代码所示,根据属性值的不同设置组件的展示。
step.wxss
.step {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.state1 .left-line {
background-color: green;
}
.state2 .left-line,
.state2 .right-line {
background-color: green;
}
.state2 .center {
background-color: green;
}
.left-line {
width: 50rpx;
height: 5rpx;
background-color: #999;
}
.right-line {
width: 50rpx;
height: 5rpx;
background-color: #999;
}
.center {
display: flex;
width: 50rpx;
height: 50rpx;
border-radius: 50%;
color: #fff;
background-color: #999;
margin: 0 10rpx;
}
.center-text {
margin: auto;
}
其他
以上就是整个组件的实现方法了,还有很多可以进一步完善的地方,比如样式的完善,比如当过程条很长,一行无法展示全时的处理。