当前位置:网站首页>如何在快应用中实现滑动操作组件
如何在快应用中实现滑动操作组件
2022-07-07 05:38:00 【华为开发者论坛】
1. 什么是滑动操作组件
在实际的移动应用程序交互方式中,最常见的就是滑动操作。像左右滑动切换页面,手指张开来放大图片等,都是由滑动操作来完成的。本文滑动操作组件是指通过手势滑动呼出隐藏的操作按钮。
如下图所示:
图1 QQ app | 图2 华为短信app |
以上滑动操作组件具有如下特点:
- 左右滑动均支持,滑动可呼出、隐藏操作按钮栏;
- 操作按钮可配置多个;
- 操作按钮可设置背景色、字体、宽度等;
- 点击操作按钮,可隐藏操作栏;
- 呼出操作栏时,界面所有的UI元素都相应移动;
2 基本实现
2.1 滑动操作组件化设计
定义支持滑动操作的组件swipe_item,父组件引用,引用时传递操作按钮、操作按钮样式。滑动操作子组件需要实现手势滑动处理。
代码结构如下:
<import name="swipeitem" src="../Component/swipe_item.ux"></import><template><div class="container"><swipeitem right-options="{{rightBtns}}" left-options="{{leftBtns}}" @swipebtnclick="swipeItemClick"><text>左右都有混合滚动</text></swipeitem></div></template>
2.2子组件swipe_item属性设计
2.3支持的属性
属性 | 类型 | 是否必选 | 默认值 | 描述 |
identity | Number | 否 | 组件唯一标识,当autoClose为true时,必须要设置 | |
disabled | Boolean | 否 | false | 是否禁止滑动 |
autoClose | Boolean | 是 | true | 滑动打开当前组件,是否关闭其他组件 |
threshold | Number | 否 | 30 | 滑动缺省距离 |
leftOptions | Array[Option] | 否 | 左侧操作栏内容及样式 | |
rightOptions | Array[Option] | 否 | 右侧操作栏内容及样式 |
Option对象参数如下:
属性 | 类型 | 是否必选 | 描述 |
txt | String | 否 | 按钮文字 |
style | Object | 是 | 按钮样式 |
style对象参数如下:
属性 | 类型 | 是否必选 | 描述 |
backgroundColor | Color | 否 | 按钮背景色 |
txtColor | Color | 否 | 按钮颜色 |
fontsize | Number | 否 | 文字大小 |
btnwidth | Number | 是 | 按钮宽度 |
支持的事件
事件名称 | 事件传递参数 | 描述 |
swipebtnclick | {clickIndex:1, position:left|right, btnValue:btnText} | 点击操作栏按钮时触发事件; clickIndex:左或右操作栏上点击的按钮数组索引; position:所点击的操作栏位置,参数值有left和right; btnValue:所点击的按钮文字; |
centerclick | {} | 点击显示在屏幕中间内容时触发事件 |
swipestatechange | {isOpened:true|false} | 操作栏打开或关闭时触发 |
2.4 swipe_item子组件布局
- 布局结构:由左侧操作栏+中间屏幕显示内容+右侧操作栏组成的横向div;
- 左侧操作栏、右侧操作栏都是基于leftOptions、rightOptions操作选项内容数组,采用用for循环形成横向布局渲染;
- 中间在屏幕内显示内容是动态变化的,每个产品要显示的内容和UI是不同的,所以采用了slot插槽,由使用swipe_item组件的页面来完成中间内容的布局。
- 中间布局宽度需设置整个屏幕的宽度,即750px;
2.5 swipe_item子组件css
- 宽度:虽然滑动操作组件在初始状态下左右操作栏都是隐藏的,但是实际属于整个布局的一部分,如果不能正确计算根节点的宽度,会导致滑动的时候,操作栏上无内容。
- 动画:初始状态下,需要满足左右操作栏都是隐藏的,只有中间内容是可见的。正常布局下(见图3),左操作栏是显示的,所以使用了transform动画样式,将根节点平移左操作栏宽度的距离,这样就确保初始状态下操作栏都是隐藏的。
- 按鈕背景色、文字大小、文字顔色: 如果父组件没有设置,子组件设置默认的值,见图4;
图3 正常布局效果 | 图4 操作栏按钮默认背景色效果 |
2.6 手势滑动
监听手势touchstart、touchmove、touchend事件,计算手指滑动像素,同时调用X轴的平移动画animate方法,实现组件的移动效果。
- touchstart:记录起始触摸点;
- touchmove:滑动距离ds>0 表示手指向右滑动,反之向左滑动,滑动过程中调用animate()移动组件,达到打开或者隐藏操作栏的效果;如果滑动距离小于阈值threshold,不触发移动效果;如果禁止滑动即disabled值为true时,也不能触发滑动效果;
- touchend:滑动结束时触发,调用animate()控制操作栏最终显示位置,需要计算准确,防止组件移动过远或者过近,从而UI效果异常。
2.8 父子组件通信
当点击子组件操作栏上的按钮时,需要父组件处理业务逻辑,通过$emit()向父组件发送通知。注意,不要在子组件中处理业务逻辑,否则代码无法维护。
1) 操作栏按钮点击事件通知
- 页面调用子组件时,监听swipebtnclick事件;
- 子组件调用$emit()向页面发送通知;
2)中间内容部分点击事件通知
- 页面调用子组件时,监听centerclick事件;
- 当操作栏处于打开状态时,点击中间部分效果是隐藏操作栏。只有操作栏处于隐藏状态时,点击中间部分时,子组件调用$emit()向页面发送通知,页面自行处理业务逻辑,比如打开新的页面。
3)操作栏打开、关闭事件通知
- 页面调用子组件时,监听swipestatechange事件;
- 当操作栏处于打开或者关闭状态时,子组件调用$emit()向页面发送通知。
相关代码如下:
2.9 autoClose实现
滑动打开当前组件时,是否关闭其他组件,当autoClose设置为true时,identity属性必须设置,且唯一标识当前子组件。
- 当子组件1操作栏打开时,通过$emit()向页面发送状态事件swipestatechange;
- 父组件监听swipestatechange事件,通过$broadcast()方法向子组件发送关闭通知事件closeSwipeItem,通知其他子组件2,3,4等要关闭已经打开的操作栏;
- 子组件1,2,3,4等监听关闭closeSwipeItem事件,在事件句柄中处理关闭逻辑,注意操作栏打开的子组件1也会收到关闭事件,此处我们需要判断identity值,确保最新打开的子组件1不会出现打开又关闭的现象。
3总结
- 学习和熟悉快应用子组件的设计和属性定义;
- 学习和熟悉快应用手势事件;
- 学习和熟悉快应用动画;
- 注意滑动操作子组件swipe_item在list-item中不生效。
4代码附录
滑动操作子组件swipe_item.ux
<template> <!-- Only one root node is allowed in template. --> <div class="container" style="{{containerStyle}}" > <div id="swipe_item" class="swipe" ontouchstart="dealTouchStart" ontouchmove="dealTouchMove" ontouchend="dealTouchEnd" > <div class="left" id="leftbtns"> <block for="(index,item) in leftOptions"> <input type="button" class="btn" style="background-color: {{calBtnBgColor(item.style.backgroundColor)}}; color:{{calBtnTxtColor(item.style.txtColor)}}; font-size:{{calBtnFontSize(item.style.fontsize)}}px;width:{{item.style.btnwidth}}px;" value="{{item.txt}}" onclick="btnsClick(index,'left',item.txt)"> </input> </block> </div> <div class="center" style="width:{{deviceLogicWidth}}px" onclick="dealClickCenter"> <slot ></slot> </div> <div class="right" id="rightbtns"> <block for="(index,item) in rightOptions"> <input type="button" class="btn" style="background-color: {{calBtnBgColor(item.style.backgroundColor)}}; color:{{calBtnTxtColor(item.style.txtColor)}}; font-size:{{calBtnFontSize(item.style.fontsize)}}px;width:{{item.style.btnwidth}}px;" value="{{item.txt}}" onclick="btnsClick(index,'right',item.txt)"> </input> </block> </div> </div> </div></template><style> .container { flex-direction: column; justify-content: center; } .swipe { flex-direction: row; align-items: center; } .txt { font-size: 40px; width: 100%; height: 100%; } .btn { height: 100%; } .left { flex-direction: row; flex-shrink: 0; height: 100%; background-color: #a9a9a9; } .center { flex-direction: row; align-items: center; padding: 16px; flex-shrink: 0; } .right { flex-direction: row; flex-shrink: 0; height: 100%; background-color: #a9a9a9; }</style><script> import device from '@system.device'; const defaultBtnBg="#f01f1f"; const defaultBtnFontSize=32; const defaultBtnTxtColor="#dcdcdc"; module.exports = { props: { // 禁用 disabled: { type: Boolean, default: false }, identity:{ type: String, default: '0', }, // 是否自动关闭 autoClose: { type: Boolean, default: true }, // 滑动缺省距离 threshold: { type: Number, default: 30 }, // 左侧按钮内容 leftOptions: { type: Array, default() { return [] } }, // 右侧按钮内容 rightOptions: { type: Array, default() { return [] } } }, data: { movestartX: 0, lastMoveX:0, translateEndX: 0, leftbtnsWidth: 0, rightbtnsWidth: 0, deviceLogicWidth:750, isLeftOpened: false, isRightOpened: false }, computed: { containerStyle() { console.info("computed containerStyle begin") var style = ''; let totalwidth=0; let leftwidth=0; this.leftOptions.forEach((item, index, array) => { console.info("computed item="+JSON.stringify(item)); leftwidth+=item.style.btnwidth; }) this.rightOptions.forEach((item, index, array) => { totalwidth+=item.style.btnwidth; }) totalwidth=totalwidth+this.deviceLogicWidth+leftwidth; console.info("computed totawidth="+totalwidth+", left width="+leftwidth); style += 'width:' + totalwidth + 'px;' style += 'transform: translateX(-' + leftwidth + 'px);' return style; } }, calBtnBgColor(color){ if(color!==undefined){ return color; } return defaultBtnBg; }, calBtnFontSize(size){ if(size!==undefined){ return size; } return defaultBtnFontSize; }, calBtnTxtColor(color){ if(color!==undefined){ return color; } return defaultBtnTxtColor; }, onInit() { console.info("oninit()"); this.$on('closeSwipeItem', this.closeFromParent); setTimeout(() => { this.calWidth(); }, 500); }, calWidth() { this.calLeftBtnsWidth(); this.calRightBtnsWidth(); }, calLeftBtnsWidth() { var that = this; this.$element('leftbtns').getBoundingClientRect({ success(res) { let msg = JSON.stringify(res); console.log('calLeftBtnsWidth 当前坐标:' + msg); // that.leftbtnsWidth = res.width; that.convertRealPx(true, res.width); }, fail() { console.log('calBoxWidth 获取失败'); }, complete() { console.log('calBoxWidth complete') } }) }, calRightBtnsWidth() { var that = this; this.$element('rightbtns').getBoundingClientRect({ success(res) { let msg = JSON.stringify(res); console.log('calRightBtnsWidth 当前坐标:' + msg); // that.rightbtnsWidth = res.width; that.convertRealPx(false, res.width); }, fail() { console.log('calBoxWidth 获取失败'); }, complete() { console.log('calBoxWidth complete') } }) }, convertRealPx(isLeftWidth, data) { var d = device.getInfoSync(); console.info("calBannerPostion1 d= " + JSON.stringify(d)); //获取页面内可见窗口的高度和宽度,此值不包括标题栏和状态栏高度 let windowWidth = d.windowWidth; //logicWidth对应manifest.json文件设置的designWidth值,默认是750 let logicWidth = d.windowLogicWidth; let result = data * 1.0 * windowWidth / logicWidth; if (isLeftWidth) { this.leftbtnsWidth = result; } else { this.rightbtnsWidth = result; } }, dealTouchStart: function (e) { if (this.disabled) { return; } this.movestartX = e.touches[0].clientX; console.info("dealTouchStart movestartX="+this.movestartX); }, dealTouchMove: function (e) { if (this.disabled) { return; } let moveX = e.touches[0].clientX; let dis = moveX - this.movestartX; console.info("dealTouchMove moveX= " + moveX + ", dis=" + dis); if (Math.abs(dis) < this.threshold) { return; } if (dis > 0) { //右滑动呼出左边的按钮或者隐藏右边按钮恢复初始状态 if (this.isRightOpened) { //隐藏右边的按钮,恢复初始状态 this.animate(dis-this.rightbtnsWidth, dis); } else { //右滑动,呼出左边的按钮 if (this.leftbtnsWidth > 0) { if (!this.isLeftOpened) { console.info("begin to show the left buttons"); this.animate(dis, dis); } } } } else if (dis < 0) { console.info("dealTouchMove left this.isLeftOpened=" + this.isLeftOpened); if (this.isLeftOpened) { //慢慢滑动将左边按钮隐藏 this.animate(this.leftbtnsWidth + dis, dis); } else { //呼出右边的按钮 if (this.rightbtnsWidth > 0) { if (!this.isRightOpened) { console.info("dealTouchMove begin to show the right buttons"); this.animate(dis, dis); } } } } }, dealTouchEnd: function (e) { if (this.disabled) { return; } let endX = e.changedTouches[0].clientX; let dis = endX - this.movestartX; if (Math.abs(dis) < this.threshold) { return; } console.info("dealTouchEnd dis=" + dis + ", endX=" + endX + ", isLeftOpened=" + this.isLeftOpened + ",isRightOpened=" + this.isRightOpened); if (dis > 0) { //往右边滑动 if (this.isRightOpened) { //隐藏右边按钮 this.animate(dis - this.rightbtnsWidth, 0); this.isRightOpened = false; this.$emit('swipestatechange', {params: {"isOpened":false}}); } else { if (!this.isLeftOpened) { //呼出左边按钮,将左边按钮完整显示出来; if (this.leftbtnsWidth > 0) { this.animate(dis, this.leftbtnsWidth); this.isLeftOpened = true; this.$emit('swipestatechange', {params: {"isOpened":true}}); } } } } else if (dis < 0) { //往左滑动 if (this.isLeftOpened) { //隐藏左边按钮 this.animate(this.leftbtnsWidth + dis, 0); this.isLeftOpened = false; this.$emit('swipestatechange', {params: {"isOpened":false}}); } else { if (this.rightbtnsWidth > 0 && !this.isRightOpened) { //呼出右边按钮,将右边按钮完整显示出来 this.animate(dis, -this.rightbtnsWidth); this.isRightOpened = true; this.$emit('swipestatechange', {params: {"isOpened":true}}); } } } }, animate(value1, value2) { console.info("aninate translateX from: " + value1 + ", to:" + value2); let cmp = this.$element('swipe_item'); var options = { duration: 300, easing: 'linear', delay: 0, fill: 'forwards' } console.info("dealTouchMove value2=" + value2); var frames = [ { transform: { translateX: value1, } }, { transform: { translateX: value2, } }]; var animation = cmp.animate(frames, options); animation.play(); animation.onfinish = function () { console.log("animation onfinish"); } animation.oncancel = function () { console.log("animation oncancel"); } }, btnsClick: function(index,direction,btnValue) { console.info("swipe.ux item click direction="+direction); this.$emit('swipebtnclick', {params: {"clickIndex":index,"position":direction,"btnValue":btnValue}}); //按钮关闭 if(direction=='left'){ this.animate(this.leftbtnsWidth, 0); this.isLeftOpened = false; }else{ this.animate( - this.rightbtnsWidth, 0); this.isRightOpened = false; } this.$emit('swipestatechange', {params: {"isOpened":false}}); }, close:function() { if(this.isLeftOpened){ //关闭 this.animate(this.leftbtnsWidth, 0); this.isLeftOpened = false; this.$emit('swipestatechange', {params: {"isOpened":false}}); }else if(this.isRightOpened){ //关闭 this.animate( - this.rightbtnsWidth, 0); this.isRightOpened = false; this.$emit('swipestatechange', {params: {"isOpened":false}}); }else{ //中间部分响应自身的点击事件 this.$emit('centerclick', {params: {}}); } }, closeFromParent:function(e) { console.info("closeFromParent e="+JSON.stringify(e)); if(!this.autoClose){ return; } //最新滑动打开的操作栏不关闭,其他的才关闭 let excludeCloseId=e.detail.exclude; if(excludeCloseId==this.identity){ return; } if(this.isLeftOpened){ //关闭 this.animate(this.leftbtnsWidth, 0); this.isLeftOpened = false; }else if(this.isRightOpened){ //关闭 this.animate( - this.rightbtnsWidth, 0); this.isRightOpened = false; } }, dealClickCenter: function() { console.info("dealClickCenter"); this.close(); }, }</script>页面main.ux<import name="swipeitem" src="../Component/swipe_item.ux"></import><template> <!-- Only one root node is allowed in template. --> <div class="container"> <div class="section"> <text class="sec_txt">只有左边</text> </div> <swipeitem identity='111' left-options="{{leftBtns}}" @swipebtnclick="swipeItemClick" @swipestatechange="swipeItemChange('111')"> <text>只有左边按钮</text> </swipeitem> <div class="section"> <text class="sec_txt">只有右边</text> </div> <swipeitem identity='112' right-options="{{rightBtns}}" @swipebtnclick="swipeItemClick" @swipestatechange="swipeItemChange('112')"> <text>只有右边按钮</text> </swipeitem> <div class="section"> <text class="sec_txt">混合滚动</text> </div> <swipeitem identity='113' right-options="{{rightBtns}}" left-options="{{leftBtns}}" @swipebtnclick="swipeItemClick" @swipestatechange="swipeItemChange('113')"> <text>左右都有混合滚动</text> </swipeitem> <div class="section"> <text class="sec_txt">禁止左右滚动</text> </div> <swipeitem disabled="{{disabled}}" left-options="{{leftBtns}}" right-options="{{rightBtns}}" @swipebtnclick="swipeItemClick"> <text>禁止左右滚动</text> </swipeitem> <div class="section"> <text class="sec_txt">div列表</text> </div> <div class="swipelist" id="swiplist"> <swipeitem identity="{{item.id}}" auto-close={{isautoclose}} right-options="{{item.options}}" for="(index,item) in swipeList" @swipestatechange="listSwipeItemChange(index)" @swipebtnclick="listSwipeItemClick(index)" @centerclick="listCenterItemClick(index)"> <text onclick="clickText">{{item.content}}</text> </swipeitem> </div> </div></template><style> .container { flex-direction: column; width: 100%; } .section { background-color: #f5f5f5; height: 100px; width: 100%; border: 1px solid #f5f5f5; } .swipelist { flex-direction: column; } .istItemStyle { width: 1300px; } .liststyle { flex-direction: column; } .sec_txt { font-size: 32px; font-weight: 600; margin-left: 25px; margin-top: 10px; }</style><script> import prompt from '@system.prompt'; module.exports = { data: { isautoclose:true, leftBtns: [], disabled: true, rightBtns: [], unstyleleftBtns: [], unstylerightBtns: [], swipeList: [] }, onInit: function () { let btn1 = { "txt": "置顶", "style": { "backgroundColor": "#00bfff", "txtColor": "#dcdcdc", "fontsize": 30, "btnwidth": 150 } }; let btn2 = { "txt": "ok", "style": { "backgroundColor": "#00bfff", "txtColor": "#dcdcdc", "fontsize": 30, "btnwidth": 200 } }; let btn3 = { "txt": "取消置顶", "style": { "backgroundColor": "#f01f1f", "txtColor": "#dcdcdc", "fontsize": 30, "btnwidth": 150 } }; let btn4 = { "txt": "cancel", "style": { "backgroundColor": "#00bfff", "txtColor": "#dcdcdc", "fontsize": 30, "btnwidth": 180 } }; let unstylebtn1 = { "txt": "置顶", "style": { "btnwidth": 230 } }; let unstylebtn2 = { "txt": "ok", "style": { "btnwidth": 200 } }; let unstylebtn3 = { "txt": "取消置顶", "style": { "btnwidth": 150 } }; let unstylebtn4 = { "txt": "cancel", "style": { "txtColor": "#ff9900", "fontsize": 30, "btnwidth": 150 } }; this.leftBtns.push(btn1); this.leftBtns.push(btn2); this.rightBtns.push(btn3); this.rightBtns.push(btn4); this.unstyleleftBtns.push(unstylebtn1); this.unstyleleftBtns.push(unstylebtn2); this.unstylerightBtns.push(unstylebtn3); this.unstylerightBtns.push(unstylebtn4); }, onReady(options) { this.swipeList = [{ options: [{ txt: '添加', style: { backgroundColor: 'rgb(255,58,49)', txtColor: "#dcdcdc", fontsize: 36, btnwidth: 150 } }], id: '10', content: 'item1' }, { id: '11', options: [{ txt: '添加', style: { backgroundColor: 'rgb(255,58,49)', txtColor: "#dcdcdc", fontsize: 30, btnwidth: 150 } }, { txt: '删除', style: { backgroundColor: 'rgb(255,58,49)', txtColor: "#dcdcdc", fontsize: 30, btnwidth: 150 } } ], content: 'item2' }, { id: '12', options: [{ txt: '置顶', style: { backgroundColor: 'rgb(255,58,49)', txtColor: "#dcdcdc", fontsize: 30, btnwidth: 150 } }, { txt: '标记为已读', style: { backgroundColor: 'rgb(255,58,49)', txtColor: "#dcdcdc", fontsize: 30, btnwidth: 150 } }, { txt: '删除', style: { backgroundColor: 'rgb(255,58,49)', txtColor: "#dcdcdc", fontsize: 30, btnwidth: 150 } } ], content: 'item3' } ] }, listSwipeItemClick: function (index, e) { console.info("main swipeItemClick e:" + JSON.stringify(e) + ",index=" + index); let position = e.detail.params.position; // // let index = e.detail.params.clickIndex; // let itemListId = e.detail.params.itemListIndex; let btnValue = e.detail.params.btnValue; let msg = ''; if (position == 'left') { // msg='点击了左侧 ${e.detail.params.btnValue}按钮 ' ; msg = '点击了左侧' + btnValue + '按钮 '; } else { msg = '点击了右侧' + btnValue + '按钮 '; } prompt.showToast({ message: msg, duration: 2000, gravity: 'center' }) if (btnValue == "添加") { console.info("begin to add ") this.addSwipteItem(); } else if (btnValue == "删除") { this.delSwipteItem(index); } }, addSwipteItem: function () { this.swipeList.push({ id: new Date().getTime(), content: '新增' + new Date().getTime(), options: [{ txt: '置顶', style: { fontsize: 30, btnwidth: 150 } }, { txt: '标记为已读', style: { backgroundColor: 'rgb(254,156,1)', fontsize: 30, btnwidth: 150 } }, { txt: '删除', style: { backgroundColor: 'rgb(255,58,49)', fontsize: 30, btnwidth: 150 } } ], }); }, delSwipteItem: function (index) { var that = this; prompt.showDialog({ title: '提示', message: '是否删除', buttons: [ { text: '确定', color: '#33dd44' }, { text: '取消', color: '#33dd44' }], success: function (data) { console.log("delSwipteItem showDialog handling callback data:" + JSON.stringify(data)); if (data.index == 0) { //delete that.swipeList.splice(index, 1); } }, fail: function (data, code) { console.log("handling fail, code = " + code); } }) }, swipeItemClick: function (e) { console.info("main swipeItemClick e:" + JSON.stringify(e)); let position = e.detail.params.position; let btnValue = e.detail.params.btnValue; let msg = ''; if (position == 'left') { // msg='点击了左侧 ${e.detail.params.btnValue}按钮 ' ; msg = '点击了左侧' + btnValue + '按钮 '; } else { msg = '点击了右侧' + btnValue + '按钮 '; } prompt.showToast({ message: msg, duration: 2000, gravity: 'center' }) }, clickText: function () { console.info("click text"); prompt.showToast({ message: 'click text', duration: 2000, gravity: 'center' }) }, listCenterItemClick: function (index, e) { console.info("main listCenterItemClick e:" + JSON.stringify(e) + ",index=" + index); prompt.showToast({ message: 'click text', duration: 2000, gravity: 'center' }) }, listSwipeItemChange: function (index, e) { console.info("main listSwipeItemChange e:" + JSON.stringify(e) + ",index=" + index); this.$broadcast('closeSwipeItem', { 'exclude': this.swipeList[index].id }); }, swipeItemChange: function(id,e) { console.info("main swipeItemChange e:" + JSON.stringify(e)+",id="+id); this.$broadcast('closeSwipeItem', { 'exclude': id }); }, }</script>
欲了解更多更全技术文章,欢迎访问https://developer.huawei.com/consumer/cn/forum/?ha_source=zzh
边栏推荐
- 使用BiSeNet实现自己的数据集
- Full text query classification
- Low success rate of unit test report
- Input of mathematical formula of obsidan
- BiSeNet的特点
- 快速集成认证服务-HarmonyOS平台
- Implement your own dataset using bisenet
- Bisenet features
- Standard function let and generic extension function in kotlin
- MES system is a necessary choice for enterprise production
猜你喜欢
The single value view in Splunk uses to replace numeric values with text
Splunk子查询模糊匹配csv中字段值为*
2-3 lookup tree
Splunk查询csv lookup table数据动态查询
Wang Zijian: is the NFT of Tencent magic core worth buying?
使用SwinUnet训练自己的数据集
Caractéristiques de bisenet
一种适用于应用频繁测试下快速查看Pod的日志的方法(grep awk xargs kuberctl)
Deit learning notes
使用BiSeNet实现自己的数据集
随机推荐
[go ~ 0 to 1] obtain timestamp, time comparison, time format conversion, sleep and timer on the seventh day
Interpreting the practical application of maker thinking and mathematics curriculum
SSM 整合
Several ways of lambda used in functions in kotlin (higher-order functions)
Practice of implementing cloud native Devops based on rainbow library app
Data type - floating point (C language)
Go语言中,函数是一种类型
使用BiSeNet实现自己的数据集
POJ - 3616 Milking Time(DP+LIS)
Data type - integer (C language)
Pytoch (VI) -- model tuning tricks
Splunk中single value视图使用将数值替换为文字
Standard function let and generic extension function in kotlin
Splunk查询csv lookup table数据动态查询
[hard core science popularization] working principle of dynamic loop monitoring system
[IELTS speaking] Anna's oral learning records part2
National standard gb28181 protocol video platform easygbs adds streaming timeout configuration
2 - 3 arbre de recherche
Tuowei information uses the cloud native landing practice of rainbow
Automatic upgrading of database structure in rainbow