mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-19 23:28:50 +08:00
fce92a2dc8
* feat(dom): add MoveRenderNode * feat(dom): Implement the MoveRenderNode interface * feat(dom): adjust layer optimization algorithm * feat(ios): support moving nodes for ios * feat(ios): support moving nodes for ios * feat(ios): support moving views for ios * feat(android): support move child node * feat(android): support move child node * fix(android): virtual node move should mark dirty * feat(voltron): support moving views for voltron * fix(android): add update node when move * feat(voltron): modify private code syntax * feat(voltron): update voltron recombine test --------- Co-authored-by: luomy <ozonelmy@163.com> Co-authored-by: maxli <maxli@tencent.com> Co-authored-by: henryjin0511 <443836568@qq.com> Co-authored-by: OpenHippy <124017524+open-hippy@users.noreply.github.com>
570 lines
14 KiB
Dart
570 lines
14 KiB
Dart
//
|
|
// Tencent is pleased to support the open source community by making
|
|
// Hippy available.
|
|
//
|
|
// Copyright (C) 2022 THL A29 Limited, a Tencent company.
|
|
// All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:voltron_renderer/render.dart';
|
|
|
|
import '../common.dart';
|
|
import '../controller.dart';
|
|
import '../style.dart';
|
|
import '../util.dart';
|
|
import '../viewmodel.dart';
|
|
import '../widget.dart';
|
|
|
|
class RootRenderNode extends RenderNode {
|
|
RootRenderNode(
|
|
int id,
|
|
String className,
|
|
RenderTree root,
|
|
ControllerManager controllerManager,
|
|
VoltronMap? props,
|
|
) : super(id, className, root, controllerManager, props);
|
|
|
|
@override
|
|
RenderViewModel createRenderViewModel(RenderContext context) {
|
|
return RootRenderViewModel(id, rootId, name, context, context.getInstance(id));
|
|
}
|
|
|
|
@override
|
|
void addEvent(Set<String> eventNameList) {
|
|
super.addEvent(eventNameList);
|
|
for (var event in eventNameList) {
|
|
if (event == ChoreographerUtil.kDoFrame.toLowerCase()) {
|
|
ChoreographerUtil.registerDoFrameListener(renderContext.engineId, rootId);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void removeEvent(Set<String> eventNameList) {
|
|
super.removeEvent(eventNameList);
|
|
for (var event in eventNameList) {
|
|
if (event == ChoreographerUtil.kDoFrame.toLowerCase()) {
|
|
ChoreographerUtil.unregisterDoFrameListener(renderContext.engineId, rootId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class RootRenderViewModel extends GroupViewModel {
|
|
final RootWidgetViewModel? _rootWidgetViewModel;
|
|
|
|
RootRenderViewModel(
|
|
int id,
|
|
int instanceId,
|
|
String className,
|
|
RenderContext context,
|
|
this._rootWidgetViewModel,
|
|
) : super(id, instanceId, className, context);
|
|
|
|
@override
|
|
void update() {
|
|
_rootWidgetViewModel?.notifyChange();
|
|
}
|
|
}
|
|
|
|
class RenderNode extends StyleNode {
|
|
// 唯一标识
|
|
final int _id;
|
|
|
|
/// 布局属性
|
|
double _x = 0;
|
|
double _y = 0;
|
|
double _width = 0;
|
|
double _height = 0;
|
|
|
|
/// 基础参数
|
|
final String _className;
|
|
final VoltronMap? _props;
|
|
VoltronMap? _propToUpdate;
|
|
|
|
/// 额外参数
|
|
Object? _extra;
|
|
Object? _extraToUpdate;
|
|
|
|
/// 树结构相关
|
|
final RenderTree _root;
|
|
RenderNode? _parent;
|
|
final List<RenderNode> _children = [];
|
|
final List<RenderNode> _childrenPendingList = [];
|
|
final List<MoveHolder> _moveHolders = [];
|
|
final Set<EventHolder> _eventHolders = {};
|
|
final Set<int> _deleteIds = {};
|
|
RenderViewModel? _viewModel;
|
|
|
|
/// 更新相关属性
|
|
|
|
final List<UIFunction> _uiFunction = [];
|
|
|
|
bool _hasPropsNeedToApply = true;
|
|
|
|
/// 外部依赖
|
|
|
|
final ControllerManager _controllerManager;
|
|
|
|
/// 控制值
|
|
bool _hasUpdateLayout = false;
|
|
bool isDelete = false;
|
|
bool _isRootHasDelete = false;
|
|
bool _isLazyLoad = false;
|
|
bool _notifyManageChildren = false;
|
|
|
|
int get indexFromParent => _parent?._children.indexOf(this) ?? 0;
|
|
|
|
RenderContext get renderContext => _controllerManager.context;
|
|
|
|
@override
|
|
int get id => _id;
|
|
|
|
int get rootId => _root.id;
|
|
|
|
@override
|
|
String get name => _className;
|
|
|
|
RenderNode? get parent => _parent;
|
|
|
|
RenderTree get root => _root;
|
|
|
|
@override
|
|
double get layoutX => _x;
|
|
|
|
@override
|
|
double get layoutY => _y;
|
|
|
|
@override
|
|
double get layoutWidth => _width;
|
|
|
|
@override
|
|
double get layoutHeight => _height;
|
|
|
|
RenderViewModel? get viewModel => _viewModel;
|
|
|
|
bool get shouldCreateView => !_isLazyLoad && _viewModel == null;
|
|
|
|
bool get isLazyLoad => _isLazyLoad;
|
|
|
|
int get childCount => _children.length;
|
|
|
|
bool get hasCustomLayout => false;
|
|
|
|
List<RenderNode> get children => _children;
|
|
|
|
VoltronViewController findController() {
|
|
var controller = _controllerManager.findController(name);
|
|
assert(controller != null);
|
|
return controller!;
|
|
}
|
|
|
|
set isLazyLoad(bool isLazy) {
|
|
setLazy(this, isLazy);
|
|
}
|
|
|
|
void setLazy(RenderNode node, bool isLazy) {
|
|
node._isLazyLoad = isLazy;
|
|
for (var childNode in node._children) {
|
|
setLazy(childNode, isLazy);
|
|
}
|
|
}
|
|
|
|
VoltronMap? get props => _props;
|
|
|
|
bool get isRoot => name == NodeProps.kRootNode;
|
|
|
|
RenderNode(
|
|
this._id,
|
|
this._className,
|
|
this._root,
|
|
this._controllerManager,
|
|
this._props, [
|
|
this._isLazyLoad = false,
|
|
this._parent,
|
|
]) : super(_className);
|
|
|
|
@override
|
|
String toString() {
|
|
var buffer = StringBuffer();
|
|
buffer.write(printChild(this));
|
|
return buffer.toString();
|
|
}
|
|
|
|
String printChild(RenderNode renderNode) {
|
|
var buffer = StringBuffer();
|
|
buffer.write(" [Id:${renderNode.id}${renderNode.name}");
|
|
for (var child in renderNode._children) {
|
|
buffer.write(printChild(child));
|
|
}
|
|
buffer.write("]");
|
|
return buffer.toString();
|
|
}
|
|
|
|
void addDeleteId(int id) {
|
|
if (_shouldUpdateView()) {
|
|
_deleteIds.add(id);
|
|
}
|
|
}
|
|
|
|
RenderViewModel get renderViewModel {
|
|
_viewModel ??= createRenderViewModel(_controllerManager.context);
|
|
return _viewModel!;
|
|
}
|
|
|
|
RenderBox? get renderBox {
|
|
return renderViewModel.currentContext?.findRenderObject() as RenderBox?;
|
|
}
|
|
|
|
BoundingClientRect? get boundingClientRect {
|
|
return renderViewModel.boundingClientRect;
|
|
}
|
|
|
|
bool checkRenderViewModel() {
|
|
if (_viewModel == null) {
|
|
_viewModel = createRenderViewModel(_controllerManager.context);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void createViewModelRecursive() {
|
|
for (var renderNode in _children) {
|
|
renderNode.createViewModelRecursive();
|
|
}
|
|
|
|
_hasUpdateLayout = true;
|
|
_extraToUpdate = _extra;
|
|
}
|
|
|
|
void createViewModel() {
|
|
if (_deleteIds.isNotEmpty) {
|
|
for (final deleteId in _deleteIds) {
|
|
_controllerManager.deleteChild(
|
|
_viewModel,
|
|
_viewModel?.childFromId(deleteId),
|
|
);
|
|
}
|
|
_deleteIds.clear();
|
|
_notifyManageChildren = true;
|
|
}
|
|
|
|
if (isDelete && isRoot && !_isRootHasDelete) {
|
|
_isRootHasDelete = true;
|
|
_controllerManager.deleteRoot(_root.id, _root);
|
|
}
|
|
|
|
var parent = _parent;
|
|
if (shouldCreateView && !isRoot && parent != null) {
|
|
_propToUpdate = null;
|
|
parent.addChildToPendingList(this);
|
|
return _controllerManager.createViewModel(this, props);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bool _shouldUpdateView() {
|
|
return _controllerManager.hasNode(this);
|
|
}
|
|
|
|
void deleteAllChild() {
|
|
if (_children.isNotEmpty) {
|
|
for (var childNode in _children) {
|
|
childNode.deleteAllChild();
|
|
_root.unregisterNode(childNode);
|
|
}
|
|
_children.clear();
|
|
}
|
|
}
|
|
|
|
void deleteChild(RenderNode node, {bool needRemoveChild = true}) {
|
|
if (_children.contains(node)) {
|
|
if (needRemoveChild) {
|
|
node.deleteAllChild();
|
|
}
|
|
_children.remove(node);
|
|
_root.unregisterNode(node);
|
|
}
|
|
}
|
|
|
|
RenderViewModel createRenderViewModel(RenderContext context) {
|
|
return findController().createRenderViewModel(this, context);
|
|
}
|
|
|
|
void updateRecursive() {
|
|
update();
|
|
for (var node in _children) {
|
|
node.updateRecursive();
|
|
}
|
|
}
|
|
|
|
void addChild(RenderNode? node, int index) {
|
|
if (node != null) {
|
|
_notifyManageChildren = true;
|
|
if (index < 0 || index >= _children.length) {
|
|
_children.add(node);
|
|
} else {
|
|
_children.insert(index, node);
|
|
}
|
|
|
|
node._parent = this;
|
|
root.registerNode(node);
|
|
}
|
|
}
|
|
|
|
void addChildToPendingList(RenderNode renderNode) {
|
|
_childrenPendingList.add(renderNode);
|
|
}
|
|
|
|
void moveChild(RenderNode? node, int index) {
|
|
if (node != null) {
|
|
_notifyManageChildren = true;
|
|
if (_children.contains(node)) {
|
|
_children.remove(node);
|
|
_children.insert(index, node);
|
|
}
|
|
}
|
|
}
|
|
|
|
void removeChild(RenderNode? node, {bool needRemoveChild = true}) {
|
|
if (node != null) {
|
|
_notifyManageChildren = true;
|
|
deleteChild(node, needRemoveChild: needRemoveChild);
|
|
}
|
|
}
|
|
|
|
void addEvent(Set<String> eventNameList) {
|
|
_eventHolders.addAll(eventNameList.map(EventHolder.new));
|
|
}
|
|
|
|
void removeEvent(Set<String> eventNameList) {
|
|
_eventHolders.addAll(eventNameList.map((e) => EventHolder(e, isAdd: false)));
|
|
}
|
|
|
|
RenderNode? getChildAt(int index) {
|
|
if (0 <= index && index < childCount) {
|
|
return _children[index];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
void batchComplete() {
|
|
if (!_isLazyLoad && !isDelete) {
|
|
_controllerManager.batchComplete(this);
|
|
}
|
|
}
|
|
|
|
void update() {
|
|
LogUtils.dRenderNode(
|
|
"ID:$id, updateStyle start, shouldUpdateView: ${_shouldUpdateView()}",
|
|
);
|
|
|
|
if (_shouldUpdateView()) {
|
|
if (_childrenPendingList.isNotEmpty) {
|
|
_childrenPendingList.sort((o1, o2) {
|
|
return o1.indexFromParent.compareTo(o2.indexFromParent);
|
|
});
|
|
|
|
for (var i = 0; i < _childrenPendingList.length; i++) {
|
|
var renderNode = _childrenPendingList[i];
|
|
_controllerManager.addChild(this, renderNode, renderNode.indexFromParent);
|
|
}
|
|
_childrenPendingList.clear();
|
|
_notifyManageChildren = true;
|
|
}
|
|
|
|
if (_propToUpdate != null) {
|
|
_controllerManager.updateWidget(this, _propToUpdate);
|
|
_propToUpdate = null;
|
|
_hasPropsNeedToApply = true;
|
|
}
|
|
|
|
if (_moveHolders.isNotEmpty) {
|
|
for (var moveHolder in _moveHolders) {
|
|
moveHolder._moveRenders.sort((o1, o2) {
|
|
return o1.indexFromParent.compareTo(o2.indexFromParent);
|
|
});
|
|
|
|
for (var node in moveHolder._moveRenders) {
|
|
_controllerManager.move(node, moveHolder._moveToNode, node.indexFromParent);
|
|
}
|
|
}
|
|
_moveHolders.clear();
|
|
final idIndexList = this.children.map((e) => e.id).toList();
|
|
/// 元素排序
|
|
_viewModel?.sortChildrenIndex(idIndexList);
|
|
}
|
|
/// 元素ZIndex排序
|
|
_viewModel?.sortChildrenZIndex();
|
|
|
|
|
|
LogUtils.dRenderNode(
|
|
"ID:$id, update style, update layout start, hasUpdateLayout:$_hasUpdateLayout",
|
|
);
|
|
if (_hasUpdateLayout && !isRoot) {
|
|
_controllerManager.updateLayout(this);
|
|
LogUtils.dRenderNode(
|
|
"ID:$id, update style, update layout end, newLayout:[$layoutX, $layoutY, $_width, $_height]",
|
|
);
|
|
_hasUpdateLayout = false;
|
|
}
|
|
|
|
var extraToUpdate = _extraToUpdate;
|
|
if (extraToUpdate != null) {
|
|
_controllerManager.updateExtra(this, extraToUpdate);
|
|
_extraToUpdate = null;
|
|
}
|
|
|
|
if (_eventHolders.isNotEmpty) {
|
|
_controllerManager.updateEvents(this, _eventHolders);
|
|
_eventHolders.clear();
|
|
}
|
|
|
|
if (_uiFunction.isNotEmpty) {
|
|
for (var i = 0; i < _uiFunction.length; i++) {
|
|
var uiFunction = _uiFunction[i];
|
|
_controllerManager.dispatchUIFunction(
|
|
rootId,
|
|
id,
|
|
name,
|
|
uiFunction._functionName,
|
|
uiFunction._params,
|
|
uiFunction._promise,
|
|
);
|
|
}
|
|
_uiFunction.clear();
|
|
}
|
|
|
|
if (_notifyManageChildren) {
|
|
manageChildrenComplete();
|
|
_notifyManageChildren = false;
|
|
}
|
|
}
|
|
LogUtils.dRenderNode("ID:$id, update style end");
|
|
}
|
|
|
|
void applyProps() {
|
|
if (_hasPropsNeedToApply) {
|
|
_controllerManager.applyProps(this);
|
|
_hasPropsNeedToApply = false;
|
|
}
|
|
}
|
|
|
|
void updateRender() {
|
|
renderViewModel.update();
|
|
}
|
|
|
|
void updateLayout(double x, double y, double w, double h) {
|
|
_x = x;
|
|
_y = y;
|
|
_width = w;
|
|
_height = h;
|
|
_hasUpdateLayout = true;
|
|
}
|
|
|
|
void _updateStyle(VoltronMap map) {
|
|
DomUpdateUtil.updateStyle(this, map);
|
|
}
|
|
|
|
void updateNode(VoltronMap map) {
|
|
var propToUpdate = _propToUpdate;
|
|
if (propToUpdate != null) {
|
|
//mProps do not syc to UI
|
|
var paramsMap = combineProps(propToUpdate, map);
|
|
if (paramsMap.size() > 0) {
|
|
for (var key in paramsMap.keySet()) {
|
|
if (key == NodeProps.kStyle) {
|
|
var styles = paramsMap.get<VoltronMap>(key);
|
|
if (styles != null) {
|
|
var stylesToUpdate = propToUpdate.get<VoltronMap>(key) ?? VoltronMap();
|
|
for (String styleKey in styles.keySet()) {
|
|
stylesToUpdate.push(styleKey, styles.get(styleKey));
|
|
}
|
|
|
|
_updateStyle(stylesToUpdate);
|
|
}
|
|
} else {
|
|
propToUpdate.push(key, paramsMap.get(key));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
var curProps = _props;
|
|
if (curProps != null) {
|
|
_propToUpdate = combineProps(curProps, map);
|
|
}
|
|
}
|
|
}
|
|
|
|
void updateExtra(Object object) {
|
|
_extra = object;
|
|
_extraToUpdate = object;
|
|
}
|
|
|
|
void dispatchUIFunction(
|
|
String funcName,
|
|
VoltronArray array,
|
|
Promise promise,
|
|
) {
|
|
_uiFunction.add(UIFunction(funcName, array, promise));
|
|
}
|
|
|
|
void manageChildrenComplete() {
|
|
if (!isLazyLoad && !isDelete) {
|
|
_controllerManager.manageChildComplete(this);
|
|
}
|
|
}
|
|
|
|
void move(List<RenderNode> moveRenders, RenderNode moveToRender) {
|
|
if (_shouldUpdateView()) {
|
|
_moveHolders.add(MoveHolder(moveRenders, moveToRender));
|
|
}
|
|
}
|
|
}
|
|
|
|
class UIFunction {
|
|
final String _functionName;
|
|
final VoltronArray _params;
|
|
final Promise _promise;
|
|
|
|
const UIFunction(this._functionName, this._params, this._promise);
|
|
}
|
|
|
|
class MoveHolder {
|
|
final List<RenderNode> _moveRenders;
|
|
final RenderNode _moveToNode;
|
|
|
|
MoveHolder(this._moveRenders, this._moveToNode);
|
|
}
|
|
|
|
@immutable
|
|
class EventHolder {
|
|
final String eventName;
|
|
final bool isAdd;
|
|
|
|
const EventHolder(this.eventName, {this.isAdd = true});
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
return other is EventHolder && eventName == other.eventName && isAdd == other.isAdd;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => super.hashCode | eventName.hashCode | isAdd.hashCode;
|
|
}
|