-
Notifications
You must be signed in to change notification settings - Fork 0
/
PopupLayout.cs
379 lines (318 loc) · 12.4 KB
/
PopupLayout.cs
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
using SkiaSharp;
using SkiaSharp.Views.Forms;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
using System;
namespace BiClubApp.Controls
{
[ContentProperty("Content")]
public class PopupLayout : Grid
{
// 包含弹出层的绝对布局
private AbsoluteLayout _absoluteLayout = new AbsoluteLayout() { InputTransparent = true };
// 弹出层的背景绘图层
private SKCanvasView _canvasView = new SKCanvasView { IsVisible = false, Opacity = 1 };
// 弹出层的背景的大小(绘气泡层需要)
private Rectangle _rect;
//箭头初始位置
private double _drawX;
//是否画气泡(保证ios、android兼容)
private bool _isDrawTriangle;
//箭头方向
private bool _isArrowUp;
// 多弹出窗列表
public ObservableCollection<PopupItem> PopupItems { get; } = new ObservableCollection<PopupItem>();
//区域外tap触发
public event EventHandler HideEvent;
// 默认子元素属性
public View Content
{
set
{
//插入子元素
Children.Add(value);
//同时插入绝对布局,保证绝对布局在子元素之后
//图层就在子元素之上
Children.Add(_absoluteLayout);
}
}
public PopupLayout()
{
// 点击手势
var tap = new TapGestureRecognizer();
// 点击手势响应事件
tap.Tapped += async (sender, e) =>
{
HideEvent?.Invoke(sender, e);
await HidePopup();
};
// 绝对布局添加点击手势
_canvasView.GestureRecognizers.Add(tap);
// 画布定位方式(大小按比例)
_canvasView.SetValue(AbsoluteLayout.LayoutFlagsProperty, AbsoluteLayoutFlags.SizeProportional);
// 画布定位
_canvasView.SetValue(AbsoluteLayout.LayoutBoundsProperty, new Rectangle(0, 0, 1, 1));
// 将画布添加到绝对布局中
_absoluteLayout.Children.Add(_canvasView);
// 弹窗列表改变事件
PopupItems.CollectionChanged += (object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) =>
{
var items = (ObservableCollection<PopupItem>)sender;
foreach (var item in items)
{
var view = item.Content;
if (!_absoluteLayout.Children.Contains(view))
{
// 设置不可见
view.IsVisible = false;
// 设置透明度0
view.Opacity = 1;
// 弹窗定位方式(绝对定位)
view.SetValue(AbsoluteLayout.LayoutFlagsProperty, AbsoluteLayoutFlags.None);
// 将弹窗添加进绝对布局
_absoluteLayout.Children.Add(view);
}
}
};
// 画布绘图事件
_canvasView.PaintSurface += (sender, e) =>
{
// 获取画布实例
var canvas = e.Surface.Canvas;
// 清除画布
canvas.Clear();
if (!_isDrawTriangle)
{
return;
}
// 计算画布上的画图区域
var rect = new Rectangle(_rect.X / Width * e.Info.Width,
_rect.Y / Height * e.Info.Height,
_rect.Width / Width * e.Info.Width,
_rect.Height / Height * e.Info.Height);
_drawX = _drawX / Width * e.Info.Width;
// 实例化刷子
var paint = new SKPaint { Style = SKPaintStyle.StrokeAndFill, Color = SKColors.Gainsboro, IsAntialias = true };
// 实例化路径
var path = new SKPath();
// 箭头方向向上
if (_isArrowUp)
{
// 绘制路径
path.MoveTo((float)(_drawX + 15), (float)(rect.Y));
path.LineTo((float)(_drawX + 30), (float)(rect.Y - 20));
path.LineTo((float)(_drawX + 45), (float)(rect.Y));
path.LineTo((float)(rect.X + rect.Width), (float)(rect.Y));
path.LineTo((float)(rect.X + rect.Width), (float)(rect.Y + rect.Height));
path.LineTo((float)(rect.X), (float)(rect.Y + rect.Height));
path.LineTo((float)(rect.X), (float)(rect.Y));
path.Close();
}
else
{
// 绘制路径
path.MoveTo((float)(_drawX + 15), (float)(rect.Y + rect.Height));
path.LineTo((float)(_drawX + 30), (float)(rect.Y + rect.Height + 20));
path.LineTo((float)(_drawX + 45), (float)(rect.Y + rect.Height));
path.LineTo((float)(rect.X + rect.Width), (float)(rect.Y + rect.Height));
path.LineTo((float)(rect.X + rect.Width), (float)(rect.Y));
path.LineTo((float)(rect.X), (float)(rect.Y));
path.LineTo((float)(rect.X), (float)(rect.Y + rect.Height));
path.Close();
}
// 按路径绘图
canvas.DrawPath(path, paint);
};
}
/// <summary>
/// 弹出
/// </summary>
/// <param name="popupItem">窗体实例</param>
/// <param name="rect">矩形区域</param>
/// <returns>Task</returns>
private async Task ShowPopup(PopupItem popupItem, Rectangle rect)
{
//重绘
_rect = rect;
_canvasView.InvalidateSurface();
// 响应不传递
_absoluteLayout.InputTransparent = false;
// 画布可见
_canvasView.IsVisible = true;
// 获取弹窗的view对象
var popup = ((AbsoluteLayout)Children[1]).Children.First(c => c == popupItem.Content);
// 向内偏移2
var localRect = new Rectangle(rect.X + 2, rect.Y + 2, rect.Width - 4, rect.Height - 4);
// 设置弹窗位置
popup.SetValue(AbsoluteLayout.LayoutBoundsProperty, localRect);
// 弹窗对象可见
//popupItem.IsShown = true;
// 弹窗view可见
popup.IsVisible = true;
// 显示动画
await Task.WhenAll(
popup.FadeTo(1, 200, Easing.CubicInOut),
_canvasView.FadeTo(1, 200, Easing.CubicInOut)
).ConfigureAwait(false);
}
/// <summary>
/// 弹出
/// </summary>
/// <param name="popupItem">窗体实例</param>
/// <param name="clickElem">被点击元素</param>
/// <param name="width">弹窗宽度</param>
/// <param name="height">弹窗高度</param>
/// <returns></returns>
public async Task ShowPopup(PopupItem popupItem, VisualElement clickElem, double width = 100, double height = 200, bool isProportional = false, bool isDrawTriangle = false)
{
if (isProportional)
{
width = width > 1 ? 1 : width;
height = height > 1 ? 1 : height;
}
_isDrawTriangle = isDrawTriangle;
// 获取点击元素相对X
var x = clickElem.X;
// 获取点击元素相对Y
var y = clickElem.Y;
// 获取点击元素高
var h = clickElem.Height;
// 获取点击元素父元素
var parent = clickElem.Parent as VisualElement;
// 如果父元素不是this布局对象
while (parent != this)
{
// X加上父元素的相对偏移量
x += parent.X;
// Y加上父元素的相对偏移量
y += parent.Y;
// 获取父元素的父元素
parent = parent.Parent as VisualElement;
}
_drawX = x;
// 加上元素高度后为弹窗起始y
if (Children[0].GetType() == typeof(ScrollView))
{
var scrollView = Children[0] as ScrollView;
var scrollY = scrollView.ScrollY;
y = y + h - scrollY;
}
else
{
y = y + h;
}
if (isProportional)
{
width = Width * width;
if ((y - h / 2) < (Height / 2))
{
height = (Height - y) * height;
}
else
{
height = (y - h) * height;
}
}
// 弹窗宽度大于屏幕宽度
if (width > Width)
{
// 起始x位置设置为0
x = 0;
// 宽度设置为屏幕宽度
width = Width;
}
else
{
// 如果x加上弹窗宽度超出屏幕
if (x + width > Width)
{
x = Width - width;
}
}
//var test = absoluteLayout.Height;
// 点击元素位置在屏幕位置一半以上
if ((y - h / 2) < (Height / 2))
{
// 弹窗高度大于下方剩余屏幕高度
if (height > Height - y)
{
height = Height - y;
}
// 箭头向上
_isArrowUp = true;
}
else
{
// 弹窗高度大于上方剩余屏幕高度
if (height > y - h)
{
height = y - h;
}
// 设置y起始位置为点击元素起始位置往上height高度处
y = y - h - height;
// 箭头向下
_isArrowUp = false;
}
// 当获取到绝对X、Y后,弹窗
await ShowPopup(popupItem, new Rectangle(x, y, width, height));
}
/// <summary>
/// 隐藏窗体
/// </summary>
/// <returns></returns>
public async Task HidePopup()
{
// 绝对布局响应传递
_absoluteLayout.InputTransparent = true;
// 获取绝对布局的所有子元素
var children = ((AbsoluteLayout)Children[1]).Children.ToList();
// 设置所有子元素不可见
children.ForEach(d => d.IsVisible = false);
// 等待所有子元素的渐出动画
await Task.WhenAll(children.Select(d => d.FadeTo(0, 200, Easing.CubicInOut)))
.ConfigureAwait(false);
// 设置所有弹窗对象不可见(没用)
//PopupItems.ToList().ForEach(d => d.IsShown = false);
}
public async Task PopUpOverBelow(object visualElement, PopupItem popupItem, float width, float height) {
_absoluteLayout.InputTransparent = false;
var popup = ((AbsoluteLayout)Children[1]).Children.First(c => c == popupItem.Content);
var v = visualElement as VisualElement;
var x = v.X;
var y = v.Y;
var w = v.Width;
var h = v.Height;
var ifPop = false;
while (true) {
v = v.Parent as VisualElement;
if (v != null) {
x += v.X;
y += v.Y;
if (v == this) {
ifPop = true;
break;
}
} else
break;
}
if (ifPop) {
popup.SetValue(AbsoluteLayout.LayoutBoundsProperty, new Rectangle(x, y + h, width, height));
//popupItem.IsShown = true;
popup.IsVisible = true;
await Task.WhenAll(
popup.FadeTo(1, 200, Easing.CubicInOut)
);
}
}
}
[ContentProperty("Content")]
public class PopupItem
{
public View Content
{
get; set;
}
}
}