快应用中实现自定义抽屉组件
1. ?什么是抽屜組件
抽屜組件是一種特殊的彈出面板,可以模擬手機App中推入拉出抽屜的效果。抽屜一般具有如下特點:
?????? 抽屜(Drawer)組件結構分為控制器和抽屜內容兩部分。 一般來說,controls都是按鈕、圖標之類的可點擊的組件,類似真實抽屜的把手,content是抽屜內部的東西,每個抽屜的content都是不一樣的。點擊controls可以觸發content的顯示和收起。 因此,在使用抽屜組件的頁面布局可以抽象成如下結構:
<div class=“page"><div class=“controls"><image></image></div><stack class=“drawer_container”><div class=“page_content”>…</div><drawer class="drawer"><div class=“content”>…</div></drawer ></stack> </div>2.實現步驟
??? 抽屜組件屬于一種擴展能力,當前快應用已有的組件是無法滿足的,需要自定義組件實現。
2.1自定義子組件
????? 抽屜外觀都是通用的,但是抽屜內部格局content不一樣,在設計的時候,不能直接寫死content布局,否則一旦content部分的UI有變化,會導致子組件也要修改,違背了代碼設計中的“開閉”原則。
???? 所以,我們子組件drawer.ux中,使用了slot 組件來承載父組件中定義的content,由使用drawer組件的頁面來完成content布局,如下圖所示:
2.2子組件設計
支持的屬性如下:
| 屬性 | 類型 | 默認值 | 描述 |
| mode | String | left | 設置抽屜的顯示位置,支持left和right |
| mask | boolean | true | 抽屜展開時是否顯示遮罩層 |
| maskClick | Boolean | true | 點擊遮罩層是否關閉抽屜 |
| width | Number | 320px | 抽屜寬度 |
支持的事件:
| 事件名稱 | 參數 | 描述 |
| drawerchange | {showDrawer:booleanValue} | 抽屜收起、展開的回調事件 |
2.3抽屜展開和收起
| 圖1 左抽屜打開、收起style | ?? 圖2 有抽屜打開、收起style |
| ??圖3 左抽屜動畫 | ?? 圖4? 右抽屜動畫 |
2.4遮罩層實現
遮罩層初始狀態不顯示,通過“display:?none;” 來隱藏。抽屜展示時,顯示遮罩層,收起時,不顯示,遮罩層使用透明度實現。
2.5父子組件通信
2.6手勢呼出抽屜
?在抽屜處手勢滑動,呼出抽屜,需要監聽touchstart和touchend事件。注意滑動范圍,只有在抽屜邊緣處呼出抽屜,其其他位置不呼出。
3.總結
實現抽屜組件,您可以從中學會如下知識點:
欲了解更多詳情,請參見:
華為官網:
https://developer.huawei.com/consumer/cn/forum/topic/0202636422958390131?fid=18?ha_source=zzh???????
最后附上完整的實現代碼:
抽屜drawer.ux
<template><div id="drawercontent" style="display:flex;position:absolute;width:100%;height:100%;top:0;left:0;bottom: 0; flex-direction: {{flexdirection}}" onswipe="dealDrawerSwipe"><div class="{{maskstyle}}" onclick="close('mask')"></div><div id="unidrawercontent" class="{{unidrawerstyle}}" style="width:{{drawerWidth}}+'px'}"><slot></slot></div></div> </template><style>.stack {flex-direction: column;height: 100%;width: 100%;}.uni-mask-open {display: flex;height: 100%;width: 100%;position: absolute;background-color: rgb(0, 0, 0);opacity: 0.4;}.uni-mask-closed {height: 100%;width: 100%;position: absolute;background-color: rgb(0, 0, 0);display: none;}.uni-drawer {display: none;height: 100%;}.uni-drawer-open-left {display: flex;height: 100%;animation-name: translateX;animation-timing-function: linear;animation-fill-mode: forwards;animation-duration: 300ms;}.uni-drawer-closed-left {display: flex;height: 100%;animation-name: translateXReverse;animation-timing-function: linear;animation-fill-mode: forwards;animation-duration: 600ms;}.uni-drawer-open-right {display: flex;height: 100%;flex-direction: row-reverse;animation-name: translateXRight;animation-timing-function: linear;animation-fill-mode: forwards;animation-duration: 300ms;}.uni-drawer-closed-right {display: flex;height: 100%;flex-direction: row-reverse;animation-name: translateXRightReverse;animation-timing-function: linear;animation-fill-mode: forwards;animation-duration: 600ms;}@keyframes translateX {from {transform: translateX(-110px);}to {transform: translateX(0px);}}@keyframes translateXReverse {from {transform: translateX(0px);}to {transform: translateX(-750px);}}@keyframes translateXRight {from {transform: translateX(300px);}to {transform: translateX(0px);}}@keyframes translateXRightReverse {from {transform: translateX(0px);}to {transform: translateX(750px);}} </style><script>module.exports = {props: {/*** 顯示模式(左、右),只在初始化生效*/mode: {type: String,default: ''},/*** 蒙層顯示狀態*/mask: {type: Boolean,default: true},/*** 遮罩是否可點擊關閉*/maskClick: {type: Boolean,default: true},/*** 抽屜寬度*/width: {type: Number,default: 320}},data() {return {visibleSync: false,showDrawer: false,watchTimer: null,drawerWidth: 600,maskstyle: 'uni-mask-closed',unidrawerstyle: 'uni-drawer',flexdirection: 'row'}},onInit() {console.info("drawer oninit");this.$on('broaddrawerstate', this.drawerStateEvt);this.drawerWidth = this.width;if(this.mode=="left"){this.flexdirection="row";}else{this.flexdirection="row-reverse";}this.$watch('mode', 'onDrawerModeChange');},onDrawerModeChange: function (newValue, oldValue) {console.info("onDrawerModeChange newValue= " + newValue + ", oldValue=" + oldValue);if (newValue === 'left') {this.flexdirection = 'row';} else {this.flexdirection = 'row-reverse';}},drawerStateEvt(evt) {this.showDrawer = evt.detail.isOpen;console.info("drawerStateEvt this.showDrawer= " + this.showDrawer);if (this.showDrawer) {this.open();} else {this.close();}},close(type) {// 抽屜尚未完全關閉或遮罩禁止點擊時不觸發以下邏輯if ((type === 'mask' && !this.maskClick) || !this.visibleSync) {return;}console.info("close");this.maskstyle = 'uni-mask-closed';if (this.mode == "left") {this.unidrawerstyle = 'uni-drawer-closed-left';} else {this.unidrawerstyle = 'uni-drawer-closed-right';}this._change('showDrawer', 'visibleSync', false)},open() {// 處理重復點擊打開的事件if (this.visibleSync) {return;}console.info("open this.mode="+this.mode);this.maskstyle = 'uni-mask-open';if (this.mode == "left") {this.unidrawerstyle = 'uni-drawer-open-left';} else {this.unidrawerstyle = 'uni-drawer-open-right';}this._change('visibleSync', 'showDrawer', true)},_change(param1, param2, status) {this[param1] = status;if (this.watchTimer) {clearTimeout(this.watchTimer);}this.watchTimer = setTimeout(() => {this[param2] = status;this.$emit('drawerchange', {'showDrawer':status});}, status ? 50 : 300)},dealDrawerSwipe: function(e) {console.info("dealDrawerSwipe");let direction=e.direction;if (this.mode == "left") {if(direction=="left"){this.close();}}else{if(direction=="right"){this.close();}}},} </script>頁面hello.ux: <import name="drawer" src="../Drawer/drawer.ux"></import> <template><!-- Only one root node is allowed in template. --><div class="container"><div class="title"><div class="icon" @click="isOpen"><text class="icon-item" for="[1,1,1,1]"></text></div><text class="page-title">模擬drawer組件</text></div><stack style="width: 100%;height:100%;" ontouchstart="touchstart" ontouchend="touchend"><div class="content"><text style="color: #0faeff;">點擊左上角按鈕滑出左側抽屜</text><text class="txt" onclick="switchLocation">切換抽屜滑出位置左或右</text><text style="color: #0faeff;margin-left: 10px;margin-right: 10px">手指在屏幕左側邊緣右滑亦可滑出左側抽屜,手指在屏幕右側邊緣左滑亦可滑出右側抽屜</text><text style="color: #0faeff;margin-top: 20px;margin-left: 10px;margin-right: 10px">滑出抽屜的寬度默認為600px(即最大可設置的寬度,最小可設置寬度為父容器的100px), 如果輸入的值超出500則按最大可設置寬度顯示,小于最小可設置寬時則按最小可設置寬度顯示</text><input id="input" class="input" type="number" placeholder="請輸入寬度值,單位為px" value="{{inputValue}}" onchange="changeValue" /><text style="color: #0faeff;">鍵盤收起后,即可滑動或點擊呼出抽屜</text><text class="txt" onclick="maxWidth">設置抽屜為最大寬度</text><text class="txt" onclick="minWidth">設置抽屜為最小寬度</text></div><drawer id="drawer" mode="{{drawerShowMode}}" width="{{drawerWidth}}" mask-click="true" @drawerchange="change"><tabs class="tabs" style="width: {{drawerWidth}}px;"><tab-content class="tabcontent"><list class="list"><block for="listarray"><list-item class="list-item" type="item" onclick="chooseItem($idx)"><text>第{{ $item }}章測試目錄</text></list-item></block></list><text>this is second page</text></tab-content><tab-bar class="tabbar"><text class="text">part one</text><text class="text">part two</text></tab-bar></tabs></drawer></stack></div> </template><style>.container {flex-direction: column;}/* 自定義內容屬性 */.content {flex-direction: column;justify-content: center;align-items: center;width: 100%;height: 100%;}.txt {width: 80%;height: 80px;background-color: #0faeff;border-radius: 10px;text-align: center;margin-left: 80px;margin-top: 10px;margin-bottom: 10px;}.input {width: 80%;height: 80px;border: 1px solid #000000;margin-left: 80px;}/* 標題屬性 */.title {height: 120px;width: 100%;align-items: center;background-color: #0faeff;padding-left: 20px;}.page-title {font-size: 40px;padding-left: 150px;}.icon {width: 60px;height: 60px;flex-direction: column;justify-content: space-around;}.icon-item {height: 4px;background-color: rgb(212, 212, 212);width: 100%;}.tabs {height: 100%;background-color: rgb(248, 230, 230);}.tabcontent {width: 100%;height: 90%;}.tabbar {width: 100%;height: 10%;}.text {width: 50%;height: 100%;font-size: 50px;text-align: center;}.list {flex: 1;width: 100%;}.list-item {height: 90px;width: 100%;padding: 0px 20px;border-bottom: 1px solid #f0f0f0;align-items: center;justify-content: space-between;} </style><script>import prompt from '@system.prompt';module.exports = {data: {componentData: {},display: false,listarray: '',drawerWidth: 360,inputValue: '',drawerShowMode: 'right',movestartX: 0},onInit() {this.listarray = this.getList(20);},isOpen() {this.display = !this.display;if (this.display) {this.showDrawer();} else {this.closeDrawer();}},// 打開抽屜showDrawer(e) {this.$broadcast('broaddrawerstate', {isOpen: true})},// 關閉抽屜closeDrawer(e) {this.$broadcast('broaddrawerstate', {isOpen: false})},// 抽屜狀態發生變化觸發change(e) {console.info("change e=" + JSON.stringify(e));this.display = e.detail.showDrawer;},getList(num) {let list = []for (let i = 1; i <= num; i++) {list.push(i)}return list},switchLocation() {if (this.drawerShowMode === 'left') {this.drawerShowMode = 'right';} else {this.drawerShowMode = 'left';}},changeValue(e) {if (e.value >= 600) {this.drawerWidth = 600} else if (e.value <= 300) {this.drawerWidth = 300} else {this.drawerWidth = e.value}console.log("hjj", this.drawerWidth);if (e.value.length === 3) {this.$element('input').focus({ focus: false })}},maxWidth() {this.drawerWidth = 600},minWidth() {this.drawerWidth = 300},chooseItem(index) {prompt.showToast({message: `該內容為簡單示例,點擊了第${index + 1}條`,})},touchstart(e) {console.info("touchstart");this.movestartX = e.touches[0].offsetX;},touchend(e) {console.info("touch end e:" + JSON.stringify(e));let moveEndX = e.changedTouches[0].offsetX;if (this.drawerShowMode === "left") {//在屏幕左邊緣從左往右邊滑動時,呼出抽屜if (this.movestartX < 30) {let dis = moveEndX - this.movestartX;if (dis > 30) {this.showDrawer();}}} else {//在屏幕右邊緣從右往左邊滑動時,呼出抽屜if (this.movestartX > 720) {let dis = moveEndX - this.movestartX;if (dis < -30) {this.showDrawer();}}}},} </script>總結
以上是生活随笔為你收集整理的快应用中实现自定义抽屉组件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信小程序 - <textarea> 多
- 下一篇: i 标签设置背景图片作 icon的问题