# (Vue)MetroWidget
# 1.基本介绍
MetroWidget组件用于 JsView-Vue3 架构。 该组件提供自动排布和焦点管理功能
# 2.控件说明
# 属性设置(props)
- name: [String]
- 焦点ID, 具体参见JsvFocusBlock
- top, left, width, height: [int]
- 控件的位置尺寸信息
- direction: [Symbol]
- 控件方向, 值为 HORIZONTAL / VERTICAL
- HORIZONTAL: 默认值, item排布向右延伸, 按从上到下, 从左到右的顺序排布
- VERTICAL: item排布向下延伸, 按从左到右,从上到下的顺序排布
- 控件方向, 值为 HORIZONTAL / VERTICAL
- provideData: [() => Array]
- 提供描画数据
- layoutType: [String]
- 布局的类型, 值为 relative/absolute
- relative: 默认值, 根据item的尺寸自动排布
- absolute: 根据item的绝对坐标进行布局
- 布局的类型, 值为 relative/absolute
- measures: [(itemData: Object) => Object]
- 返回item描画相关信息
- 返回对象说明
- left, top: [int] layoutType为absolute时的坐标
- width, height: [int] item的尺寸, 注意item的尺寸大于组件排布区域(组件尺寸减去padding)时会导致错误
- marginRight, marginBottom: [int] item与右/底方向相邻item的间距
- focusable: [boolean] item是否可以获得焦点
- findNextAnchor: [Object] 自定义上下左右四条边寻找临近item的基准位置,值为0-1 {left: 0.5, right: 0.5, top: 0.5, bottom: 0.5}
- itemSlide: [number] item的滚动控制, 可选值为:
- METRO_WIDGET_CONST.ITEM_SLIDE.DISABLE 不滚动
- METRO_WIDGET_CONST.ITEM_SLIDE.ACT_ITEM_FOCUS 按照SlideSetting滚动
- METRO_WIDGET_CONST.ITEM_SLIDE.ACT_FOCUS_RECT_EVENT 按照子组件发送的focusRect事件滚动
- zIndex: [int | object] item的zIndex, 为object时可以分别设置{focus: 0, normal: 0}, 为number时则同时设置获焦和不获焦的zIndex
- uid: [string] item的uid, 可以通过uid来设置焦点
- permanent: [boolean] 是否永久保留
- enableTap: [boolean] 是否接受触控tap
- showSkeleton: [boolean] 打断描画时, 是否显示骨架图
- 返回对象说明
- 返回item描画相关信息
- padding: [{left: int ,top: int, right: int, bottom: int}]
- 描画item时的内边距, 组件的可排布区域为组件尺寸减去padding
- focusMoveType: [int]
- 焦点移动模式, 值为FocusMoveType中定义的类型
- NO_ADJUST: 无特殊处理
- COLUMN_LOOP: 到达列首/尾后跳转到上/下一列, 只在水平滚动时生效
- ROW_LOOP: 到达行首/尾后跳转到上/下一行, 只在竖直滚动时生效
- COLUMN_FIND_NEAR: 一列中没有下一个元素时是否跳转到相邻列
- ROW_FIND_NEAR: 一行中没有下一个元素时是否跳转到相邻行
- 焦点移动模式, 值为FocusMoveType中定义的类型
- initFocusId: [int]
- 组件首次创建时的初始焦点
- slideSetting: [WholePageSlide | SeamlessSlide | FixPositionSlide]
- 滚动设置, 只为三种设置类的对象, 具体见SlideSetting
- WholePageSlide: 整页滚动设置
- SeamlessSlide: 无缝滚动设置
- FixPositionSlide: 固定位滚动设置
- 滚动设置, 只为三种设置类的对象, 具体见SlideSetting
- enableItemRenderBreak: [boolean]
- 按键事件是否能打断item的描画, 默认为false
- placeHolderSetting: [{backgroundColor: string, borderRadius: string, gap: int}]
- item描画可打断时, 显示的占位符
- backgroundColor: [String] 颜色
- focusBackgroundColor: [String] 获焦时的颜色
- borderRadius: [String] 圆角设置
- gap: [int] 间隔(弃用)
- item描画可打断时, 显示的占位符
- sendFocusRectEvent: [boolean]
- item获焦时在焦点树上冒泡一个事件, MetroWidget接收到事件时会触发滚动操作, 一般用于嵌套时的滚动
- onFocus: [() => Void]
- 获焦的回调, 具体见JsvFocusBlock
- onBlur: [() => Void]
- 失焦的回调, 具体见JsvFocusBlock
- onEdge: [(info: {direction: Symbol, rect: Object}) => Void]
- 焦点移动到边缘时的回调
- direction: [Symbol] 边缘方向
- rect: [{x: int, y: int, width: int, height: int}] 到达边缘时的区域
- 焦点移动到边缘时的回调
- onScroll: [(visibleStart: int, visibleRange: int, totalSize: int) => Void]
- 滚动时的回调
- visibleStart: [int] 可视区域的起点
- visibleRange: [int] 可视区域大小(不含padding)
- totalSize: [int] 所有item的总长
- 滚动时的回调
- loadAll: [boolean]
- 强制加载所有view, 包括不显示的view
- disableClip: [boolean]
- 取消对显示范围的裁剪
- keepTraceRange: [number]
- 可视区域前后多少屏的item在移出可视范围后不释放(默认移出可视区域的item会被释放)
# slotProps
- data
- 此item对应的原始数据
- onAction
- 供item组件注册回调的对象
- query
- 提供MetroWidget的内部状态和一些方法
- id: [int] item的id,
- position: [(index : int) => Object ] 获取 item 相对 MetroWidget 左上角坐标
- return: [{left: int, top: int, width: int, height: int}]
- templatePosition: {(index : int) => Object} 获取 item 的布局坐标, 一般用于自定义滚动
- return: [{left: int, top: int, width: int, height: int}]
- getCurrentFocusId: {() => Object } 获取当前焦点信息,
- return: [{id: int, index: int}]
- id: [int] 在可获得焦点元素列表中的索
- index: [int] 在所有元素列表中的索引
- return: [{id: int, index: int}]
- slideTo: [(targetPosition : int, doAnim : boolean) => Void] 滚动到指定位置,注意这个位置是 item 的布局坐标,另外当某些item需要接管滚动时,itemConfig中takeOverSlide要为true。
- params
- targetPosition: [int] 目标位置
- doAnim: [boolean] 是否做动画
- params
- 提供MetroWidget的内部状态和一些方法
- onItemEdge
- 当item组件内部有按键逻辑时, item到达边缘后通知MetroWidget的回调
# 方法
- slideTo: [(position: int, doAnim: boolean) => Void]
- 滚动到指定位置
- params
- position: [int] 目标位置
- doAnim: [boolean] 是否做动画
- params
- 滚动到指定位置
- slideToItem: [(index: int, doAnim: boolean) => Void]
- 滚动到指定item, 具体位置由slideType属性决定
- params
- index: [int] 目标item的index
- doAnim: [boolean] 是否做动画
- params
- 滚动到指定item, 具体位置由slideType属性决定
- setEnterFocusId: [(rectInfo: Object) => Void]
- 设置焦点移入时的区域,组件将会根据此区域寻找最近的item
- params
- rectInfo: [{direction: EdgeDirection, rect: {x: int, y: int, width: int, height: int}}] 移入区域
- params
- 设置焦点移入时的区域,组件将会根据此区域寻找最近的item
- setFocusId: [(id: int, needSlide: boolean, doAnim: boolean, extraSetting: Object) => Void]
- 设置焦点
- params
- id: [int] 目标id
- needSlide: [boolean] 是否需要滚动
- doAnim: [boolean] 是否做动画
- extraSetting: [{lockChildSlideEvent: boolean}] 设置焦点时忽略子发过来的滚动事件
- params
- 设置焦点
- refreshData: [(forceUpdate: boolean) => Void]
- 刷新数据 refresh时的对比逻辑:
- 对比array里的object是否是同一个对象
- 对比array里的jsvKey字段, 若一致则认为该数据未改变
- params
- forceUpdate: [boolean] 是否强制刷新
- 刷新数据 refresh时的对比逻辑:
- getVisibleItems: [() => {start: int, end: int, dataList: Array}]
- 获取可视item的列表信息
- return: [{start: int, end: int, dataList: Array}]
- start: [int] 开始的index
- end: [int] 结尾的index
- dataList: [Arrya] 可视item对应的原始数据列表
- return: [{start: int, end: int, dataList: Array}]
- 获取可视item的列表信息
- lock: [(type: int) => () => Void)]
- 设置内部的锁
- params
- type: [int] 锁的类型, 由METRO_WIDGET定义的参数通过|组合而成
- SLIDE: 滚动动画锁
- CHILD_SLIDE_EVENT: 子组件发送的滚动事件锁
- type: [int] 锁的类型, 由METRO_WIDGET定义的参数通过|组合而成
- return: [() => Void] 解锁的函数
- params
- 设置内部的锁
- unlock: [(type: int) => Void]
- 解锁
- params
- type: [int] 同lock
- params
- 解锁
- setZIndex [(index: int, blurZIndex: int, focusZIndex: int) => Void]
- 设置item的zIndex
- params
- index: [int] item的index
- blurZIndex: [int] 未获焦时的zIndex
- focusZIndex: [int] 获焦时的zIndex, 缺省时使用blurZIndex的值
- params
- 设置item的zIndex
# SlideSetting {#slide-setting}
- WholePageSlide: 整页滚动设置
- constructor: [new (settingObj: {speed: int, easing: string}) => WholePageSlide]
- speed: 滚动速度, 像素/毫秒
- easing: 动画的缓动函数
- constructor: [new (settingObj: {speed: int, easing: string}) => WholePageSlide]
- SeamlessSlide: 无缝滚动设置
- constructor: [new (settingObj: {startPercent: float, endPercent: float, speed: int, easing: string}) => SeamlessSlide]
- headRange: 头部触发滚动的区域, 取值范围0-1
- tailRange: 尾部触发滚动的区域, 取值范围0-1
- headSafeArea: 保证头部item完整显示的安全区域大小
- tailSafeArea: 保证尾部item完整显示的安全区域大小
- speed: 滚动速度, 像素/毫秒
- easing: 动画的缓动函数
- fixFirstPage: 首页不滚动
- constructor: [new (settingObj: {startPercent: float, endPercent: float, speed: int, easing: string}) => SeamlessSlide]
- FixPositionSlide: 固定位滚动设置
- constructor: [new (settingObj: {fixPercent: float, speed: int, easing: string}) => FixPositionSlide]
- fixPercent: 固定的位置, 取值范围0-1
- speed: 滚动速度, 像素/毫秒
- easing: 动画的缓动函数
- fixFirstPage: 首页不滚动
- constructor: [new (settingObj: {fixPercent: float, speed: int, easing: string}) => FixPositionSlide]
# 3.技巧展示
插槽如何使用?
- 插槽属性参见slotProp
提问: item的onFocus/onBlur和JsvFocusBlock的onFocus/onBlur有何区别?
- 整个MetroWidget在焦点树上只有一个节点, 既MetroWidget内部只有一个<jsv-focus-block/>, props中的onFocus/onBlur对应这个JsvFocusBlock
- item的onFocus/onBlur对应的是MetroWidget自身内部状态的改变
按键是怎么处理的?
- 上下左右键已经由控件接管,不需要开发者处理。
- 通过注册onClick回调来处理用户的OK键动作。
- 其他按键组件不会处理, 按JsvFocusBlock统一的冒泡处理
焦点到达边缘后怎么离开此组件?
- 当焦点移动到控件边缘时,会调用onEdge回调。在回调中通过参数传递的值来决定焦点转移的行为
# 4.示例搭建
# demo.vue
- 前置操作
import { MetroWidget, HORIZONTAL, // 定义MetroWidget方向的Symbol EdgeDirection, // MetroWidget onEdge回调中的方向比对 useFocusHub, } from "jsview"; import { onMounted } from "vue"; import Item from "./Item.vue" // 描画item的组件 const focusHub = useFocusHub(); // mounted后设置焦点 onMounted(() => { focusHub.setFocus(name); })
1
2
3
4
5
6
7
8
9
10
11
12
13 - template中添加MetroWidget组件
<template> <metro-widget :name="name" :left="widgetLayout.left" :top="widgetLayout.top" :width="widgetLayout.width" :height="widgetLayout.height" :provideData="provideData" :direction="HORIZONTAL" :measures="measures" :onEdge="onEdge" > </metro-widget> </template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14 - 在script中定义MetroWidget所需的prop
//组件的焦点ID const name = "widget" //组件的布局信息 const widgetLayout = { left: 50, top: 50, width: 300, height: 200, } //提供数据的回调 const provideData = () => { const data = []; for (let i = 0; i < 30; i++) { data.push({ width: 90, height: 90, marginRight: 10, marginBottom: 10, color: "#00DD00", content: i, }); } return data; } // 提供item布局信息的回调 const measures = (data) => { return { width: data.width, height: data.height, marginRight: data.marginRight, marginBottom: data.marginBottom, }; }; //组件到达边缘时的回调 const onEdge = (edgeInfo) => { if (edgeInfo.direction == EdgeDirection.top) { console.log("top edge"); } else { console.log("other side edge") } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 - 添加MetroWidget的slot, 并在slot中描画item
<template> <metro-widget ... > <template #renderItem="{ data, onAction }"> <item :data="data" :onAction="onAction" /> </template> </metro-widget> </template>
1
2
3
4
5
6
7
8
9 - 全部代码
<script setup> import { MetroWidget, HORIZONTAL, // 定义MetroWidget方向的Symbol EdgeDirection, // MetroWidget onEdge回调中的方向比对 useFocusHub, } from "jsview"; import Item from "./Item.vue" import { onMounted } from "vue"; const focusHub = useFocusHub(); // mounted后设置焦点 onMounted(() => { focusHub.setFocus(name); }); //组件的焦点ID const name = "widget"; //组件的布局信息 const widgetLayout = { left: 50, top: 50, width: 300, height: 200, }; //提供数据的回调 const provideData = () => { const data = []; for (let i = 0; i < 30; i++) { data.push({ width: 90, height: 90, marginRight: 10, marginBottom: 10, color: '#00DD00', content: i, }); } return data; }; // 提供item布局信息的回调 const measures = (data) => { return { width: data.width, height: data.height, marginRight: data.marginRight, marginBottom: data.marginBottom, }; }; //组件到达边缘时的回调 const onEdge = (edgeInfo) => { if (edgeInfo.direction == EdgeDirection.top) { console.log("top edge"); } else { console.log("other side edge"); } }; </script> <template> <metro-widget :name="name" :left="widgetLayout.left" :top="widgetLayout.top" :width="widgetLayout.width" :height="widgetLayout.height" :provideData="provideData" :direction="HORIZONTAL" :measures="measures" :onEdge="onEdge" > <template #renderItem="{ data, onAction }"> <item :data="data" :onAction="onAction" /> </template> </metro-widget> </template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# item.vue
- 主要操作
- 根据数据描画内容
- 向onAction对象注册回调
- 在回调中更改自身状态
- 全部代码
<script setup> import { ref, shallowRef, inject } from "vue"; const props = defineProps({ data: Object, onAction: Object, }); const focused = ref(false); //在回调中更改自身状态 const onFocus = () => { focused.value = true; }; const onBlur = () => { focused.value = false; }; const onClick = () => { console.log("item onclick ", props.data); }; // 注册回调 props.onAction.register("onFocus", onFocus); props.onAction.register("onBlur", onBlur); props.onAction.register("onClick", onClick); </script> <template> <div :style="{ width: data.width, height: data.height, fontSize: 30, color: focused ? '#FF0000' : '#FFFFFF', backgroundColor: data.color, }" > {{ data.content }} </div> </template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38