Recently, a new project needs to make a mobile application. In order to reduce the development cost, UNI-App, a cross-platform development method, first came into my mind
Uni-app has two rendering methods:
-
One is to write.vue and then render the page as a Web-view. This is browser-based so it’s easy to keep ios and Android pages consistent, but it has a fatal drawback: performance
-
The second option is to write.nvue files and render them as native components using Week technology. This mode does not perform well, but due to the limitations of week itself, it is difficult to keep ios and Android pages consistent in some ways
Since performance is my priority, I have to use NVUE to do this. When developing a page, I usually use list to wrap page elements to achieve high performance scrolling. This is where the problems discussed in this article occur: The slideshow menus I found in the DCloud plugin market all have the same problem on The Android platform, because the memory reclamation mechanism only works when the page is open, and the slideshow menus in the following invisible places are invalid. Let’s explore the reasons and solutions below.
list
Dedicated NVUE components on the APP side. Under app-NVUE, using the List component provides better performance than using view or Scroll-View for long lists. The reason is that the list has a special optimization for the invisible part of the rendering resource collection.
Android platform, due to
efficient memory reclamation mechanism, components that are not in the visible area of the screen will not be created, resulting in some internal components that need to calculate the width and height cannot work properly
List component documentation on the official website
The problem is that the width of the invisible part of the Android platform fails, causing sideslipping to fail. Let me tell you about my solution and the problems I encountered.
Take a look at my page first
Screenshot from iPhone XS Max
Knowing the cause of the failure, our solution was simple, just avoid calculating the height and width, and I made each chat list entry and the side menu a separate component.
.<cell v-for="(item,index) in chats">
<chat-item :portrait="item.portrait"
:userName="item.userName"
:messages="item.messages"
:key="'chat-item-'+index"
:code="'chat-item-'+index"
:unread="item.unread"
:lastTime="item.lastTime"></chat-item>
</cell>.Copy the code
Let’s take a look at what the custom component
looks like.
<! -- Chat list entry container -->
<div class="chat-container">
<! -- Scroll menu -->
<div class="chat-operate">.</div>
<! -- Chat list entry body -->
<div class="chat-item">.</div>
</div>
Copy the code
Because the z-index attribute is not provided in week, this attribute cannot be set correctly. In the future, the chat list entry body will need to be overwritten on the side slider menu, so you need to write the chat list entry body at the end.
Week Common style
For the sideslip function we need to listen for finger events of the chat list item body (class=”chat-item”). Here we need to listen for three events:
@touchstart
Finger button trigger@touchmove
Finger slide trigger@touchend
Finger off screen trigger
<template>
<! -- Chat list entry -->
<div class="chat-container">
<! -- Scroll menu -->
<div class="chat-operate">.</div>
<! -- Body part -->
<div class="chat-item"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
:style="chatItemStyle">.</div>
</div>
</template>
<script>
export default {
computed: {
// The main part of the displacement size
chatItemStyle(){
return {
transform: `translateX(-The ${this.moveX}) `}}},data(){
return {
// Body part of the finger drop position
startX: 0.// The main part of the displacement distance
moveX: 0}},methods: {
touchStart(e){
// Determine whether the event exists
if (e.changedTouches.length == 1) {
// Set the horizontal position of the touch start point
this.startX = e.changedTouches[0].pageX
this.startX += this.moveX; }},touchMove(e){
if (e.changedTouches.length == 1) {
// The finger moves horizontally
var moveX = e.changedTouches[0].pageX;
// The difference between the starting position of the finger and the movement period. Here multiply the value by 2 to open the side slider menu more conveniently
var disX = (this.startX - moveX) * 2;
// Assign the displacement distance
this.moveX = disX; }},touchEnd(e){
if (e.changedTouches.length == 1) {
// Horizontal position after finger movement
var endX = e.changedTouches[0].pageX;
// Start and end of touch, how far the finger moves
var disX = this.startX - endX;
// Set 55 as the boundary value, if the current finger release displacement exceeds 55 will automatically open the remaining distance, otherwise close the side slide menu
// The width of the slider menu must be set to a fixed value of 110 to avoid calculation
if(disX > 55) {/ / open
this.moveX = 110
}else{
/ / close
this.moveX = 0
}
}
}
}
}
</script>
<style lang="scss" scoped>// Chat list entry container.chat-container{
background-color: #FEFEFE;
padding: 15rpx 20rpx;
padding-bottom: 10rpx;
position: relative; } // Chat list box.chat-item{
flex-direction: row;
align-items: stretch;
border-radius: 20rpx;
padding: 15rpx;
background-color: #FEFEFE;
transition-property: transform;
transition-duration:.2s;
transition-timing-function: ease; } / / menu.chat-operate{
position: absolute;
width: 100px;
height: 100rpx;
top: 30rpx;
right: 20rpx;
flex-direction: row;
justify-content: space-between;
}
</style>
Copy the code
The parent container is position: relative, and the list box must not be position: absolute or the height of the item cannot be extended. The slider menu can be Position: Absolute but there is no width or height calculation.
Add a billion details
- Add background color hints for operations
- Prevents sliding during opening
list
rolling - Open a slide menu to close the menu of other items
Low end or older Android models have a side slip Bug
In some low-end Android models or older models, there may be a sideshow Bug(sideshow is stuck in half, and the external list does not receive the normal switch and thinks it is still sliding, so it cannot scroll the page), because these models may not trigger the @Touchend finger leave event correctly. The calculation of the sideslip menu fails. The solution here can be replaced with the @TouchCancel finger interrupt event, which can check the operating system version at uni.getSystemInfo when the component is initialized.
All the code
chart-item.nvue
<template>
<! -- Chat list -->
<div class="chat-container">
<div class="chat-operate">
<! - top - >
<div class="operate-top">
<text class="chat-operate-icon operate-top-icon"></text>
</div>
<! - delete - >
<div class="operate-del">
<text class="chat-operate-icon operate-del-icon"></text>
</div>
</div><! -- @touchcancel="touchEnd2" -->
<div :class="{'isMove':isMove}" class="chat-item" hover-class="none" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @touchcancel="touchEnd2" :style="chatItemStyle">
<! - image - >
<div class="chat-portrait">
<image class="chat-portrait-img" :src="portrait" mode="widthFix"></image>
</div>
<! Text -- - >
<div class="chat-message-group">
<text class="chat-userName">{{userName}}</text>
<text class="chat-messages">{{messages}}</text>
</div>
<! Time and the little red dot -->
<div class="chat-info">
<! -- Little red dot -->
<div class="info-tag" v-if="unread > 0">
<text class="info-tag-text">{{unread | numberForMat}}</text>
</div>
<div v-if="unread <= 0"></div>
<! Time -- -- -- >
<text class="info-time">{{lastTime | timeConversion}}</text>
</div>
</div>
</div>
</template>
<script>
import { EventBus } from ".. /.. /unit/bus.js";
export default {
filters: {
timeConversion(date){
if(!new Date(date)) return 'Display exception'
var date = new Date(date);
return (date.getHours() > 10 ? date.getHours() : '0' + date.getHours()) + ':' + (date.getMinutes() > 10 ? date.getMinutes() : "0" + date.getMinutes())
},
numberForMat(data){
if(data > 999)
return '+ 999'
else
return data
}
},
computed: {
chatItemStyle(){
return {
transform: `translateX(-The ${this.moveX}) `}}},props: {
portrait: String.userName: String.messages: String.code: String.unread: Number.lastTime: [String.Number],},mounted() {
this.init();
EventBus.$on('chatItemOpen'.(data) = >{
if(data ! =this.code)
this.moveX = 0
});
EventBus.$on('chatListScroll'.() = >{
this.moveX = 0
this.startX = 0
});
},
watch: {
moveX: function(val){
if(val > 0)
this.isMove = true
else if(val == 0)
this.isMove = false}},methods: {
init(){
uni.getSystemInfo({
success:(res) = > {
this.platform = res.platform
}
})
},
touchStart(e){
if (e.changedTouches.length == 1) {
// Set the horizontal position of the touch start point
this.startX = e.changedTouches[0].pageX
this.startX += this.moveX; }},touchMove(e){
if (e.changedTouches.length == 1) {
// The finger moves horizontally
var moveX = e.changedTouches[0].pageX;
// The difference between the starting position of the finger and the movement period
var disX = (this.startX - moveX) * 2;
if(disX > 20) {// if(this.platform == 'ios'){
EventBus.$emit('chatItemMove'.true);
if(disX > 80){
EventBus.$emit('chatItemMove'.false);
}
if (disX == 0 || disX < 0) {
disX = 0
}else if(disX > 0){
EventBus.$emit('chatItemOpen'.this.code);
if(disX >= 110){
disX = 110}}// }else{
// if (disX == 0 || disX < 0) {
// disX = 0
// }else if(disX > 20){
// EventBus.$emit('chatItemOpen',this.code);
// if(disX >= 110){
// disX = 110
/ /}
/ /}
// }
this.moveX = disX; }}},touchEnd(e){
// if(this.platform ! == 'ios') return;
if (e.changedTouches.length == 1) {
EventBus.$emit('chatItemMove'.false);
// Horizontal position after finger movement
var endX = e.changedTouches[0].pageX;
// Start and end of touch, how far the finger moves
var disX = this.startX - endX
if(disX > 55) {/ / open
this.moveX = 110
}else{
/ / close
this.moveX = 0}}},touchEnd2(){
EventBus.$emit('chatItemMove'.false);
}
// touchEnd2(e){
// if(this.platform == 'ios') return;
// if (e.changedTouches.length == 1) {
// EventBus.$emit('chatItemMove',false);
// // horizontal position after the end of finger movement
// var endX = e.changedTouches[0].pageX;
// // Start and end of touch, how far the finger moves
// var disX = this.startX - endX;
// if(disX > 55){
/ / / / open
// this.moveX = 110
// }else{
/ / / / closed
// this.moveX = 0
/ /}
/ /}
// }
},
data(){
return {
startX: 0.moveX: 0.isMove: false.platform: ' '}}}</script>
<style lang="scss" scoped>
.chat-container{
background-color: #FEFEFE;
padding: 15rpx 20rpx;
padding-bottom: 10rpx;
position: relative; } // Chat list box.chat-item{
flex-direction: row;
align-items: stretch;
border-radius: 20rpx;
padding: 15rpx;
background-color: #FEFEFE;
transition-property: transform;
transition-duration:.2s;
transition-timing-function: ease; } // Moving.chat-item.isMove{
background-color: #EEEEEE; } / / head.chat-portrait{
width: 100rpx;
height: 100rpx;
border-radius: 20rpx;
overflow: hidden;
align-items: center;
justify-content: center;
}
.chat-portrait-img{
width: 100rpx;
height: 100rpx; } / / chat.chat-message-group{
margin: 0 20rpx;
justify-content: center;
flex: 1;
}
.chat-userName{
font-family: 'HarmonyOS_Sans_SC';
color: $primaryText;
font-size: 35rpx;
font-weight: 600;
}
.chat-messages{
font-family: 'HarmonyOS_Sans_SC';
color: $regularText;
font-size: 25rpx;
font-weight: 400;
flex: 1;
text-overflow: ellipsis;
overflow: hidden;
lines: 1; } / / operation.chat-operate{
position: absolute;
width: 100px;
height: 100rpx;
top: 30rpx;
right: 20rpx;
flex-direction: row;
justify-content: space-between;
}
.operate-del{
width: 45px;
height: 100rpx;
border-radius: 20rpx;
align-items: center;
justify-content: center;
background-color: rgba($dangerColor,.3);
}
.chat-operate-icon{
font-family: iconfont;
font-size: 35rpx;
}
.operate-del-icon{
color: $dangerColor;
}
.operate-top{
width: 45px;
height: 100rpx;
border-radius: 20rpx;
align-items: center;
justify-content: center;
background-color: rgba($warningColor,.3);
}
.operate-top-icon{
color: $warningColor;
font-size: 40rpx; } // Time and little red dot.chat-info{
justify-content: space-between;
align-items: flex-end;
}
.info-tag{
margin-top: 10rpx;
background-color: #FE3B30;
padding: 6rpx 12rpx;
align-items: center;
justify-content: center;
border-radius: 20rpx;
}
.info-tag-text{
color: #fff;
line-height: 25rpx;
font-size: 25rpx;
font-weight: 500;
font-family: 'HarmonyOS_Sans_SC';
}
.info-time{
color: $regularText;
font-size: 20rpx;
font-family: 'HarmonyOS_Sans_SC';
}
</style>
Copy the code
message.vue
The list has a property that controls whether scrolling is enabled: scrollable, which controls whether scrolling can be enabled by listening for the chatItemMove event sent by the child component chart-Item.
.mounted() {
// if(this.platform == 'ios'){
// Listen to whether the child component is sliding
EventBus.$on('chatItemMove'.(data) = >{
// The slide has finished and the list can be scrolled
if(! data)this.scrollable = true
// The list cannot scroll while sliding
else
this.scrollable = false
});
// }}...Copy the code
Listen for the @scroll event of the list to close all scroll menus while the page is scrolling.
.methods: {
listScroll(e){
// if(this.platform == 'ios'){
// Send the chatListScroll event to the child component when the page scrolls to close all scroll menus
EventBus.$emit('chatListScroll');
// }}}...Copy the code