add listview

This commit is contained in:
pengfei.zhou 2019-11-12 15:31:25 +08:00
parent 5b3f929ee0
commit e14f4d04cd
12 changed files with 369 additions and 156 deletions

View File

@ -22,9 +22,6 @@ import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.IOException; import java.io.IOException;
@ -36,6 +33,7 @@ import pub.doric.dev.event.EnterDebugEvent;
import pub.doric.dev.event.QuitDebugEvent; import pub.doric.dev.event.QuitDebugEvent;
import pub.doric.engine.ChangeEngineCallback; import pub.doric.engine.ChangeEngineCallback;
import pub.doric.utils.DoricUtils; import pub.doric.utils.DoricUtils;
import pub.doric.utils.ThreadMode;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {

View File

@ -20,6 +20,7 @@ import android.text.TextUtils;
import pub.doric.plugin.ShaderPlugin; import pub.doric.plugin.ShaderPlugin;
import pub.doric.shader.HLayoutNode; import pub.doric.shader.HLayoutNode;
import pub.doric.shader.ImageNode; import pub.doric.shader.ImageNode;
import pub.doric.shader.list.ListNode;
import pub.doric.shader.RootNode; import pub.doric.shader.RootNode;
import pub.doric.shader.StackNode; import pub.doric.shader.StackNode;
import pub.doric.shader.TextNode; import pub.doric.shader.TextNode;
@ -66,6 +67,7 @@ public class DoricRegistry {
this.registerViewNode(StackNode.class); this.registerViewNode(StackNode.class);
this.registerViewNode(VLayoutNode.class); this.registerViewNode(VLayoutNode.class);
this.registerViewNode(HLayoutNode.class); this.registerViewNode(HLayoutNode.class);
this.registerViewNode(ListNode.class);
initRegistry(this); initRegistry(this);
} }

View File

@ -45,73 +45,69 @@ public abstract class GroupNode<F extends ViewGroup> extends ViewNode<F> {
@Override @Override
protected void blend(F view, ViewGroup.LayoutParams layoutParams, String name, JSValue prop) { protected void blend(F view, ViewGroup.LayoutParams layoutParams, String name, JSValue prop) {
super.blend(view, layoutParams, name, prop); if ("children".equals(name)) {
switch (name) { JSArray jsArray = prop.asArray();
case "children": int i;
JSArray jsArray = prop.asArray(); List<ViewNode> tobeRemoved = new ArrayList<>();
int i; for (i = 0; i < jsArray.size(); i++) {
List<ViewNode> tobeRemoved = new ArrayList<>(); JSValue jsValue = jsArray.get(i);
for (i = 0; i < jsArray.size(); i++) { if (!jsValue.isObject()) {
JSValue jsValue = jsArray.get(i); continue;
if (!jsValue.isObject()) { }
continue; JSObject childObj = jsValue.asObject();
} String type = childObj.getProperty("type").asString().value();
JSObject childObj = jsValue.asObject(); String id = childObj.getProperty("id").asString().value();
String type = childObj.getProperty("type").asString().value(); ViewNode child = mChildrenNode.get(id);
String id = childObj.getProperty("id").asString().value(); if (child == null) {
ViewNode child = mChildrenNode.get(id); child = ViewNode.create(getDoricContext(), type);
if (child == null) { child.index = i;
child = ViewNode.create(getDoricContext(), type); child.mParent = this;
child.mId = id;
mChildrenNode.put(id, child);
} else {
if (i != child.index) {
mIndexInfo.remove(i);
child.index = i; child.index = i;
child.mParent = this; mView.removeView(child.getView());
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);
} }
tobeRemoved.remove(child);
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++;
} }
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()); mChildrenNode.remove(node.getId());
mIndexInfo.remove(i);
tobeRemoved.remove(node);
mView.removeView(node.getView());
} }
break; i++;
default: }
super.blend(view, layoutParams, name, prop);
break; for (ViewNode node : tobeRemoved) {
mChildrenNode.remove(node.getId());
}
} else {
super.blend(view, layoutParams, name, prop);
} }
} }

View File

@ -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<ListAdapter.DoricViewHolder> {
private final ListNode listNode;
String renderItemFuncId;
String renderBunchedItemsFuncId;
int itemCount = 0;
int batchCount = 15;
private SparseArray<JSObject> 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);
}
}
}

View File

@ -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<RecyclerView> {
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;
}
}
}

View File

@ -1,4 +1,5 @@
export default [ export default [
'src/Counter', 'src/Counter',
'src/Snake', 'src/Snake',
'src/ListDemo',
] ]

26
demo/src/ListDemo.ts Normal file
View File

@ -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
}
}
}

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
export * from "./src/ui/view" export * from "./src/ui/view"
export * from "./src/ui/listview"
export * from "./src/ui/panel" export * from "./src/ui/panel"
export * from "./src/ui/declarative" export * from "./src/ui/declarative"
export * from "./src/util/color" export * from "./src/util/color"

View File

@ -1,51 +1,5 @@
import { Text, Image, HLayout, VLayout, Stack, LayoutConfig, View } from './view' import { Text, Image, HLayout, VLayout, Stack, LayoutConfig, View, IText, IImage } from './view'
import { Color, GradientColor } from '../util/color'
import { Gravity } from '../util/gravity'
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) { export function text(config: IText) {
const ret = new Text const ret = new Text
for (let key in config) { for (let key in config) {

View File

@ -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<string, ListItem> = 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))
}
}

View File

@ -41,7 +41,28 @@ export function Property(target: Object, propKey: string) {
Reflect.defineMetadata(propKey, true, target) 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 @Property
width: number = 0 width: number = 0
@ -89,12 +110,6 @@ export abstract class View implements Modeling {
@Property @Property
onClick?: Function onClick?: Function
/**
* Set to reuse native view
*/
@Property
identifier?: string
parent?: Group parent?: Group
callbacks: Map<String, Function> = new Map callbacks: Map<String, Function> = new Map
@ -242,7 +257,7 @@ export interface LinearConfig extends LayoutConfig {
} }
export interface SuperView { export interface SuperView {
subViewById(id: string): View | undefined subviewById(id: string): View | undefined
} }
export abstract class Group extends View implements SuperView { 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) { for (let view of this.children) {
if (view.viewId === id) { if (view.viewId === id) {
return view return view
@ -309,8 +324,11 @@ export abstract class Group extends View implements SuperView {
return super.isDirty() return super.isDirty()
} }
} }
export interface IStack extends IView {
gravity?: Gravity
}
export class Stack extends Group { export class Stack extends Group implements IStack {
@Property @Property
gravity?: Gravity gravity?: Gravity
} }
@ -319,7 +337,7 @@ export class Scroller extends View implements SuperView {
@Property @Property
contentView?: View contentView?: View
subViewById(id: string): View | undefined { subviewById(id: string): View | undefined {
return this.contentView return this.contentView
} }
} }
@ -335,13 +353,32 @@ class LinearLayout extends Group {
gravity?: Gravity 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 @Property
text?: string text?: string
@ -358,44 +395,20 @@ export class Text extends View {
textAlignment?: Gravity textAlignment?: Gravity
} }
export class Image extends View { export interface IImage extends IView {
imageUrl?: string
}
export class Image extends View implements IImage {
@Property @Property
imageUrl?: string imageUrl?: string
} }
export class List extends View implements SuperView {
private cachedViews: Map<string, View> = 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 { export class SectionList extends View implements SuperView {
private cachedViews: Map<string, View> = new Map private cachedViews: Map<string, View> = new Map
subViewById(id: string): View | undefined { subviewById(id: string): View | undefined {
return this.cachedViews.get(id) return this.cachedViews.get(id)
} }
@Property @Property
@ -450,7 +463,8 @@ export class Slide extends View implements SuperView {
renderPage!: (pageIdx: number) => View renderPage!: (pageIdx: number) => View
private cachedViews: Map<string, View> = new Map private cachedViews: Map<string, View> = new Map
subViewById(id: string): View | undefined {
subviewById(id: string): View | undefined {
return this.cachedViews.get(id) return this.cachedViews.get(id)
} }
private getPage(pageIdx: number) { private getPage(pageIdx: number) {

View File

@ -76,4 +76,7 @@ export class Gravity implements Modeling {
return this.val return this.val
} }
}
export function gravity() {
return new Gravity
} }