jsbox-cview 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/components/alert/input-alert.ts +73 -0
- package/components/alert/login-alert.ts +75 -0
- package/components/alert/plain-alert.ts +49 -0
- package/components/alert/uialert.ts +110 -0
- package/components/artificial-flowlayout.ts +321 -0
- package/components/base.ts +47 -0
- package/components/custom-navigation-bar.ts +570 -0
- package/components/dialogs/dialog-sheet.ts +87 -0
- package/components/dialogs/form-dialog.ts +23 -0
- package/components/dialogs/list-dialog.ts +87 -0
- package/components/dialogs/text-dialog.ts +34 -0
- package/components/dynamic-itemsize-matrix.ts +190 -0
- package/components/dynamic-preference-listview.ts +691 -0
- package/components/dynamic-rowheight-list.ts +62 -0
- package/components/enhanced-imageview.ts +128 -0
- package/components/image-pager.ts +177 -0
- package/components/page-control.ts +91 -0
- package/components/pageviewer-titlebar.ts +170 -0
- package/components/pageviewer.ts +124 -0
- package/components/rotating-view.ts +126 -0
- package/components/searchbar.ts +373 -0
- package/components/sheet.ts +113 -0
- package/components/single-views.ts +828 -0
- package/components/spinners/loading-double-rings.ts +121 -0
- package/components/spinners/loading-dual-ring.ts +90 -0
- package/components/spinners/loading-wedges.ts +112 -0
- package/components/spinners/spinner-androidstyle.ts +264 -0
- package/components/static-preference-listview.ts +991 -0
- package/components/symbol-button.ts +105 -0
- package/components/tabbar.ts +451 -0
- package/controller/base-controller.ts +216 -0
- package/controller/controller-router.ts +74 -0
- package/controller/pageviewer-controller.ts +86 -0
- package/controller/presented-page-controller.ts +57 -0
- package/controller/splitview-controller.ts +323 -0
- package/controller/tabbar-controller.ts +99 -0
- package/package.json +23 -0
- package/test.ts +0 -0
- package/tsconfig.json +121 -0
- package/utils/colors.ts +13 -0
- package/utils/cvid.ts +34 -0
- package/utils/l10n.ts +42 -0
- package/utils/path.ts +100 -0
- package/utils/rect.ts +67 -0
- package/utils/uitools.ts +117 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # cview Dynamic RowHeight List
|
|
3
|
+
*
|
|
4
|
+
* 可以自动更新 rowHeight 的 list
|
|
5
|
+
*
|
|
6
|
+
* 核心策略为 list 的所有行均为 cview,且每一个 cview 需要实现一个方法:heightToWidth(width: number) => number
|
|
7
|
+
* 通过这个方法汇报自己在某个宽度的时候所需要的高度,这必须任何时候均立即可用;
|
|
8
|
+
* 如果这个方法不可用,那么行高设为 44
|
|
9
|
+
*
|
|
10
|
+
* 特别参数
|
|
11
|
+
* sections: {title: string, rows: cview[]}[]
|
|
12
|
+
*
|
|
13
|
+
* 除了 props.data, props.template 和 events.rowHeight 不可用,其他均和 list 一致
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Base } from './base';
|
|
17
|
+
|
|
18
|
+
interface DynamicRowHeightListProps extends Omit<UiTypes.ListProps, "data" | "template"> {}
|
|
19
|
+
interface DynamicRowHeightListEvents extends Omit<UiTypes.ListEvents, "rowHeight"> {}
|
|
20
|
+
interface DynamicRowHeightListCView extends Base<any, any> {
|
|
21
|
+
heightToWidth: (width: number) => number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class DynamicRowHeightList extends Base<UIListView, UiTypes.ListOptions>{
|
|
25
|
+
_sections: {title: string, rows: DynamicRowHeightListCView[]}[];
|
|
26
|
+
_defineView: () => UiTypes.ListOptions;
|
|
27
|
+
constructor({ sections, props, layout, events }: {
|
|
28
|
+
sections: {title: string, rows: DynamicRowHeightListCView[]}[];
|
|
29
|
+
props: DynamicRowHeightListProps;
|
|
30
|
+
layout: (make: MASConstraintMaker, view: UIListView) => void;
|
|
31
|
+
events: DynamicRowHeightListEvents;
|
|
32
|
+
}) {
|
|
33
|
+
super();
|
|
34
|
+
this._sections = sections;
|
|
35
|
+
this._defineView = () => {
|
|
36
|
+
const data = this._sections.map(n => ({
|
|
37
|
+
title: n.title,
|
|
38
|
+
rows: n.rows.map(r => r.definition)
|
|
39
|
+
}));
|
|
40
|
+
return {
|
|
41
|
+
type: "list",
|
|
42
|
+
props: {
|
|
43
|
+
data,
|
|
44
|
+
...props
|
|
45
|
+
},
|
|
46
|
+
layout,
|
|
47
|
+
events: {
|
|
48
|
+
rowHeight: (sender, indexPath) => {
|
|
49
|
+
const cview = this._sections[indexPath.section].rows[indexPath.row];
|
|
50
|
+
if (
|
|
51
|
+
cview.heightToWidth &&
|
|
52
|
+
typeof cview.heightToWidth === "function"
|
|
53
|
+
)
|
|
54
|
+
return cview.heightToWidth(sender.frame.width);
|
|
55
|
+
else return 44;
|
|
56
|
+
},
|
|
57
|
+
...events
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 此组件是为了加强 imageView,实现以下目的:
|
|
3
|
+
* 1. 点击实现上下翻页
|
|
4
|
+
* 2. 双指放大缩小(但不可以双击放大缩小)
|
|
5
|
+
*
|
|
6
|
+
* 请注意:此组件使用了Runtime代码重新设置了Tapped事件。
|
|
7
|
+
* 与以前使用touchesEnded事件来实现相比,可以避免在滑动手指时误触发。
|
|
8
|
+
* 但因此带来了副作用:必须在关闭前通过releaseGestureObject释放掉此视图中自定义的NSObject,否则再次启动可能会有问题。
|
|
9
|
+
*
|
|
10
|
+
* Props:
|
|
11
|
+
* src: string, 图片地址
|
|
12
|
+
* maxZoomScale: number, 最大缩放倍数,默认为2
|
|
13
|
+
*
|
|
14
|
+
* Events:
|
|
15
|
+
* upperLocationTouched: (sender: EnhancedImageView) => void, 上半部分被点击
|
|
16
|
+
* lowerLocationTouched: (sender: EnhancedImageView) => void, 下半部分被点击
|
|
17
|
+
*/
|
|
18
|
+
import { Base } from "./base";
|
|
19
|
+
import { Scroll } from "./single-views";
|
|
20
|
+
import { cvid } from "../utils/cvid";
|
|
21
|
+
|
|
22
|
+
export class EnhancedImageView extends Base<UIView, UiTypes.ViewOptions> {
|
|
23
|
+
private _props: { src: string; maxZoomScale: number };
|
|
24
|
+
private _scroll: Scroll;
|
|
25
|
+
private _gestureObject: any;
|
|
26
|
+
_defineView: () => UiTypes.ViewOptions;
|
|
27
|
+
|
|
28
|
+
constructor({ props, layout, events = {} }: {
|
|
29
|
+
props: { src: string; maxZoomScale?: number };
|
|
30
|
+
layout: (make: MASConstraintMaker, view: UIView) => void;
|
|
31
|
+
events?: {
|
|
32
|
+
upperLocationTouched?: (sender: EnhancedImageView) => void;
|
|
33
|
+
lowerLocationTouched?: (sender: EnhancedImageView) => void;
|
|
34
|
+
}
|
|
35
|
+
}) {
|
|
36
|
+
super();
|
|
37
|
+
this._props = { maxZoomScale: 2, ...props };
|
|
38
|
+
this._scroll = new Scroll({
|
|
39
|
+
props: {
|
|
40
|
+
zoomEnabled: true,
|
|
41
|
+
doubleTapToZoom: false,
|
|
42
|
+
maxZoomScale: this._props.maxZoomScale
|
|
43
|
+
},
|
|
44
|
+
layout: $layout.fill,
|
|
45
|
+
views: [
|
|
46
|
+
{
|
|
47
|
+
type: "image",
|
|
48
|
+
props: {
|
|
49
|
+
id: "image",
|
|
50
|
+
src: this._props.src,
|
|
51
|
+
contentMode: 1
|
|
52
|
+
},
|
|
53
|
+
layout: $layout.fill
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
events: {
|
|
57
|
+
ready: view => {
|
|
58
|
+
$delay(0.1, () =>
|
|
59
|
+
this._addGesture(view, (gesture: any) => {
|
|
60
|
+
const location = gesture.$locationInView(view.ocValue());
|
|
61
|
+
const realLocation = $point(location.x - view.bounds.x, location.y - view.bounds.y)
|
|
62
|
+
const frame = this.view.frame
|
|
63
|
+
if (realLocation.y <= frame.height / 2) {
|
|
64
|
+
if (events.upperLocationTouched) events.upperLocationTouched(this)
|
|
65
|
+
} else {
|
|
66
|
+
if (events.lowerLocationTouched) events.lowerLocationTouched(this)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
this._defineView = () => {
|
|
74
|
+
return {
|
|
75
|
+
type: "view",
|
|
76
|
+
props: {
|
|
77
|
+
id: this.id
|
|
78
|
+
},
|
|
79
|
+
views: [this._scroll.definition],
|
|
80
|
+
layout,
|
|
81
|
+
events: {
|
|
82
|
+
layoutSubviews: sender => {
|
|
83
|
+
$delay(0.1, () => (this.src = this.src));
|
|
84
|
+
$delay(0.3, () => (this.src = this.src));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
private _addGesture(view: AllUIView, event: (gesture: any) => void) {
|
|
93
|
+
const objectId = cvid.newId;
|
|
94
|
+
$define({
|
|
95
|
+
type: objectId + ": NSObject",
|
|
96
|
+
events: {
|
|
97
|
+
tapped: event
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
const object = $objc(objectId).$new();
|
|
101
|
+
$objc_retain(object); // 此步骤是必须的,否则将很快被系统释放掉,但是必须在关闭时手动释放掉,否则再次启动可能会有问题
|
|
102
|
+
this._gestureObject = object;
|
|
103
|
+
const tapGestureRecognizer = $objc("UITapGestureRecognizer")
|
|
104
|
+
.$alloc()
|
|
105
|
+
.$initWithTarget_action(this._gestureObject, "tapped:");
|
|
106
|
+
view.ocValue().$addGestureRecognizer(tapGestureRecognizer);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
releaseGestureObject() {
|
|
110
|
+
if (this._gestureObject) $objc_release(this._gestureObject);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get src() {
|
|
114
|
+
return this._props.src;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
set src(src) {
|
|
118
|
+
this._props.src = src;
|
|
119
|
+
const view = this._scroll.view.get("image") as UIImageView;
|
|
120
|
+
view.src = src;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get image() {
|
|
124
|
+
const view = this._scroll.view.get("image") as UIImageView;
|
|
125
|
+
return view.image;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 与内置的Gallery组件相比,ImagePager组件可以动态刷新,适用于图片数量较多的场景,以及需要动态加载图片列表的场景
|
|
3
|
+
*
|
|
4
|
+
* # Props
|
|
5
|
+
* srcs: string[] - 图片地址列表
|
|
6
|
+
* page: number - 当前页码
|
|
7
|
+
*
|
|
8
|
+
* # Events
|
|
9
|
+
* changed: (page: number) => void - 页码变化时触发
|
|
10
|
+
* tapped: (sender: ImagePager) => void - 点击图片时触发
|
|
11
|
+
*/
|
|
12
|
+
import { Base } from "./base";
|
|
13
|
+
import { Matrix } from "./single-views";
|
|
14
|
+
|
|
15
|
+
export class ImagePager extends Base<UIView, UiTypes.ViewOptions> {
|
|
16
|
+
_props: {
|
|
17
|
+
srcs: string[];
|
|
18
|
+
page: number;
|
|
19
|
+
};
|
|
20
|
+
_matrix: Matrix;
|
|
21
|
+
_pageLoadRecorder: { [key: number]: boolean };
|
|
22
|
+
_defineView: () => UiTypes.ViewOptions;
|
|
23
|
+
constructor({ props, layout, events = {} }: {
|
|
24
|
+
props: {
|
|
25
|
+
srcs?: string[];
|
|
26
|
+
page?: number;
|
|
27
|
+
};
|
|
28
|
+
layout: (make: MASConstraintMaker, view: UIView) => void;
|
|
29
|
+
events: {
|
|
30
|
+
changed?: (page: number) => void;
|
|
31
|
+
tapped?: (sender: ImagePager) => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
}) {
|
|
35
|
+
super();
|
|
36
|
+
this._props = {
|
|
37
|
+
srcs: [],
|
|
38
|
+
page: 0,
|
|
39
|
+
...props
|
|
40
|
+
};
|
|
41
|
+
this._pageLoadRecorder = {};
|
|
42
|
+
this._matrix = new Matrix({
|
|
43
|
+
props: {
|
|
44
|
+
direction: $scrollDirection.horizontal,
|
|
45
|
+
pagingEnabled: true,
|
|
46
|
+
alwaysBounceVertical: false,
|
|
47
|
+
showsVerticalIndicator: false,
|
|
48
|
+
showsHorizontalIndicator: false,
|
|
49
|
+
template: {
|
|
50
|
+
views: [
|
|
51
|
+
{
|
|
52
|
+
type: "scroll",
|
|
53
|
+
props: {
|
|
54
|
+
id: "scroll",
|
|
55
|
+
zoomEnabled: true,
|
|
56
|
+
maxZoomScale: 3,
|
|
57
|
+
doubleTapToZoom: true
|
|
58
|
+
},
|
|
59
|
+
layout: $layout.fill,
|
|
60
|
+
views: [
|
|
61
|
+
{
|
|
62
|
+
type: "image",
|
|
63
|
+
props: {
|
|
64
|
+
id: "image",
|
|
65
|
+
contentMode: $contentMode.scaleAspectFit
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
data: this._props.srcs.map(n => {
|
|
73
|
+
return { image: { src: n } };
|
|
74
|
+
})
|
|
75
|
+
},
|
|
76
|
+
layout: $layout.fill,
|
|
77
|
+
events: {
|
|
78
|
+
ready: sender => {
|
|
79
|
+
// 如果没有此处的relayout,则会出现莫名其妙的bug
|
|
80
|
+
sender.relayout();
|
|
81
|
+
this.page = this.page;
|
|
82
|
+
this.loadsrc(this.page);
|
|
83
|
+
},
|
|
84
|
+
itemSize: (sender, indexPath) => {
|
|
85
|
+
return $size(sender.frame.width, sender.frame.height);
|
|
86
|
+
},
|
|
87
|
+
forEachItem: (view, indexPath) => {
|
|
88
|
+
const scroll = view.get("scroll") as UIScrollView;
|
|
89
|
+
scroll.zoomScale = 0;
|
|
90
|
+
//$delay(0.01, () => {});
|
|
91
|
+
},
|
|
92
|
+
willEndDragging: (sender, velocity, target) => {
|
|
93
|
+
const oldPage = this.page;
|
|
94
|
+
this._props.page = Math.round(target.x / sender.frame.width);
|
|
95
|
+
//this.loadsrc(this.page, true);
|
|
96
|
+
if (oldPage !== this.page && events.changed)
|
|
97
|
+
events.changed(this.page);
|
|
98
|
+
},
|
|
99
|
+
didScroll: sender => {
|
|
100
|
+
this.loadsrc(this.page + 1, true);
|
|
101
|
+
this.loadsrc(this.page - 1, true);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
this._defineView = () => {
|
|
106
|
+
return {
|
|
107
|
+
type: "view",
|
|
108
|
+
props: {
|
|
109
|
+
id: this.id,
|
|
110
|
+
userInteractionEnabled: true
|
|
111
|
+
},
|
|
112
|
+
layout,
|
|
113
|
+
views: [this._matrix.definition],
|
|
114
|
+
events: {
|
|
115
|
+
layoutSubviews: sender => {
|
|
116
|
+
this._pageLoadRecorder = {};
|
|
117
|
+
sender.relayout();
|
|
118
|
+
this._matrix.view.reload();
|
|
119
|
+
this.page = this.page;
|
|
120
|
+
$delay(0.1, () => this.loadsrc(this.page, true));
|
|
121
|
+
$delay(0.3, () => this.loadsrc(this.page, true));
|
|
122
|
+
},
|
|
123
|
+
tapped: sender => {
|
|
124
|
+
const cell = this._matrix.view.cell($indexPath(0, this.page));
|
|
125
|
+
if (!cell) return;
|
|
126
|
+
const scroll = cell.get("scroll") as UIScrollView;
|
|
127
|
+
const zoomScale = scroll.zoomScale;
|
|
128
|
+
$delay(0.3, () => {
|
|
129
|
+
const zoomScale1 = scroll.zoomScale;
|
|
130
|
+
if (zoomScale === zoomScale1 && events.tapped)
|
|
131
|
+
events.tapped(this);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
loadsrc(page: number, forced = false) {
|
|
140
|
+
if (page < 0 || page >= this._props.srcs.length) return;
|
|
141
|
+
const cell = this._matrix.view.cell($indexPath(0, page));
|
|
142
|
+
if (!cell) return;
|
|
143
|
+
const image = cell.get("image") as UIImageView;
|
|
144
|
+
if (forced || !image.image || !this._pageLoadRecorder[page]) {
|
|
145
|
+
const scroll = cell.get("scroll") as UIScrollView;
|
|
146
|
+
scroll.zoomScale = 0;
|
|
147
|
+
this._pageLoadRecorder[page] = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get page() {
|
|
152
|
+
return this._props.page;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get currentImage() {
|
|
156
|
+
const cell = this._matrix.view.cell($indexPath(0, this.page));
|
|
157
|
+
if (!cell) return;
|
|
158
|
+
const image = cell.get("image") as UIImageView;
|
|
159
|
+
return image.image;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
set page(page) {
|
|
163
|
+
this._matrix.view.scrollTo({
|
|
164
|
+
indexPath: $indexPath(0, page),
|
|
165
|
+
animated: false
|
|
166
|
+
});
|
|
167
|
+
this._props.page = page;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
scrollToPage(page: number) {
|
|
171
|
+
this._matrix.view.scrollTo({
|
|
172
|
+
indexPath: $indexPath(0, page),
|
|
173
|
+
animated: true
|
|
174
|
+
});
|
|
175
|
+
this._props.page = page;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # CView PageControl
|
|
3
|
+
*
|
|
4
|
+
* 基于 Runtime 构建 PageControl
|
|
5
|
+
*
|
|
6
|
+
* 请注意本视图如果没有足够的横向宽度,会显示不全
|
|
7
|
+
* ## Props
|
|
8
|
+
*
|
|
9
|
+
* - 只写 numberOfPages: numnber
|
|
10
|
+
* - 读写 currentPage: number
|
|
11
|
+
* - 只写 pageIndicatorTintColor?: JSBoxColor
|
|
12
|
+
* - 只写 currentPageIndicatorTintColor?: JSBoxColor
|
|
13
|
+
* - 其他通用属性
|
|
14
|
+
*
|
|
15
|
+
* ## Events
|
|
16
|
+
*
|
|
17
|
+
* changed: (cview: Cview, currentPage: number) => void
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { Runtime } from "./single-views";
|
|
21
|
+
|
|
22
|
+
export class PageControl extends Runtime {
|
|
23
|
+
private _numberOfPages: number;
|
|
24
|
+
private _currentPage: number;
|
|
25
|
+
private _pageIndicatorTintColor?: UIColor;
|
|
26
|
+
private _currentPageIndicatorTintColor?: UIColor;
|
|
27
|
+
private _changed?: (sender: PageControl, currentPage: number) => void;
|
|
28
|
+
private _pageControl: any;
|
|
29
|
+
constructor({ props, layout, events = {} }: {
|
|
30
|
+
props: {
|
|
31
|
+
numberOfPages?: number;
|
|
32
|
+
currentPage?: number;
|
|
33
|
+
pageIndicatorTintColor?: UIColor;
|
|
34
|
+
currentPageIndicatorTintColor?: UIColor;
|
|
35
|
+
};
|
|
36
|
+
layout: (make: MASConstraintMaker, view: UIView) => void;
|
|
37
|
+
events?: {
|
|
38
|
+
changed?: (sender: PageControl, currentPage: number) => void;
|
|
39
|
+
}
|
|
40
|
+
}) {
|
|
41
|
+
const {
|
|
42
|
+
numberOfPages = 3,
|
|
43
|
+
currentPage = 0,
|
|
44
|
+
pageIndicatorTintColor,
|
|
45
|
+
currentPageIndicatorTintColor,
|
|
46
|
+
...restProps
|
|
47
|
+
} = props;
|
|
48
|
+
const { changed, ...restEvents } = events;
|
|
49
|
+
super({ props: restProps, layout, events: restEvents });
|
|
50
|
+
this._numberOfPages = numberOfPages;
|
|
51
|
+
this._currentPage = currentPage;
|
|
52
|
+
this._pageIndicatorTintColor = pageIndicatorTintColor;
|
|
53
|
+
this._currentPageIndicatorTintColor = currentPageIndicatorTintColor;
|
|
54
|
+
this._changed = changed;
|
|
55
|
+
this._pageControl = this._createPageControl();
|
|
56
|
+
if (this._props) this._props.view = this._pageControl;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_createPageControl() {
|
|
60
|
+
const pageControl = $objc("UIPageControl").$new();
|
|
61
|
+
pageControl.$setNumberOfPages(this._numberOfPages);
|
|
62
|
+
pageControl.$setCurrentPage(this._currentPage);
|
|
63
|
+
if (this._pageIndicatorTintColor)
|
|
64
|
+
pageControl.$setPageIndicatorTintColor(
|
|
65
|
+
this._pageIndicatorTintColor.ocValue()
|
|
66
|
+
);
|
|
67
|
+
if (this._currentPageIndicatorTintColor)
|
|
68
|
+
pageControl.$setCurrentPageIndicatorTintColor(
|
|
69
|
+
this._currentPageIndicatorTintColor.ocValue()
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
pageControl.$addEventHandler({
|
|
73
|
+
events: $UIEvent.valueChanged,
|
|
74
|
+
handler: $block("void", () => {
|
|
75
|
+
const currentPage = pageControl.$currentPage();
|
|
76
|
+
this._currentPage = currentPage
|
|
77
|
+
if (this._changed) this._changed(this, currentPage);
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
return pageControl;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get currentPage() {
|
|
84
|
+
return this._currentPage;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
set currentPage(num) {
|
|
88
|
+
this._currentPage = num
|
|
89
|
+
if (this._pageControl) this._pageControl.$setCurrentPage(num);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # CView PageViewer TitleBar
|
|
3
|
+
*
|
|
4
|
+
* props:
|
|
5
|
+
*
|
|
6
|
+
* - 只写 items: string[]
|
|
7
|
+
* - 读写 index: number
|
|
8
|
+
* - 只写 selectedItemColor
|
|
9
|
+
* - 只写 defaultItemColor
|
|
10
|
+
*
|
|
11
|
+
* events:
|
|
12
|
+
*
|
|
13
|
+
* - changed: (cview, index) => void 在点击变更 index 的时候回调
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Base } from './base';
|
|
17
|
+
import { ContentView, Label, Stack } from "./single-views";
|
|
18
|
+
//const { getTextWidth } = require("cview-util-ui");
|
|
19
|
+
|
|
20
|
+
function weightedAverageColors(c0: UIColor, c1: UIColor, w: number) {
|
|
21
|
+
const red = c0.components.red * w + c1.components.red * (1 - w);
|
|
22
|
+
const green = c0.components.green * w + c1.components.green * (1 - w);
|
|
23
|
+
const blue = c0.components.blue * w + c1.components.blue * (1 - w);
|
|
24
|
+
return $rgb(red, green, blue);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PageViewerTitleBarProps {
|
|
28
|
+
items: string[];
|
|
29
|
+
index?: number;
|
|
30
|
+
selectedItemColor?: UIColor;
|
|
31
|
+
defaultItemColor?: UIColor;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface PageViewerTitleBarEvents extends UiTypes.BaseViewEvents {
|
|
35
|
+
changed?: (cview: PageViewerTitleBar, index: number) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class PageViewerTitleBar extends Base<UIView, UiTypes.ViewOptions> {
|
|
39
|
+
private _props: Required<PageViewerTitleBarProps>;
|
|
40
|
+
private _floatedIndex: number;
|
|
41
|
+
private _lineStartLocationPercentage: number;
|
|
42
|
+
labels: Label[];
|
|
43
|
+
stack: Stack;
|
|
44
|
+
placeholderView: ContentView;
|
|
45
|
+
line: ContentView;
|
|
46
|
+
|
|
47
|
+
_defineView: () => UiTypes.ViewOptions;
|
|
48
|
+
|
|
49
|
+
constructor({ props, layout, events = {} }: {
|
|
50
|
+
props: PageViewerTitleBarProps;
|
|
51
|
+
layout: (make: MASConstraintMaker, view: UIView) => void;
|
|
52
|
+
events: PageViewerTitleBarEvents;
|
|
53
|
+
}) {
|
|
54
|
+
super();
|
|
55
|
+
this._props = {
|
|
56
|
+
index: 0,
|
|
57
|
+
selectedItemColor: $color("systemLink"),
|
|
58
|
+
defaultItemColor: $color("secondaryText"),
|
|
59
|
+
...props
|
|
60
|
+
};
|
|
61
|
+
const { changed, ...restEvents } = events;
|
|
62
|
+
this._floatedIndex = this._props.index;
|
|
63
|
+
this._lineStartLocationPercentage = this._floatedIndex / this._props.items.length;
|
|
64
|
+
this.labels = this._props.items.map((n, i) => {
|
|
65
|
+
return new Label({
|
|
66
|
+
props: {
|
|
67
|
+
text: n,
|
|
68
|
+
font: $font("bold", 17),
|
|
69
|
+
textColor:
|
|
70
|
+
i === this.index
|
|
71
|
+
? this._props.selectedItemColor
|
|
72
|
+
: this._props.defaultItemColor,
|
|
73
|
+
align: $align.center,
|
|
74
|
+
userInteractionEnabled: true
|
|
75
|
+
},
|
|
76
|
+
events: {
|
|
77
|
+
tapped: sender => {
|
|
78
|
+
this.index = i;
|
|
79
|
+
if (changed) changed(this, i);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
this.stack = new Stack({
|
|
85
|
+
props: {
|
|
86
|
+
axis: $stackViewAxis.horizontal,
|
|
87
|
+
distribution: $stackViewDistribution.fillEqually,
|
|
88
|
+
stack: {
|
|
89
|
+
views: this.labels.map(n => n.definition)
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
layout: $layout.fill
|
|
93
|
+
});
|
|
94
|
+
this.placeholderView = new ContentView({
|
|
95
|
+
props: {
|
|
96
|
+
bgcolor: $color("clear")
|
|
97
|
+
},
|
|
98
|
+
layout: (make, view) => {
|
|
99
|
+
make.left.bottom.inset(0);
|
|
100
|
+
make.width.equalTo(view.super).multipliedBy(this._floatedIndex / this._props.items.length);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
this.line = new ContentView({
|
|
104
|
+
props: {
|
|
105
|
+
bgcolor: this._props.selectedItemColor
|
|
106
|
+
},
|
|
107
|
+
layout: (make, view) => {
|
|
108
|
+
make.height.equalTo(4);
|
|
109
|
+
make.width.equalTo(view.super).dividedBy(this._props.items.length);
|
|
110
|
+
make.bottom.inset(0);
|
|
111
|
+
make.left.equalTo(view.prev.right);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
this._defineView = () => {
|
|
115
|
+
return {
|
|
116
|
+
type: "view",
|
|
117
|
+
props: {
|
|
118
|
+
id: this.id
|
|
119
|
+
},
|
|
120
|
+
layout,
|
|
121
|
+
events: restEvents,
|
|
122
|
+
views: [
|
|
123
|
+
this.stack.definition,
|
|
124
|
+
this.placeholderView.definition,
|
|
125
|
+
this.line.definition
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get lineStartLocationPercentage() {
|
|
132
|
+
return this._lineStartLocationPercentage;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
set lineStartLocationPercentage(percent) {
|
|
136
|
+
this._lineStartLocationPercentage = percent;
|
|
137
|
+
this.placeholderView.view.remakeLayout((make, view) => {
|
|
138
|
+
make.left.bottom.inset(0);
|
|
139
|
+
make.width.equalTo(view.super).multipliedBy(percent);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
get floatedIndex() {
|
|
144
|
+
return this._floatedIndex;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
set floatedIndex(floatedIndex) {
|
|
148
|
+
this._floatedIndex = floatedIndex;
|
|
149
|
+
this.lineStartLocationPercentage = floatedIndex / this._props.items.length;
|
|
150
|
+
this.labels.forEach((n, i) => {
|
|
151
|
+
if (Math.abs(floatedIndex - i) < 1) {
|
|
152
|
+
n.view.textColor = weightedAverageColors(
|
|
153
|
+
this._props.selectedItemColor,
|
|
154
|
+
this._props.defaultItemColor,
|
|
155
|
+
floatedIndex - i > 0 ? 1 - (floatedIndex - i) : 1 - (i - floatedIndex)
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
n.view.textColor = this._props.defaultItemColor;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
get index() {
|
|
164
|
+
return this._props.index;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
set index(index) {
|
|
168
|
+
this._props.index = index;
|
|
169
|
+
}
|
|
170
|
+
}
|