From e14f4d04cd7b50cd156c9f0cd4a628a0c97cbca2 Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Tue, 12 Nov 2019 15:31:25 +0800 Subject: [PATCH] add listview --- .../java/pub/doric/demo/MainActivity.java | 4 +- .../main/java/pub/doric/DoricRegistry.java | 2 + .../main/java/pub/doric/shader/GroupNode.java | 118 +++++++++--------- .../pub/doric/shader/list/ListAdapter.java | 77 ++++++++++++ .../java/pub/doric/shader/list/ListNode.java | 68 ++++++++++ demo/index.ts | 1 + demo/src/ListDemo.ts | 26 ++++ js-framework/index.ts | 1 + js-framework/src/ui/declarative.ts | 48 +------ js-framework/src/ui/listview.ts | 73 +++++++++++ js-framework/src/ui/view.ts | 104 ++++++++------- js-framework/src/util/gravity.ts | 3 + 12 files changed, 369 insertions(+), 156 deletions(-) create mode 100644 Android/doric/src/main/java/pub/doric/shader/list/ListAdapter.java create mode 100644 Android/doric/src/main/java/pub/doric/shader/list/ListNode.java create mode 100644 demo/src/ListDemo.ts create mode 100644 js-framework/src/ui/listview.ts diff --git a/Android/app/src/main/java/pub/doric/demo/MainActivity.java b/Android/app/src/main/java/pub/doric/demo/MainActivity.java index ada3a72a..791d7735 100644 --- a/Android/app/src/main/java/pub/doric/demo/MainActivity.java +++ b/Android/app/src/main/java/pub/doric/demo/MainActivity.java @@ -22,9 +22,6 @@ import android.widget.FrameLayout; import androidx.appcompat.app.AppCompatActivity; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; import java.io.IOException; @@ -36,6 +33,7 @@ import pub.doric.dev.event.EnterDebugEvent; import pub.doric.dev.event.QuitDebugEvent; import pub.doric.engine.ChangeEngineCallback; import pub.doric.utils.DoricUtils; +import pub.doric.utils.ThreadMode; public class MainActivity extends AppCompatActivity { diff --git a/Android/doric/src/main/java/pub/doric/DoricRegistry.java b/Android/doric/src/main/java/pub/doric/DoricRegistry.java index d36af6b0..8bb597b3 100644 --- a/Android/doric/src/main/java/pub/doric/DoricRegistry.java +++ b/Android/doric/src/main/java/pub/doric/DoricRegistry.java @@ -20,6 +20,7 @@ import android.text.TextUtils; import pub.doric.plugin.ShaderPlugin; import pub.doric.shader.HLayoutNode; import pub.doric.shader.ImageNode; +import pub.doric.shader.list.ListNode; import pub.doric.shader.RootNode; import pub.doric.shader.StackNode; import pub.doric.shader.TextNode; @@ -66,6 +67,7 @@ public class DoricRegistry { this.registerViewNode(StackNode.class); this.registerViewNode(VLayoutNode.class); this.registerViewNode(HLayoutNode.class); + this.registerViewNode(ListNode.class); initRegistry(this); } diff --git a/Android/doric/src/main/java/pub/doric/shader/GroupNode.java b/Android/doric/src/main/java/pub/doric/shader/GroupNode.java index 81cd53b5..dd1e690e 100644 --- a/Android/doric/src/main/java/pub/doric/shader/GroupNode.java +++ b/Android/doric/src/main/java/pub/doric/shader/GroupNode.java @@ -45,73 +45,69 @@ public abstract class GroupNode extends ViewNode { @Override protected void blend(F view, ViewGroup.LayoutParams layoutParams, String name, JSValue prop) { - super.blend(view, layoutParams, name, prop); - switch (name) { - case "children": - JSArray jsArray = prop.asArray(); - int i; - List tobeRemoved = new ArrayList<>(); - for (i = 0; i < jsArray.size(); i++) { - JSValue jsValue = jsArray.get(i); - if (!jsValue.isObject()) { - continue; - } - JSObject childObj = jsValue.asObject(); - String type = childObj.getProperty("type").asString().value(); - String id = childObj.getProperty("id").asString().value(); - ViewNode child = mChildrenNode.get(id); - if (child == null) { - child = ViewNode.create(getDoricContext(), type); + if ("children".equals(name)) { + JSArray jsArray = prop.asArray(); + int i; + List tobeRemoved = new ArrayList<>(); + for (i = 0; i < jsArray.size(); i++) { + JSValue jsValue = jsArray.get(i); + if (!jsValue.isObject()) { + continue; + } + JSObject childObj = jsValue.asObject(); + String type = childObj.getProperty("type").asString().value(); + String id = childObj.getProperty("id").asString().value(); + ViewNode child = mChildrenNode.get(id); + if (child == null) { + child = ViewNode.create(getDoricContext(), type); + child.index = i; + child.mParent = this; + child.mId = id; + mChildrenNode.put(id, child); + } else { + if (i != child.index) { + mIndexInfo.remove(i); child.index = i; - child.mParent = this; - child.mId = id; - mChildrenNode.put(id, child); - } else { - if (i != child.index) { - mIndexInfo.remove(i); - child.index = i; - mView.removeView(child.getView()); - } - tobeRemoved.remove(child); + mView.removeView(child.getView()); } - - ViewNode node = mIndexInfo.get(i); - - if (node != null && node != child) { - mView.removeViewAt(i); - mIndexInfo.remove(i); - tobeRemoved.add(node); - } - - ViewGroup.LayoutParams params = child.getLayoutParams(); - if (params == null) { - params = generateDefaultLayoutParams(); - } - child.blend(childObj.getProperty("props").asObject(), params); - if (mIndexInfo.get(i) == null) { - mView.addView(child.getView(), i, child.getLayoutParams()); - mIndexInfo.put(i, child); - } - } - int count = mView.getChildCount(); - while (i < count) { - ViewNode node = mIndexInfo.get(i); - if (node != null) { - mChildrenNode.remove(node.getId()); - mIndexInfo.remove(i); - tobeRemoved.remove(node); - mView.removeView(node.getView()); - } - i++; + tobeRemoved.remove(child); } - for (ViewNode node : tobeRemoved) { + ViewNode node = mIndexInfo.get(i); + + if (node != null && node != child) { + mView.removeViewAt(i); + mIndexInfo.remove(i); + tobeRemoved.add(node); + } + + ViewGroup.LayoutParams params = child.getLayoutParams(); + if (params == null) { + params = generateDefaultLayoutParams(); + } + child.blend(childObj.getProperty("props").asObject(), params); + if (mIndexInfo.get(i) == null) { + mView.addView(child.getView(), i, child.getLayoutParams()); + mIndexInfo.put(i, child); + } + } + int count = mView.getChildCount(); + while (i < count) { + ViewNode node = mIndexInfo.get(i); + if (node != null) { mChildrenNode.remove(node.getId()); + mIndexInfo.remove(i); + tobeRemoved.remove(node); + mView.removeView(node.getView()); } - break; - default: - super.blend(view, layoutParams, name, prop); - break; + i++; + } + + for (ViewNode node : tobeRemoved) { + mChildrenNode.remove(node.getId()); + } + } else { + super.blend(view, layoutParams, name, prop); } } diff --git a/Android/doric/src/main/java/pub/doric/shader/list/ListAdapter.java b/Android/doric/src/main/java/pub/doric/shader/list/ListAdapter.java new file mode 100644 index 00000000..d8ad0e6f --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/list/ListAdapter.java @@ -0,0 +1,77 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * 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. + */ +package pub.doric.shader.list; + +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.github.pengfeizhou.jscore.JSObject; + +/** + * @Description: com.github.penfeizhou.doric.widget + * @Author: pengfei.zhou + * @CreateDate: 2019-11-12 + */ +public class ListAdapter extends RecyclerView.Adapter { + + private final ListNode listNode; + String renderItemFuncId; + String renderBunchedItemsFuncId; + int itemCount = 0; + int batchCount = 15; + private SparseArray itemObjects = new SparseArray<>(); + + public ListAdapter(ListNode listNode) { + this.listNode = listNode; + } + + @NonNull + @Override + public DoricViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return null; + } + + @Override + public void onBindViewHolder(@NonNull DoricViewHolder holder, int position) { + + } + + @Override + public int getItemCount() { + return itemCount; + } + + @Override + public int getItemViewType(int position) { + return super.getItemViewType(position); + } + + + private void jsCallRenderItem() { + listNode.callJSResponse(renderItemFuncId); + } + + + public static class DoricViewHolder extends RecyclerView.ViewHolder { + public DoricViewHolder(@NonNull View itemView) { + super(itemView); + } + } +} diff --git a/Android/doric/src/main/java/pub/doric/shader/list/ListNode.java b/Android/doric/src/main/java/pub/doric/shader/list/ListNode.java new file mode 100644 index 00000000..43b6b64a --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/list/ListNode.java @@ -0,0 +1,68 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * 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. + */ +package pub.doric.shader.list; + +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + +import com.github.pengfeizhou.jscore.JSObject; +import com.github.pengfeizhou.jscore.JSValue; + +import pub.doric.DoricContext; +import pub.doric.extension.bridge.DoricPlugin; +import pub.doric.shader.ViewNode; + +/** + * @Description: com.github.penfeizhou.doric.widget + * @Author: pengfei.zhou + * @CreateDate: 2019-11-12 + */ +@DoricPlugin(name = "List") +public class ListNode extends ViewNode { + private final ListAdapter listAdapter; + + public ListNode(DoricContext doricContext) { + super(doricContext); + this.listAdapter = new ListAdapter(this); + } + + @Override + protected RecyclerView build(JSObject jsObject) { + return new RecyclerView(getContext()); + } + + @Override + protected void blend(RecyclerView view, ViewGroup.LayoutParams layoutParams, String name, JSValue prop) { + switch (name) { + case "itemCount": + this.listAdapter.itemCount = prop.asNumber().toInt(); + break; + case "renderItem": + this.listAdapter.renderItemFuncId = prop.asString().value(); + break; + case "renderBunchedItemsFuncId": + this.listAdapter.renderBunchedItemsFuncId = prop.asString().value(); + break; + case "batchCount": + this.listAdapter.batchCount = 15; + break; + default: + super.blend(view, layoutParams, name, prop); + break; + } + } +} diff --git a/demo/index.ts b/demo/index.ts index 2ff77250..852d019c 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -1,4 +1,5 @@ export default [ 'src/Counter', 'src/Snake', + 'src/ListDemo', ] \ No newline at end of file diff --git a/demo/src/ListDemo.ts b/demo/src/ListDemo.ts new file mode 100644 index 00000000..a041e25c --- /dev/null +++ b/demo/src/ListDemo.ts @@ -0,0 +1,26 @@ +import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, ListItem } from "doric"; + +@Entry +class ListPanel extends Panel { + build(rootView: Group): void { + const list = new List + list.layoutConfig = { + widthSpec: LayoutSpec.AT_MOST, + heightSpec: LayoutSpec.AT_MOST, + } + rootView.addChild(list) + list.itemCount = 10 + list.bgColor = Color.parse("#ff00ff") + list.renderItem = (idx) => { + const item = new ListItem + item.addChild(text({ + width: 100, + height: 100, + text: `第${idx}行内容`, + textAlignment: gravity().center(), + })) + return item + } + } + +} \ No newline at end of file diff --git a/js-framework/index.ts b/js-framework/index.ts index 678d75f7..1397afed 100644 --- a/js-framework/index.ts +++ b/js-framework/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ export * from "./src/ui/view" +export * from "./src/ui/listview" export * from "./src/ui/panel" export * from "./src/ui/declarative" export * from "./src/util/color" diff --git a/js-framework/src/ui/declarative.ts b/js-framework/src/ui/declarative.ts index 96064545..e8f58894 100644 --- a/js-framework/src/ui/declarative.ts +++ b/js-framework/src/ui/declarative.ts @@ -1,51 +1,5 @@ -import { Text, Image, HLayout, VLayout, Stack, LayoutConfig, View } from './view' -import { Color, GradientColor } from '../util/color' -import { Gravity } from '../util/gravity' +import { Text, Image, HLayout, VLayout, Stack, LayoutConfig, View, IText, IImage } from './view' -export interface IView { - width?: number - height?: number - bgColor?: Color | GradientColor - corners?: number | { leftTop?: number; rightTop?: number; leftBottom?: number; rightBottom?: number } - border?: { width: number; color: Color; } - shadow?: { color: Color; opacity: number; radius: number; offsetX: number; offsetY: number } - alpha?: number - hidden?: boolean - padding?: { - left?: number, - right?: number, - top?: number, - bottom?: number, - } - layoutConfig?: LayoutConfig - onClick?: Function - identifier?: string -} -export interface IText extends IView { - text?: string - textColor?: Color - textSize?: number - maxLines?: number - textAlignment?: Gravity -} - -export interface IImage extends IView { - imageUrl?: string -} - -export interface IStack extends IView { - gravity?: Gravity -} - -export interface IVLayout extends IView { - space?: number - gravity?: Gravity -} - -export interface IHLayout extends IView { - space?: number - gravity?: Gravity -} export function text(config: IText) { const ret = new Text for (let key in config) { diff --git a/js-framework/src/ui/listview.ts b/js-framework/src/ui/listview.ts new file mode 100644 index 00000000..ff6b9de2 --- /dev/null +++ b/js-framework/src/ui/listview.ts @@ -0,0 +1,73 @@ +import { View, Stack, Property, SuperView, Group, LayoutSpec } from "./view"; +import { Model } from "../util/types"; +import { O_TRUNC } from "constants"; + +/* + * Copyright [2019] [Doric.Pub] + * + * 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. + */ + +export function listItem(item: View) { + return (new ListItem).also((it) => { + it.layoutConfig = { + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + it.addChild(item) + }) +} + +export class ListItem extends Stack { + /** + * Set to reuse native view + */ + @Property + identifier?: string + list!: List + onChildPropertyChanged(child: View) { + super.onChildPropertyChanged(child) + } +} + +export class List extends View implements SuperView { + private cachedViews: Map = new Map + + subviewById(id: string): ListItem | undefined { + return this.cachedViews.get(id) + } + + @Property + itemCount = 0 + + @Property + renderItem!: (index: number) => ListItem + + @Property + batchCount = 15 + + private getItem(itemIdx: number) { + let view = this.cachedViews.get(`${itemIdx}`) + if (view === undefined) { + view = this.renderItem(itemIdx) + view.list = this + this.cachedViews.set(`${itemIdx}`, view) + } + return view + } + + @Property + private renderBunchedItems(items: number[]): ListItem[] { + return items.map(e => this.getItem(e)) + } +} \ No newline at end of file diff --git a/js-framework/src/ui/view.ts b/js-framework/src/ui/view.ts index ed60ad3a..d199ae79 100644 --- a/js-framework/src/ui/view.ts +++ b/js-framework/src/ui/view.ts @@ -41,7 +41,28 @@ export function Property(target: Object, propKey: string) { Reflect.defineMetadata(propKey, true, target) } -export abstract class View implements Modeling { +export interface IView { + width?: number + height?: number + bgColor?: Color | GradientColor + corners?: number | { leftTop?: number; rightTop?: number; leftBottom?: number; rightBottom?: number } + border?: { width: number; color: Color; } + shadow?: { color: Color; opacity: number; radius: number; offsetX: number; offsetY: number } + alpha?: number + hidden?: boolean + padding?: { + left?: number, + right?: number, + top?: number, + bottom?: number, + } + layoutConfig?: LayoutConfig + onClick?: Function + identifier?: string +} + + +export abstract class View implements Modeling, IView { @Property width: number = 0 @@ -89,12 +110,6 @@ export abstract class View implements Modeling { @Property onClick?: Function - /** - * Set to reuse native view - */ - @Property - identifier?: string - parent?: Group callbacks: Map = new Map @@ -242,7 +257,7 @@ export interface LinearConfig extends LayoutConfig { } export interface SuperView { - subViewById(id: string): View | undefined + subviewById(id: string): View | undefined } export abstract class Group extends View implements SuperView { @@ -266,7 +281,7 @@ export abstract class Group extends View implements SuperView { } }) - subViewById(id: string): View | undefined { + subviewById(id: string): View | undefined { for (let view of this.children) { if (view.viewId === id) { return view @@ -309,8 +324,11 @@ export abstract class Group extends View implements SuperView { return super.isDirty() } } +export interface IStack extends IView { + gravity?: Gravity +} -export class Stack extends Group { +export class Stack extends Group implements IStack { @Property gravity?: Gravity } @@ -319,7 +337,7 @@ export class Scroller extends View implements SuperView { @Property contentView?: View - subViewById(id: string): View | undefined { + subviewById(id: string): View | undefined { return this.contentView } } @@ -335,13 +353,32 @@ class LinearLayout extends Group { gravity?: Gravity } -export class VLayout extends LinearLayout { +export interface IVLayout extends IView { + space?: number + gravity?: Gravity } -export class HLayout extends LinearLayout { +export class VLayout extends LinearLayout implements VLayout { } -export class Text extends View { + +export interface IHLayout extends IView { + space?: number + gravity?: Gravity +} + +export class HLayout extends LinearLayout implements IHLayout { +} + +export interface IText extends IView { + text?: string + textColor?: Color + textSize?: number + maxLines?: number + textAlignment?: Gravity +} + +export class Text extends View implements IText { @Property text?: string @@ -358,44 +395,20 @@ export class Text extends View { textAlignment?: Gravity } -export class Image extends View { +export interface IImage extends IView { + imageUrl?: string +} + +export class Image extends View implements IImage { @Property imageUrl?: string } -export class List extends View implements SuperView { - private cachedViews: Map = new Map - - subViewById(id: string): View | undefined { - return this.cachedViews.get(id) - } - - @Property - itemCount = 0 - - @Property - renderItem!: (index: number) => View - - - private getItem(itemIdx: number) { - let view = this.cachedViews.get(`${itemIdx}`) - if (view === undefined) { - view = this.renderItem(itemIdx) - this.cachedViews.set(`${itemIdx}`, view) - } - return view - } - - @Property - private renderBunchedItems(items: number[]): View[] { - return items.map(e => this.getItem(e)) - } -} export class SectionList extends View implements SuperView { private cachedViews: Map = new Map - subViewById(id: string): View | undefined { + subviewById(id: string): View | undefined { return this.cachedViews.get(id) } @Property @@ -450,7 +463,8 @@ export class Slide extends View implements SuperView { renderPage!: (pageIdx: number) => View private cachedViews: Map = new Map - subViewById(id: string): View | undefined { + + subviewById(id: string): View | undefined { return this.cachedViews.get(id) } private getPage(pageIdx: number) { diff --git a/js-framework/src/util/gravity.ts b/js-framework/src/util/gravity.ts index 4a3eba9c..e044c150 100644 --- a/js-framework/src/util/gravity.ts +++ b/js-framework/src/util/gravity.ts @@ -76,4 +76,7 @@ export class Gravity implements Modeling { return this.val } +} +export function gravity() { + return new Gravity } \ No newline at end of file