DPL 来了——百度2019AI开发者大会DuerOS公开课摘要解读之三
在百度2019AI开发者大会上有很多相对精彩的公开课,DuerOS相关的公开课有4场,分别是:
DuerOS技能开发与CFC编程
如何在DuerOS技能中实现用户支付购买
面向多方式交互模型的DPL应用
故事引擎在DuerOS技能开发中的应用
DPL来了, DPL给我们带来了什么呢?本文将摘要解读叶老师分享的“面向多方式交互模型的DPL应用”。
什么是DPL?DPL能够做什么?DPL是由什么组成的?有哪些特点呢?......"talk is cheap, show me the codes", 公开课上通过一些实际的例子展示了DPL的页面构建和如何实现页面交互。
什么是DPL呢?DPL全称是Dueros Presentation Language即Dueros展现语言,是度秘面向开发者提供的一种可以自定义模版展现与交互方式的一种描述语言。它主要包括2个部分:组件和指令。
那么,如何去理解DPL的组件和指令呢?可以类比HTML来理解DPL。在HTML中有DOM和事件监听的概念,那么,DPL中的组件就相当于HTML中DOM, DOM通过嵌套构成HTML的视图,DPL的组件也是通过互相嵌套构成了DPL的视图。
同样的,HTML中可以绑定事件监听,DPL中也是可以绑定事件监听的。在与后端服务交互时, HTML可以发送request请求, DPL发送的则是UserEvent事件。
在没有DPL之前,在DuerOS 有屏音箱上是如何展现技能的呢?
一般使用模版来完成有屏展现,比如左侧各种类型的BodyTemplate和ListTemplate等。在使用这些模版的过程中,会发现这些模版的布局相对固定,可以做调整的地方比较少,页面的交互方式也相对较少。
使用DPL开发就会灵活很多,用户可以自定义页面的样式布局。交互上也可以通过Command实现更加丰富的页面交互效果。
下面是一个图文混排的样例:
这个屏幕是一个左文右图的页面,左边有4段文本描述,右边有一张图,这些文本的样式和整体布局都是自定义实现的。
再看一个纵向列表的示例:
这个列表样式的页面,列表项是由序号、图片、文本组成的,呈现方式也是自定义的。
同样的,类似的横向列表也是可以通过DPL实现。
图中的每一个列表项,都是上图下文,图片有圆角,左上有小图标,下面是文字。这些使用DPL的Container容器组件都实现起来比较容易。
列表的嵌套也是如此——
上图是一个嵌套列表的结构,它可以上下滑动,然后每个单个的横向list 也是可以滑动的。这是使用DPL的list 嵌套来实现的。其实,只要熟练了DPL的组件和布局方法,就可以像HTML那样来开发DPL的页面。
在公开课上,叶老师演示了一个复杂一点的完整示例,包含了页面样式布局和交互的实现。这个demo里面用到了Text、Image、Container、ScrollView、 Frame、 Video、Header 等组件, Text和Image比较容易理解,就是展示图片和文本,然后Container是容器组件,可以嵌套其他组件,能够控制展现方向,常用来布局。SrcollView是滑动窗口组件,就是当内容的尺寸超出规定的宽高时,会产生滚动条,滑动可以看见隐藏的内容。Frame可以理解成是一种可以设置边线样式的Container,就比如说设置边线宽度,颜色或者圆角这些。Video是视频播放组件,Header是复合组件,是页面的头部。
指令可以理解成端上的一些动作逻辑,比如控制列表上下滑动的Command,可以左右翻页的Command,可以产生动画的Command,可以控制视频播放暂停的Command等等。有的Command必须绑定在特定组件上才能执行,比如ControlMedia必须作用在视频或者音频组件上。
对视频演示的简要说明如下:
首先打开技能,进入到首页,首页左边是一个ScrollView,右边是一个Pager嵌套Frame组件。
然后通过视频相册的Query进入到相册页面,相册页面使用pager实现,现在看到的是Pager的第一页。接着,通过Query向左翻页进入Pager的第二页,这个翻页是通过返回SetPage指令进行控制的,然后这个是通过播放Query返回媒体指令,开始播放视频。
通过播放第几个来切换视频,这个是通过UpdateComponent指令实现。向上滑动列表也是通过command 控制List组件的滑动,然后回到列表顶部。
点击了第一个,切换视频。通过query收藏视频,也是通过Command实现。
点击了左下角的收藏图片,颜色变成红色,同时有动画效果。右下角这个白心变红,是组件事件监听实现的。
通过Query“播放第几个”来切换视频,它返回的也是Command。通过点击切换视频,与页面组件的事件监听实现类似。向上滑动列表可以通过query操作List 滑动,使用Scroll实现。回到列表顶部也是通过ScrollToIndex实现的。
那么,DPL 有哪些特点呢?
首先呢,DPL是使用JSON的格式来进行编写的,支持自定义布局的,可以将页面中重复的组件进行抽取,然后在需要的地方进行引用,组件之间支持嵌套。同时,支持自定义样式和常量,也是可以在组件和布局中进行引用,这样可以对样式进行集中管理。
数据绑定的目的是拆分布局样式和展示的数据,做到模版与数据分离,可以通过表达式在组件、布局以及样式中进行引用。 这就是DPL的6个特点。
上代码:
示例代码主要分为:resources,dataSource,styles), layouts 和mainTemplate5个部分。
其中mainTemplate是模版的解析入口,Resources和styles就是自定义的常量和样式,然后这些常量和样式会在mainTemplate中被引用,在dataSource绑定数据源,数据源在mainTemplate中使用$花括号形式的表达式来进行引用,最后layouts是自定义的布局。只有mainTemplate是非空的,其他都可以为空,也就是可以把所有的内容都直接写在mainTemplate,不去拆分,但是从开发的角度还是建议合理的使用这些特性,不要都写在mianTemplate中。
以一个简单图片展示为例
上图的布局比较简单,首先有一个背景图片,就是这个看着像地板的这个背景。然后有一个Header,主体的部分就是一张图,下面来看下它的DPL代码。
从意图处理的代码看起, this.geDPLDirective()中引用的DPL描述文件。
mainTemplate这个字段下的items, 第一个是Image组件,是一张绝对布局的背景图。主体的部分是一个Container组件,Container是一个容器组件,容器组件是描述页面布局结构的重要组件,可以设置它的宽高、展现方向和背景颜色,描述展现方向的属性为:flex-direction, 默认是垂直方向展现-column,如果需要设置为水平展现-row,需要明确指定flex-direction的属性值为 row。
在Container代码的内部,上面是一个Header复合组件,下面是一个Image组件,image组件设置了宽高,通过margin-left, margin-top使图片呈现居中效果。当然,实现居中效果还有其他的方式,比如通过设置它外层Container的内边距padding-left 和padding-top,也可以达到这样的呈现效果,还可以使用绝对布局的方式去实现, 通过left top的属性去实现。
再看一下演示视频中的DPL实现——
这个页面的布局是上面有个Header,然后左边是一个List列表,右边是一个包含视频组件的嵌套容器,在这个容器里,上面是一个视频组件,下面是视频title 和收藏状态的图片。
该页面布局整体上是一个上下垂直的结构,然后主体部分是一个水平横向的结构,然后左边是list,右边又是一个上下垂直的结构,然后右下“葡萄酒”所在的布局它是一个水平横向的结构。通过这样的层层嵌套就呈现了所展示的页面布局。
上代码——
同样从意图处理部分开始,重点看mainTemplate部分,最上面是使用Image组件实现的背景图,然后下面一个总体Container组件,宽高100%铺满全屏,沿垂直方向展现。它的里面是一个Header加上一个 Container,这个子Container沿水平方向展现,然后左边是一个List组件,右边又是一个嵌套的Container,右边这个Containe沿垂直方向展现。上面是Video,下面又是一个包含左文右图的Container,左文右图呈现的是水平的结构,然后它和上面这个video组成一个垂直的结构。
从代码分析来看:通过设置Container的宽高和展现方向实现了页面的结构布局,然后每个Container再填充TextImage 或者其他Container,呈现出最终效果。
通过对这个DPL进行改造,可以介绍一下自定义布局、自定义样式以及数据绑定和语法表达式的使用。
在这个DPL文档中包含了 resources, dataSource, styles和layouts 的部分,resources就是自定义的常量集合,这里定义了一个颜色的常量。Styles是用户自定义的样式集合, 样式支持继承, 这里面它是继承一个baseStyle的样式。layouts部分定义了2个layout,他们互相之间进行了嵌套,并且通过params进行参数传递。
然后, dataSource就是引用的数据源,在mainTemplate中通过$花括号的表达式进行了引用,表达式除了支持这种点值运算还支持支持其他的运算符语法。
其实表达式的用法很灵活多样,常见的操作符和运算符: 如算术运算符、关系运算符、逻辑运算、位运算符、点值/条件运算符等。
为什么要使用这些高级的特性呢? 如果一个文档直接平铺的写下来,属性样式数据都直接写在组件上, 会导致无法重用代码,结构混乱臃肿,而且不利于后期维护。
DPL目前支持的所有的组件,包括9种基础组件和2种复合组件,还在更新中。
DPB平台有专门的DPL调试模拟器,可以辅助开发者诊断页面视图,使用模拟器来进行调试,修改组件样式以后可以立马看到展现的效果,比使用真机调试要快很多。
回归主题,交互是DPL的另一个核心。
DPL的交互分为2种:一种是本地交互,通过给组件预先绑定事件监听属性,然后通过点击等事件来触发对应的Command动作。另一种是云端交互,可以通过Query返回包含ExecuteCommands指令的意图来触发端上的Command动作,
还有一类是通过监听UserEvent事件以后返回包含ExecuteCommands指令的意图,这个UserEvent是SendEvent类型的Command在被点击的时候下发的事件,返回的结果都是相同的。
回归视频中展示的例子,先介绍端上的本地交互。
在DPL文档中,绑定的事件监听属性是自定义布局listItemLayout中的onClick属性。onClick这个事件属性绑定了一系列的Command,这里绑定了6个Command,第一个SetState指定了需要作用的ComponnetId,然后替换了它的src的值,这个就是更换播放url的动作。需要特别强调的一点是:Comamnd的使用需要和ComponnetId一一对应,这样Command才能找到目标作用的组件,另外,在事件属性中如果不指定ComponentId的值,默认为当前组件, 如果是意图返回,就必须指定ComponentId,否则找不到组件就不会执行Command动作。
接下来,第二个Command还是SetState,它把视频组件的autoplay这个属性设置为true。然后是一个动画Animation的command,它指定的动画效果是把height属性从90dp放大到120dp,repeatCount=1指定了动画只执行一次,
这样的效果就是类似一个图片被放大后又恢复原样。剩下的2个SetState的作用是替换视频下方的title和收藏状态图标。最后是一个媒体指令,动作是让视频开始播放。
这一系列的动作综合下来,效果是一个图片放大,视频切换,同时视频title以及收藏图片的颜色发生了变化。
本地交互是在组件上直接绑定事件监听来实现的,整个过程都是在当前的页面上完成的,没有与后端产生数据的交互。
而云端交互是通过意图返回ExecuteCommands来实现的——
意图的实现代码对应的Query是:“播放第几个?”。返回的结果是ExecuteCommands指令,ExecuteCommands同样包含了6个Command,它返回的Command和前面所说的本地交互所绑定的Command是一样的。所不同的是云端交互的Command是包含在指令当中的,并且是通过Query召回,最终返回到端上产生效果。
回顾一下在云端交互中,通过监听上报的UserEvent事件,返回ExecuteCommands的情况。
有一种类型的Command叫SendEvent,UserEvent这个事件就是和SendEvent对应的,如果组件绑定了SendEvent类型的Command,当组件有事件被触发的时候,就会上报UserEvent事件到云端,如果组件不绑定SendEvent就不会上报UserEvent。在开发中,绑定SendEvent的组件一般都要指定当前组件的ComponnetId,并且保证它在当前DPL文档中的唯一性,这样UserEvent组件在下发的时候同样会带上这个ComponentId,那么云端就可以依据上报的ComponnetId做逻辑判断。
通过监听返回的UserEvent事件与Query的返回UserEvent事件对比,唯一不同的地方是,云端监听到这个UserEvent事件以后返回了ExecuteCommands,返回的数据格式都是一致的,最终产生的动作效果也是一样的。
通过DPL的交互实现方式,可以页面的异步更新。
在本地交互和云端交互中,用到了3种Command,分别是SetState,Animation,ControlMedia。为了实现切换视频的功能,多次使用了SetState这个Command。
实际上,有更加合适的Command可以实现这个功能,那就是异步更新指令-UpdateComponent,它的作用是使用指定的DPL文件来替换目标组件的的内容,在上面的代码中,就使用了update.json中的布局样式替换了指定组件的样式,在这里,它返回了一个布局样式相同,但是播放地址不一样,视频title也不一样的DPL文档,这一新的DPL文档替换了原有的组件也实现了切换视频的目的。
异步更新指令是常用指令,在异步刷新一些布局复杂的样式时经常用到。它对应的语法格式和DPL是一样的,具有很强的灵活性。
那么,DPL 有多少种指令呢?
目前有三种指令类型:标准指令,复合指令以及媒体指令。
标准指令包括8种:
SendEvent指令:绑定了SendEvent指令的组件在事件触发的时候会上报UserEvent事件,用于和后端进行数据交互。
SetPage指令:专门作用在Pager组件上的, Pager组件是一个可以左右翻页的组件,SetPage指令的作用是设置页面到指定的Index页。
ScrollToIndex指令:作用在List组件上的,它的作用是滑动组件的视图到指定的item。
Scroll指令:可以作用在SrollView和List上,作用是向上或向下滑动。展示视频中上下滑动List的部分用的就是这个指令。
Animation指令:产生动画效果,目前支持动画指令的组件只有Text 和 Image。
AutoPage指令:作用在Page组件上,作用是可以实现自动翻页,翻页的时间间隔可以自行设定。
SetState指令:用于替换组件的属性值,可以作用在所用基础组件上。
UpdateComponnet指令:在异步更新页面的时候经常用到。
复合指令包括并行和串行指令两种:
目前Sequential串行指令暂不支持,还在开发中。并行指令可以一次包含多个Command,在执行的时候所用的Command都同时执行,互不依赖。
媒体指令主要是ControlMedia指令——
这个指令主要是作用在音频和视频组件,控制动作有播放,暂停,下一个等等,Video组件还有控制弹幕开关的动作。
在公开课的最后, 叶老师动手演示了如何使用DPL开发技能,部署的方式采用的是CFC。
以上是“面向多方式交互模型的DPL应用”公开课的内容概要,详细信息和完整PPT 可以关注 dueros.baidu.com/dbp 官网和DuerOS 开发者公众号以及各种开发者社群。