当前位置:网站首页>如何在快应用中实现滑动操作组件
如何在快应用中实现滑动操作组件
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
边栏推荐
- go写一个在一定时间内运行的程序
- Give full play to the wide practicality of maker education space
- PLSQL的安装和配置
- Tuowei information uses the cloud native landing practice of rainbow
- The truth of robot education in hands-on practice
- Improve the delivery efficiency of enterprise products (1) -- one click installation and upgrade of enterprise applications
- Rainbow combines neuvector to practice container safety management
- Data type - floating point (C language)
- Opencv learning note 5 - gradient calculation / edge detection
- Kotlin combines flatmap for filtering and zip merge operators
猜你喜欢

Xcit learning notes

Installation and configuration of PLSQL

Ebpf cilium practice (2) - underlying network observability

【雅思口语】安娜口语学习记录 Part2
![[untitled]](/img/b5/348b1d8b5d34cf10e715522b9871f2.png)
[untitled]

One click deployment of highly available emqx clusters in rainbow

A single game with goods increased by 100000, and the rural anchor sold men's clothes on top of the list?

Componentspace2022, assertions, protocols, bindings, and configuration files

One click installation of highly available Nacos clusters in rainbow

Exercise arrangement 2.10, 11
随机推荐
Merge sort and non comparison sort
POJ - 3616 Milking Time(DP+LIS)
Arm GIC (IV) GIC V3 register class analysis notes.
21 general principles of wiring in circuit board design_ Provided by Chengdu circuit board design
opencv学习笔记五——梯度计算/边缘检测
[kuangbin]专题十五 数位DP
The reified keyword in kotlin is used for generics
Virtual address space
[quick start of Digital IC Verification] 14. Basic syntax of SystemVerilog learning 1 (array, queue, structure, enumeration, string... Including practical exercises)
Grpc, oauth2, OpenSSL, two-way authentication, one-way authentication and other column directories
Leetcode 1984. Minimum difference in student scores
单场带货涨粉10万,农村主播竟将男装卖爆单?
What is the method of manual wiring in PCB design in 22protel DXP_ Chengdu electromechanical Development Undertaking
MySQL introduction - crud Foundation (establishment of the prototype of the idea of adding, deleting, changing and searching)
接口作为参数(接口回调)
The single value view in Splunk uses to replace numeric values with text
Coquette data completes the cloud native transformation through rainbow to realize offline continuous delivery to customers
23 Chengdu instrument customization undertaking_ Discussion on automatic wiring method of PCB in Protel DXP
[quick start of Digital IC Verification] 12. Introduction to SystemVerilog testbench (svtb)
All about PDF crack, a complete solution to meet all your PDF needs



