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:

  • @touchstartFinger button trigger
  • @touchmoveFinger slide trigger
  • @touchendFinger 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

  1. Add background color hints for operations
  2. Prevents sliding during openinglistrolling
  3. 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">&#xe61c;</text>
			</div>
			<! - delete - >
			<div class="operate-del">
				<text class="chat-operate-icon operate-del-icon">&#xe6c7;</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

If you have to useuni-appIn thenvueThere is a better way to model multiple platforms, welcome to discuss.