1.  什么是抽屉组件

抽屉组件是一种特殊的弹出面板,可以模拟手机App中推入拉出抽屉的效果。抽屉一般具有如下特点:

  1. 抽屉可显示在左边,也可以显示在右边;
  2. 抽屉宽度可定制;
  3. 抽屉有遮罩层,点击遮罩层可收起抽屉;
  4. 手势滑动可呼出抽屉;

抽屉(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. 抽屉默认是关闭不显示的,通过“display: none;” 来隐藏。
  2. 收起、展开通过X轴的平移动画控制,收起时,移到屏幕之外,展开时平移到可视区域。不管是展开还是收起,都是平滑的动画效果。
  3. 抽屉显示在左侧还是右侧,是通过div的flex-direction控制的,显示在左侧时,设置row,显示在右侧时,设置为row-reverse。

图1 左抽屉打开、收起style

图2 有抽屉打开、收起style

图3 左抽屉动画

图4  右抽屉动画

2.4遮罩层实现

遮罩层初始状态不显示,通过“display: none;” 来隐藏。抽屉展示时,显示遮罩层,收起时,不显示,遮罩层使用透明度实现。

2.5父子组件通信

  1. 父组件通过parentVm.$broadcast()向子组件传递抽屉打开、收起的事件,子组件通过$on()监听事件和参数。
  2. 子组件通过$watch()方法监听抽屉显示模式mode属性的变化,从而修改css样式,让其在正确的位置显示抽屉。
  3. 子组件通过drawerchange事件及参数通知父组件。

2.6手势呼出抽屉

在抽屉处手势滑动,呼出抽屉,需要监听touchstart和touchend事件。注意滑动范围,只有在抽屉边缘处呼出抽屉,其其他位置不呼出。

3.总结

实现抽屉组件,您可以从中学会如下知识点:

  1. 熟悉快应用子组件的设计和属性定义;
  2. 熟悉父子组件通信;
  3. 熟悉动画样式的实现;
  4. 学会如何实现一个遮罩层。

欲了解更多详情,请参见:
华为官网:
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>

快应用中实现自定义抽屉组件相关推荐

  1. java 自定义 jpanel_在JList中使用自定义JPanel组件 - java

    我需要显示带有名为Item的自定义JPanel组件的JList.这些组件具有唯一的标识name.它们可以动态添加到JList中,也可以更新(如果已经存在).我尝试以下实现,但它只会生成一个空的JLis ...

  2. Android中实现自定义View组件并使其能跟随鼠标移动

    场景 实现效果如下 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书.教程推送与免费下载. 实现 新建An ...

  3. vue抽屉_VUE组件中的 Drawer 抽屉实现代码

    因为项目中用的是 element-ui 框架,而这个框架并没有抽屉组件,所以自己实现一个,具体代码如下: drawer.vue {{ title }} x export default { props ...

  4. 「后端小伙伴来学前端了」关于Vue中的自定义事件,组件绑定自定义事件实现通信

    傍晚的月亮 前言 原本这篇打算写Vue中的那个全局事件总线的原理,但是发现自己少写了这个自定义事件,不讲明白这个自定义事件的操作,不好写全局事件原理,于是就有了这篇文章拉. 一.v-on指令 要讲自定 ...

  5. 《九》微信小程序中的自定义组件

    开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用:也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护. 自定义组件:可复用的 WXML.WXSS.JS.JSON. beh ...

  6. 手把手教你实现小程序中的自定义组件

    之前做小程序开发的时候,对于开发来说比较头疼的莫过于自定义组件了,当时官方对这方面的文档也只是寥寥几句,一笔带过而已,所以写起来真的是非常非常痛苦!! 好在微信小程序的库从 1.6.3 开始,官方对于 ...

  7. vue自定义组件中再嵌套其他组件

    其实就是在容器组件里放一个插槽(slot). VUE的看点是组件.组件应用的典型例子,是一个网站首页.分为若干版块.每个版块都是一个方框框,样式一致,只是版块中间内容不同.对于VUE来说,很自然的想法 ...

  8. delphi 自定义控件_Delphi中的自定义组件开发

    delphi 自定义控件 Components are essential elements of the Delphi environment. One of the most important ...

  9. java中同步组件_Java并发编程(自定义同步组件)

    并发包结构图: 编写一个自定义同步组件来加深对同步器的理解 业务要求: * 编写一个自定义同步组件来加深对同步器的理解. * 设计一个同步工具:该工具在同一时刻,只允许至多两个线程同时访问,超过两个线 ...

  10. Expo大作战(十二)--expo中的自定义样式Custom font,以及expo中的路由RouteNavigation

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

最新文章

  1. 实例化Bean的方法(基于xml配置)-http://blog.csdn.net/shymi1991/article/details/48153293
  2. 爬虫python代码-一则python3的简单爬虫代码
  3. AndroidUI的组成部分RoomButton
  4. 吴恩达Deeplearning.ai课程学习全体验:深度学习必备课程 By 路雪2017年8月14日 11:44 8 月 8 日,吴恩达正式发布了 Deepleanring.ai——基于 Cours
  5. 算法工程师想拿百万高薪,5大维度评估竞争力,情商也很重要
  6. DirectX11 With Windows SDK--27 计算着色器:双调排序
  7. linux a8启动过程,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  8. java怎么配置tomcat_Eclipse中配置Tomcat
  9. shell脚本连接、读写、操作mysql数据库实例
  10. (转)ASP.NET 3.5 企业级开发
  11. canvas基础学习笔记
  12. 盘点2020年最好用的7款3D游戏建模软件
  13. 有效处理Java异常三原则
  14. 【更新】【Windows Server 2019】存储服务器的配置和管理——iSCSI的安装和配置(上)
  15. 目标检测回归损失函数:SmoothL1/IoU/GIoU/DIoU/CIoU Loss
  16. Python爬取喜马拉雅有声小说【转载】
  17. pku1036Gangsters Dp
  18. python 下载m3u8视频
  19. 汇编语言程序设计V-贺利坚-专题视频课程
  20. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java疫情防控管理系统02vsf

热门文章

  1. k8s设计-多容器pod设计模式
  2. 如何利用pygame 开发坦克大战小游戏
  3. python暑假培训成都
  4. 域名转入需要经过“命名审核”状态
  5. C语言中的指针加减偏移量
  6. IIS6,IIS7,IIS8的脚本自动安装
  7. ajax多个分页,通过Ajax与kaminari进行多重分页
  8. MySQL数据库授权与索引
  9. 未转变者服务器.id大全,Unturned未转变者Elver最新地图ID汇总 2021最新版ID大全
  10. 关于“墨者安全专家3.7”不得不说的事情