Proxy view container

This commit is contained in:
vakrilov
2016-01-21 10:56:04 +02:00
parent 62d85b0dc0
commit eac95156d1
15 changed files with 265 additions and 119 deletions

View File

@ -726,6 +726,8 @@
<TypeScriptCompile Include="ui\builder\binding-builder.d.ts" />
<TypeScriptCompile Include="ui\builder\special-properties.d.ts" />
<TypeScriptCompile Include="ui\builder\special-properties.ts" />
<TypeScriptCompile Include="ui\view-container\view-container.ts" />
<TypeScriptCompile Include="ui\view-container\view-container.d.ts" />
<TypeScriptCompile Include="ui\styling\background.android.d.ts" />
<TypeScriptCompile Include="ui\styling\style-scope.d.ts" />
<TypeScriptCompile Include="ui\styling\style.d.ts" />
@ -2087,6 +2089,9 @@
</Content>
<Content Include="ui\package.json" />
<Content Include="tsconfig.json" />
<Content Include="ui\view-container\package.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="build\tslint.json" />

View File

@ -664,6 +664,8 @@
"ui/ui.ts",
"ui/utils.d.ts",
"ui/utils.ios.ts",
"ui/view-container/proxy-view-container.d.ts",
"ui/view-container/proxy-view-container.ts",
"ui/web-view/web-view-common.ts",
"ui/web-view/web-view.android.ts",
"ui/web-view/web-view.d.ts",

View File

@ -22,7 +22,7 @@ registerSpecialProperty("class", (instance: definition.View, propertyValue: stri
instance.className = propertyValue;
});
function getEventOrGestureName(name: string) : string {
function getEventOrGestureName(name: string): string {
return name.indexOf("on") === 0 ? name.substr(2, name.length - 2) : name;
}
@ -190,7 +190,7 @@ export class View extends ProxyObject implements definition.View {
observe(type: gestures.GestureTypes, callback: (args: gestures.GestureEventData) => void, thisArg?: any): void {
if (!this._gestureObservers[type]) {
this._gestureObservers[type] = [];
}
}
this._gestureObservers[type].push(gestures.observe(this, type, callback, thisArg));
}
@ -926,6 +926,26 @@ export class View extends ProxyObject implements definition.View {
//
}
_childIndexToNativeChildIndex(index?: number): number {
return index;
}
_getNativeViewsCount(): number {
return this._isAddedToNativeVisualTree ? 1 : 0;
}
_eachLayoutView(callback: (View) => void): void {
return callback(this);
}
_addToSuperview(superview: any, index?: number): boolean {
// IOS specific
return false;
}
_removeFromSuperview(): void {
// IOS specific
}
/**
* Core logic for adding a child view to this instance. Used by the framework to handle lifecycle events more centralized. Do not outside the UI Stack implementation.
* // TODO: Think whether we need the base Layout routine.
@ -954,7 +974,8 @@ export class View extends ProxyObject implements definition.View {
view.style._inheritStyleProperties();
if (!view._isAddedToNativeVisualTree) {
view._isAddedToNativeVisualTree = this._addViewToNativeVisualTree(view, atIndex);
var nativeIndex = this._childIndexToNativeChildIndex(atIndex);
view._isAddedToNativeVisualTree = this._addViewToNativeVisualTree(view, nativeIndex);
}
// TODO: Discuss this.

10
ui/core/view.d.ts vendored
View File

@ -445,7 +445,7 @@ declare module "ui/core/view" {
* Sets in-line CSS string as style.
* @param style - In-line CSS string.
*/
public setInlineStyle(style: string) : void;
public setInlineStyle(style: string): void;
public getGestureObservers(type: gestures.GestureTypes): Array<gestures.GesturesObserver>;
@ -497,6 +497,14 @@ declare module "ui/core/view" {
_removeView(view: View);
_context: any /* android.content.Context */;
_childIndexToNativeChildIndex(index?: number): number;
_getNativeViewsCount(): number;
_eachLayoutView(callback: (View) => void): void;
_addToSuperview(superview: any, index?: number): boolean;
_removeFromSuperview();
public _applyXmlAttribute(attribute: string, value: any): boolean;
// TODO: Implement logic for stripping these lines out

View File

@ -265,6 +265,28 @@ export class View extends viewCommon.View {
if (this._cachedFrame) {
this._setNativeViewFrame(this._nativeView, this._cachedFrame);
}
}
public _addToSuperview(superview: any, atIndex?: number): boolean {
if (superview && this._nativeView) {
var types = require("utils/types");
if (types.isNullOrUndefined(atIndex) || atIndex >= this._nativeView.subviews.count) {
superview.addSubview(this._nativeView);
} else {
superview.insertSubviewAtIndex(this._nativeView, atIndex);
}
return true;
}
return false;
}
public _removeFromSuperview() {
if (this._nativeView) {
this._nativeView.removeFromSuperview();
}
}
}
@ -290,30 +312,15 @@ export class CustomLayoutView extends View {
}
public _addViewToNativeVisualTree(child: View, atIndex: number): boolean {
super._addViewToNativeVisualTree(child);
super._addViewToNativeVisualTree(child, atIndex);
if (this._nativeView && child._nativeView) {
var types = require("utils/types");
if (types.isNullOrUndefined(atIndex) || atIndex >= this._nativeView.subviews.count) {
this._nativeView.addSubview(child._nativeView);
}
else {
this._nativeView.insertSubviewAtIndex(child._nativeView, atIndex);
}
return true;
}
return false;
return child._addToSuperview(this._nativeView, atIndex);
}
public _removeViewFromNativeVisualTree(child: View): void {
super._removeViewFromNativeVisualTree(child);
if (child._nativeView) {
child._nativeView.removeFromSuperview();
}
child._removeFromSuperview();
}
}

View File

@ -30,16 +30,11 @@ export class AbsoluteLayout extends common.AbsoluteLayout {
let childMeasureSpec = utils.layout.makeMeasureSpec(0, utils.layout.UNSPECIFIED);
let density = utils.layout.getDisplayDensity();
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
let childSize = View.measureChild(this, child, childMeasureSpec, childMeasureSpec);
measureWidth = Math.max(measureWidth, AbsoluteLayout.getLeft(child) * density + childSize.measuredWidth);
measureHeight = Math.max(measureHeight, AbsoluteLayout.getTop(child) * density + childSize.measuredHeight);
}
});
measureWidth += (this.paddingLeft + this.paddingRight) * density;
measureHeight += (this.paddingTop + this.paddingBottom) * density;
@ -57,12 +52,7 @@ export class AbsoluteLayout extends common.AbsoluteLayout {
super.onLayout(left, top, right, bottom);
let density = utils.layout.getDisplayDensity();
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty);
let childWidth = child.getMeasuredWidth();
@ -74,7 +64,7 @@ export class AbsoluteLayout extends common.AbsoluteLayout {
let childBottom = childTop + childHeight + (lp.topMargin + lp.bottomMargin) * density;
View.layoutChild(this, child, childLeft, childTop, childRight, childBottom);
}
});
AbsoluteLayout.restoreOriginalParams(this);
}

View File

@ -35,13 +35,8 @@ export class DockLayout extends common.DockLayout {
var childWidthMeasureSpec: number;
var childHeightMeasureSpec: number;
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
if (this.stretchLastChild && i === (count - 1)) {
this.eachLayoutChild((child, last) => {
if (this.stretchLastChild && last) {
childWidthMeasureSpec = utils.layout.makeMeasureSpec(remainingWidth, widthMode);
childHeightMeasureSpec = utils.layout.makeMeasureSpec(remainingHeight, heightMode);
}
@ -72,7 +67,7 @@ export class DockLayout extends common.DockLayout {
measureHeight = Math.max(measureHeight, tempHeight + childSize.measuredHeight);
break;
}
}
});
measureWidth += (this.paddingLeft + this.paddingRight) * density;
measureHeight += (this.paddingTop + this.paddingBottom) * density;
@ -100,19 +95,7 @@ export class DockLayout extends common.DockLayout {
var remainingWidth = Math.max(0, right - left - ((this.paddingLeft + this.paddingRight) * density));
var remainingHeight = Math.max(0, bottom - top - ((this.paddingTop + this.paddingBottom) * density));
var count = this.getChildrenCount();
var childToStretch = null;
if (count > 0 && this.stretchLastChild) {
count--;
childToStretch = this.getChildAt(count);
}
for (let i = 0; i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty);
let childWidth = child.getMeasuredWidth() + (lp.leftMargin + lp.rightMargin) * density;
@ -152,12 +135,12 @@ export class DockLayout extends common.DockLayout {
break;
}
View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childTop + childHeight);
}
if (childToStretch) {
View.layoutChild(this, childToStretch, x, y, x + remainingWidth, y + remainingHeight);
}
if (!last) {
View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childTop + childHeight);
} else {
View.layoutChild(this, child, x, y, x + remainingWidth, y + remainingHeight);
}
});
DockLayout.restoreOriginalParams(this);
}

View File

@ -145,16 +145,11 @@ export class GridLayout extends common.GridLayout {
this.helper.clearMeasureSpecs();
this.helper.init();
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
let measureSpecs = this.map.get(child);
this.updateMeasureSpecs(child, measureSpecs);
this.helper.addMeasureSpec(measureSpecs);
}
});
this.helper.measure();

View File

@ -50,6 +50,12 @@
*/
removeChildren(): void;
/**
* Calls the callback for each child that should be laid out.
* @param callback The callback
*/
eachLayoutChild(callback: (child: view.View, isLast: boolean) => void): void;
/**
* Iterates over children and changes their width and height to one calculated from percentage values.
*

View File

@ -1,4 +1,5 @@
import definition = require("ui/layouts/layout-base");
import types = require("utils/types");
import view = require("ui/core/view");
import dependencyObservable = require("ui/core/dependency-observable");
import proxy = require("ui/core/proxy");
@ -41,13 +42,13 @@ export class LayoutBase extends view.CustomLayoutView implements definition.Layo
public addChild(child: view.View): void {
// TODO: Do we need this method since we have the core logic in the View implementation?
this._addView(child);
this._subViews.push(child);
this._addView(child);
}
public insertChild(child: view.View, atIndex: number): void {
this._addView(child, atIndex);
this._subViews.splice(atIndex, 0, child);
this._addView(child, atIndex);
}
public removeChild(child: view.View): void {
@ -64,19 +65,6 @@ export class LayoutBase extends view.CustomLayoutView implements definition.Layo
}
}
public _eachChildView(callback: (child: view.View) => boolean): void {
var i;
var length = this._subViews.length;
var retVal: boolean;
for (i = 0; i < length; i++) {
retVal = callback(this._subViews[i]);
if (retVal === false) {
break;
}
}
}
get padding(): string {
return this.style.padding;
}
@ -127,6 +115,52 @@ export class LayoutBase extends view.CustomLayoutView implements definition.Layo
}
}
public _childIndexToNativeChildIndex(index?: number): number {
if (types.isUndefined(index)) {
return undefined;
}
var result = 0;
for (let i = 0; i < index && i < this._subViews.length; i++) {
result += this._subViews[i]._getNativeViewsCount();
}
return result;
}
public _eachChildView(callback: (child: view.View) => boolean): void {
var i;
var length = this._subViews.length;
var retVal: boolean;
for (i = 0; i < length; i++) {
retVal = callback(this._subViews[i]);
if (retVal === false) {
break;
}
}
}
public eachLayoutChild(callback: (child: view.View, isLast: boolean) => void): void {
var index = 0;
var lastChild: view.View = null;
this._eachChildView((cv) => {
cv._eachLayoutView((lv) => {
if (lastChild && lastChild._isVisible) {
callback(lastChild, false);
}
lastChild = lv;
});
return true;
});
if (lastChild && lastChild._isVisible) {
callback(lastChild, true);
}
}
private static onClipToBoundsPropertyChanged(data: dependencyObservable.PropertyChangeData): void {
var layout = <LayoutBase>data.object;
layout.onClipToBoundsChanged(data.oldValue, data.newValue);

View File

@ -54,12 +54,8 @@ export class StackLayout extends common.StackLayout {
}
var childSize: { measuredWidth: number; measuredHeight: number };
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
if (isVertical) {
childSize = View.measureChild(this, child, childMeasureSpec, utils.layout.makeMeasureSpec(remainingLength, measureSpec));
measureWidth = Math.max(measureWidth, childSize.measuredWidth);
@ -74,7 +70,7 @@ export class StackLayout extends common.StackLayout {
measureWidth += viewWidth;
remainingLength = Math.max(0, remainingLength - viewWidth);
}
}
});
measureWidth += horizontalPadding;
measureHeight += verticalPadding;
@ -129,18 +125,13 @@ export class StackLayout extends common.StackLayout {
break;
}
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty);
let childHeight = child.getMeasuredHeight() + (lp.topMargin + lp.bottomMargin) * density;
View.layoutChild(this, child, childLeft, childTop, childRight, childTop + childHeight);
childTop += childHeight;
}
})
}
private layoutHorizontal(left: number, top: number, right: number, bottom: number): void {
@ -170,17 +161,12 @@ export class StackLayout extends common.StackLayout {
break;
}
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty);
let childWidth = child.getMeasuredWidth() + (lp.leftMargin + lp.rightMargin) * density;
View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childBottom);
childLeft += childWidth;
}
});
}
}

View File

@ -56,12 +56,7 @@ export class WrapLayout extends common.WrapLayout {
let itemWidth = this.itemWidth;
let itemHeight = this.itemHeight;
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
var desiredSize = View.measureChild(this, child, childWidthMeasureSpec, childHeightMeasureSpec);
let childMeasuredWidth = useItemWidth ? itemWidth : desiredSize.measuredWidth;
let childMeasuredHeight = useItemHeight ? itemHeight : desiredSize.measuredHeight;
@ -99,7 +94,7 @@ export class WrapLayout extends common.WrapLayout {
else {
this._lengths[rowOrColumn] = Math.max(this._lengths[rowOrColumn], isVertical ? childMeasuredWidth : childMeasuredHeight);
}
}
});
if (isVertical) {
measureHeight = Math.max(maxLength, measureHeight);
@ -144,12 +139,7 @@ export class WrapLayout extends common.WrapLayout {
}
var rowOrColumn = 0;
for (let i = 0, count = this.getChildrenCount(); i < count; i++) {
let child = this.getChildAt(i);
if (!child._isVisible) {
continue;
}
this.eachLayoutChild((child, last) => {
// Add margins because layoutChild will sustract them.
// * density converts them to device pixels.
let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty);
@ -203,7 +193,7 @@ export class WrapLayout extends common.WrapLayout {
// Move next child Left position to right.
childLeft += childWidth;
}
}
});
WrapLayout.restoreOriginalParams(this);
}

View File

@ -0,0 +1,2 @@
{ "name" : "proxy-view-container",
"main" : "proxy-view-container.js" }

View File

@ -0,0 +1,6 @@
declare module "ui/proxy-view-container" {
import layout = require("ui/layouts/layout-base");
export class ProxyViewContainer extends layout.LayoutBase {
}
}

View File

@ -0,0 +1,111 @@
import types = require("utils/types");
import view = require("ui/core/view");
import definition = require("ui/proxy-view-container");
import trace = require("trace");
import layout = require("ui/layouts/layout-base");
/**
* Proxy view container that adds all its native children dirctly to the parent.
* To be used as a logical grouping container of views.
*/
// Cases to cover:
// * Child is added to the attached proxy. Handled in _addViewToNativeVisualTree.
// * Proxy (with children) is added to the DOM.
// - IOS: Handled in _addToSuperview - when the proxy is added, it adds all its children to the new parent.
// - Android: _onAttached calls _addViewToNativeVisualTree recoursively when the proxy is added to the parent.
// * Child is removed from attached proxy. Handled in _removeViewFromNativeVisualTree.
// * Proxy (with children) is removed form the DOM.
// - IOS: Handled in _removeFromSuperview - when the proxy is removed, it removes all its children from its parent.
// - Android: _onDetached calls _removeViewFromNativeVisualTree recoursively when the proxy is removed from its parent.
export class ProxyViewContainer extends layout.LayoutBase implements definition.ProxyViewContainer {
// No native view for proxy container.
get ios(): any {
return null;
}
get android(): any {
return null;
}
get _nativeView(): any {
return null;
}
public _createUI() {
//
}
public _getNativeViewsCount(): number {
let result = 0;
this._eachChildView((cv) => {
result += cv._getNativeViewsCount();
return true;
});
return result;
}
public _eachLayoutView(callback: (View) => void): void {
this._eachChildView((cv) => {
cv._eachLayoutView(callback);
return true;
});
}
public _addViewToNativeVisualTree(child: view.View, atIndex?: number): boolean {
trace.write("ViewContainer._addViewToNativeVisualTree for a child " + view + " ViewContainer.parent: " + this.parent, trace.categories.ViewHierarchy);
super._addViewToNativeVisualTree(child);
var parent = this.parent;
if (parent) {
let baseIndex = 0;
let insideIndex = 0;
if (parent instanceof layout.LayoutBase) {
baseIndex = parent.getChildIndex(this);
baseIndex = parent._childIndexToNativeChildIndex(baseIndex);
}
if (types.isDefined(atIndex)) {
insideIndex = this._childIndexToNativeChildIndex(atIndex);
} else {
// Add last;
insideIndex = this._getNativeViewsCount();
}
trace.write("ProxyViewContainer._addViewToNativeVisualTree at: " + atIndex + " base: " + baseIndex + " additional: " + insideIndex, trace.categories.ViewHierarchy);
return parent._addViewToNativeVisualTree(child, baseIndex + insideIndex);
}
return false;
}
public _removeViewFromNativeVisualTree(child: view.View): void {
trace.write("ProxyViewContainer._removeViewFromNativeVisualTree for a child " + view + " ViewContainer.parent: " + this.parent, trace.categories.ViewHierarchy);
super._removeViewFromNativeVisualTree(child);
var parent = this.parent;
if (parent) {
return parent._removeViewFromNativeVisualTree(child);
}
}
public _addToSuperview(superview: any, atIndex?: number): boolean {
var index = 0;
this._eachChildView((cv) => {
if (!cv._isAddedToNativeVisualTree) {
cv._isAddedToNativeVisualTree = this._addViewToNativeVisualTree(cv, index++);
}
return true;
});
return true;
}
public _removeFromSuperview() {
this._eachChildView((cv) => {
if (cv._isAddedToNativeVisualTree) {
this._removeViewFromNativeVisualTree(cv);
}
return true;
});
}
}