From 353ff356a5d9d550f9c89cd3cb0e0a61a861116b Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Tue, 19 Nov 2019 14:56:25 +0800 Subject: [PATCH] add slider --- .../main/java/pub/doric/DoricRegistry.java | 4 + .../pub/doric/shader/list/ListAdapter.java | 2 +- .../java/pub/doric/shader/list/ListNode.java | 2 +- .../pub/doric/shader/slider/SlideAdapter.java | 136 ++++++++++++++++++ .../doric/shader/slider/SlideItemNode.java | 56 ++++++++ .../pub/doric/shader/slider/SliderNode.java | 115 +++++++++++++++ demo/src/SliderDemo.ts | 55 +++++++ iOS/Pod/Classes/Shader/DoricScrollerNode.h | 19 ++- iOS/Pod/Classes/Shader/DoricScrollerNode.m | 19 ++- js-framework/index.ts | 3 +- js-framework/src/ui/declarative.ts | 11 +- js-framework/src/ui/{listview.ts => list.ts} | 0 js-framework/src/ui/slider.ts | 72 ++++++++++ 13 files changed, 482 insertions(+), 12 deletions(-) create mode 100644 Android/doric/src/main/java/pub/doric/shader/slider/SlideAdapter.java create mode 100644 Android/doric/src/main/java/pub/doric/shader/slider/SlideItemNode.java create mode 100644 Android/doric/src/main/java/pub/doric/shader/slider/SliderNode.java create mode 100644 demo/src/SliderDemo.ts rename js-framework/src/ui/{listview.ts => list.ts} (100%) create mode 100644 js-framework/src/ui/slider.ts diff --git a/Android/doric/src/main/java/pub/doric/DoricRegistry.java b/Android/doric/src/main/java/pub/doric/DoricRegistry.java index 3747cdce..afc03619 100644 --- a/Android/doric/src/main/java/pub/doric/DoricRegistry.java +++ b/Android/doric/src/main/java/pub/doric/DoricRegistry.java @@ -28,6 +28,8 @@ import pub.doric.shader.StackNode; import pub.doric.shader.TextNode; import pub.doric.shader.VLayoutNode; import pub.doric.shader.ViewNode; +import pub.doric.shader.slider.SlideItemNode; +import pub.doric.shader.slider.SliderNode; import pub.doric.utils.DoricMetaInfo; import pub.doric.plugin.DoricJavaPlugin; import pub.doric.plugin.ModalPlugin; @@ -72,6 +74,8 @@ public class DoricRegistry { this.registerViewNode(ListNode.class); this.registerViewNode(ListItemNode.class); this.registerViewNode(ScrollerNode.class); + this.registerViewNode(SliderNode.class); + this.registerViewNode(SlideItemNode.class); initRegistry(this); } 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 index ca821c54..37123db3 100644 --- a/Android/doric/src/main/java/pub/doric/shader/list/ListAdapter.java +++ b/Android/doric/src/main/java/pub/doric/shader/list/ListAdapter.java @@ -37,7 +37,7 @@ import pub.doric.shader.ViewNode; * @Author: pengfei.zhou * @CreateDate: 2019-11-12 */ -public class ListAdapter extends RecyclerView.Adapter { +class ListAdapter extends RecyclerView.Adapter { private final ListNode listNode; String renderItemFuncId; 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 index 4a8feb43..590e5d32 100644 --- a/Android/doric/src/main/java/pub/doric/shader/list/ListNode.java +++ b/Android/doric/src/main/java/pub/doric/shader/list/ListNode.java @@ -81,7 +81,7 @@ public class ListNode extends SuperNode { clearSubModel(); break; case "batchCount": - this.listAdapter.batchCount = 15; + this.listAdapter.batchCount = prop.asNumber().toInt(); break; default: super.blend(view, name, prop); diff --git a/Android/doric/src/main/java/pub/doric/shader/slider/SlideAdapter.java b/Android/doric/src/main/java/pub/doric/shader/slider/SlideAdapter.java new file mode 100644 index 00000000..d67c7169 --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/slider/SlideAdapter.java @@ -0,0 +1,136 @@ +/* + * 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.slider; + +import android.text.TextUtils; +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.JSArray; +import com.github.pengfeizhou.jscore.JSDecoder; +import com.github.pengfeizhou.jscore.JSNull; +import com.github.pengfeizhou.jscore.JSObject; +import com.github.pengfeizhou.jscore.JSValue; + +import pub.doric.async.AsyncResult; +import pub.doric.shader.ViewNode; + +/** + * @Description: com.github.penfeizhou.doric.widget + * @Author: pengfei.zhou + * @CreateDate: 2019-11-12 + */ +class SlideAdapter extends RecyclerView.Adapter { + + private final SliderNode sliderNode; + int itemCount = 0; + int batchCount = 3; + SparseArray itemValues = new SparseArray<>(); + + SlideAdapter(SliderNode sliderNode) { + this.sliderNode = sliderNode; + } + + @NonNull + @Override + public DoricViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + SlideItemNode node = (SlideItemNode) ViewNode.create(sliderNode.getDoricContext(), "SlideItem"); + node.init(sliderNode); + return new DoricViewHolder(node, node.getDoricLayer()); + } + + @Override + public void onBindViewHolder(@NonNull DoricViewHolder holder, int position) { + JSValue jsValue = getItemModel(position); + if (jsValue.isObject()) { + JSObject jsObject = jsValue.asObject(); + holder.slideItemNode.setId(jsObject.getProperty("id").asString().value()); + holder.slideItemNode.blend(jsObject.getProperty("props").asObject()); + } + } + + @Override + public int getItemCount() { + return itemCount; + } + + @Override + public int getItemViewType(int position) { + JSValue value = getItemModel(position); + if (value.isObject()) { + if (value.asObject().getProperty("identifier").isString()) { + return value.asObject().getProperty("identifier").asString().value().hashCode(); + } + } + return super.getItemViewType(position); + } + + private JSValue getItemModel(final int position) { + String id = itemValues.get(position); + if (TextUtils.isEmpty(id)) { + AsyncResult asyncResult = sliderNode.callJSResponse( + "renderBunchedItems", + position, + batchCount); + try { + JSDecoder jsDecoder = asyncResult.synchronous().get(); + JSValue result = jsDecoder.decode(); + if (result.isArray()) { + JSArray jsArray = result.asArray(); + for (int i = 0; i < jsArray.size(); i++) { + JSObject itemModel = jsArray.get(i).asObject(); + String itemId = itemModel.getProperty("id").asString().value(); + itemValues.put(i + position, itemId); + sliderNode.setSubModel(itemId, itemModel); + } + return sliderNode.getSubModel(itemValues.get(position)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return new JSNull(); + } else { + JSObject childModel = sliderNode.getSubModel(id); + if (childModel == null) { + return new JSNull(); + } else { + return childModel; + } + } + } + + + void blendSubNode(JSObject subProperties) { + for (int i = 0; i < itemValues.size(); i++) { + if (subProperties.getProperty("id").asString().value().equals(itemValues.valueAt(i))) { + notifyItemChanged(i); + } + } + } + + static class DoricViewHolder extends RecyclerView.ViewHolder { + SlideItemNode slideItemNode; + + DoricViewHolder(SlideItemNode node, @NonNull View itemView) { + super(itemView); + slideItemNode = node; + } + } +} diff --git a/Android/doric/src/main/java/pub/doric/shader/slider/SlideItemNode.java b/Android/doric/src/main/java/pub/doric/shader/slider/SlideItemNode.java new file mode 100644 index 00000000..88c257a0 --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/slider/SlideItemNode.java @@ -0,0 +1,56 @@ +/* + * 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.slider; + +import android.widget.FrameLayout; + +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.StackNode; + +/** + * @Description: com.github.penfeizhou.doric.widget + * @Author: pengfei.zhou + * @CreateDate: 2019-11-12 + */ +@DoricPlugin(name = "SlideItem") +public class SlideItemNode extends StackNode { + public String identifier = ""; + + public SlideItemNode(DoricContext doricContext) { + super(doricContext); + this.mReusable = true; + } + + @Override + protected void blend(FrameLayout view, String name, JSValue prop) { + if ("identifier".equals(name)) { + this.identifier = prop.asString().value(); + } else { + super.blend(view, name, prop); + } + } + + @Override + public void blend(JSObject jsObject) { + super.blend(jsObject); + getDoricLayer().getLayoutParams().width = getLayoutParams().width; + getDoricLayer().getLayoutParams().height = getLayoutParams().height; + } +} diff --git a/Android/doric/src/main/java/pub/doric/shader/slider/SliderNode.java b/Android/doric/src/main/java/pub/doric/shader/slider/SliderNode.java new file mode 100644 index 00000000..050f89f3 --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/slider/SliderNode.java @@ -0,0 +1,115 @@ +/* + * 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.slider; + +import android.view.View; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.PagerSnapHelper; +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.SuperNode; +import pub.doric.shader.ViewNode; + +/** + * @Description: pub.doric.shader + * @Author: pengfei.zhou + * @CreateDate: 2019-11-19 + */ +@DoricPlugin(name = "Slider") +public class SliderNode extends SuperNode { + private final SlideAdapter slideAdapter; + + public SliderNode(DoricContext doricContext) { + super(doricContext); + this.slideAdapter = new SlideAdapter(this); + } + + @Override + protected RecyclerView build() { + RecyclerView recyclerView = new RecyclerView(getContext()); + + LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); + layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); + recyclerView.setLayoutManager(layoutManager); + PagerSnapHelper mPagerSnapHelper = new PagerSnapHelper(); + mPagerSnapHelper.attachToRecyclerView(recyclerView); + recyclerView.setAdapter(this.slideAdapter); + return recyclerView; + } + + @Override + public ViewNode getSubNodeById(String id) { + RecyclerView.LayoutManager manager = mView.getLayoutManager(); + if (manager == null) { + return null; + } + for (int i = 0; i < manager.getChildCount(); i++) { + View view = manager.getChildAt(i); + if (view == null) { + continue; + } + SlideAdapter.DoricViewHolder viewHolder = (SlideAdapter.DoricViewHolder) mView.getChildViewHolder(view); + if (id.equals(viewHolder.slideItemNode.getId())) { + return viewHolder.slideItemNode; + } + } + return null; + } + + @Override + protected void blendSubNode(JSObject subProperties) { + slideAdapter.blendSubNode(subProperties); + } + + @Override + public void blend(JSObject jsObject) { + super.blend(jsObject); + if (mView != null) { + mView.post(new Runnable() { + @Override + public void run() { + slideAdapter.notifyDataSetChanged(); + } + }); + } + } + + @Override + protected void blend(RecyclerView view, String name, JSValue prop) { + switch (name) { + case "itemCount": + this.slideAdapter.itemCount = prop.asNumber().toInt(); + break; + case "renderItem": + // If reset renderItem,should reset native cache. + this.slideAdapter.itemValues.clear(); + clearSubModel(); + break; + case "batchCount": + this.slideAdapter.batchCount = prop.asNumber().toInt(); + break; + default: + super.blend(view, name, prop); + break; + } + } +} diff --git a/demo/src/SliderDemo.ts b/demo/src/SliderDemo.ts new file mode 100644 index 00000000..d8a3a885 --- /dev/null +++ b/demo/src/SliderDemo.ts @@ -0,0 +1,55 @@ +import { Group, Panel, List, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, slider, slideItem } from "doric"; +const colors = [ + "#f0932b", + "#eb4d4b", + "#6ab04c", + "#e056fd", + "#686de0", + "#30336b", +] +@Entry +class SliderPanel extends Panel { + build(rootView: Group): void { + rootView.addChild(vlayout([ + text({ + text: "SliderDemo", + layoutConfig: { + widthSpec: LayoutSpec.AT_MOST, + heightSpec: LayoutSpec.EXACTLY, + }, + textSize: 30, + textColor: Color.parse("#535c68"), + bgColor: Color.parse("#dff9fb"), + textAlignment: gravity().center(), + height: 50, + }), + slider({ + itemCount: 3, + renderPage: (idx) => { + return slideItem(text({ + layoutConfig: { + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.EXACTLY, + alignment: gravity().center(), + }, + text: `Cell At Line ${idx}`, + textAlignment: gravity().center(), + textColor: Color.parse("#ffffff"), + textSize: 20, + height: 50, + bgColor: Color.parse('#00ff00'), + })) + }, + layoutConfig: { + widthSpec: LayoutSpec.AT_MOST, + heightSpec: LayoutSpec.AT_MOST, + }, + }), + ]).also(it => { + it.layoutConfig = { + widthSpec: LayoutSpec.AT_MOST, + heightSpec: LayoutSpec.AT_MOST, + } + })) + } +} \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricScrollerNode.h b/iOS/Pod/Classes/Shader/DoricScrollerNode.h index a61a48cb..a6120ef0 100644 --- a/iOS/Pod/Classes/Shader/DoricScrollerNode.h +++ b/iOS/Pod/Classes/Shader/DoricScrollerNode.h @@ -1,7 +1,18 @@ -// -// Created by pengfei.zhou on 2019/11/19. -// - +/* + * 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. + */ #import #import "DoricSuperNode.h" diff --git a/iOS/Pod/Classes/Shader/DoricScrollerNode.m b/iOS/Pod/Classes/Shader/DoricScrollerNode.m index 0c48225d..e0a36e55 100644 --- a/iOS/Pod/Classes/Shader/DoricScrollerNode.m +++ b/iOS/Pod/Classes/Shader/DoricScrollerNode.m @@ -1,7 +1,18 @@ -// -// Created by pengfei.zhou on 2019/11/19. -// - +/* + * 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. + */ #import "DoricScrollerNode.h" #import "DoricExtensions.h" diff --git a/js-framework/index.ts b/js-framework/index.ts index 8ad97435..02e3c137 100644 --- a/js-framework/index.ts +++ b/js-framework/index.ts @@ -15,7 +15,8 @@ */ export * from "./src/ui/view" export * from "./src/ui/layout" -export * from "./src/ui/listview" +export * from "./src/ui/list" +export * from "./src/ui/slider" export * from "./src/ui/scroller" export * from "./src/ui/widgets" export * from "./src/ui/panel" diff --git a/js-framework/src/ui/declarative.ts b/js-framework/src/ui/declarative.ts index 13ce2903..9a609cca 100644 --- a/js-framework/src/ui/declarative.ts +++ b/js-framework/src/ui/declarative.ts @@ -16,7 +16,8 @@ import { View, LayoutSpec } from './view' import { Stack, HLayout, VLayout } from './layout' import { IText, IImage, Text, Image } from './widgets' -import { IList, List } from './listview' +import { IList, List } from './list' +import { ISlider, Slider } from './slider' export function text(config: IText) { const ret = new Text @@ -84,4 +85,12 @@ export function list(config: IList) { Reflect.set(ret, key, Reflect.get(config, key, config), ret) } return ret +} + +export function slider(config: ISlider) { + const ret = new Slider + for (let key in config) { + Reflect.set(ret, key, Reflect.get(config, key, config), ret) + } + return ret } \ No newline at end of file diff --git a/js-framework/src/ui/listview.ts b/js-framework/src/ui/list.ts similarity index 100% rename from js-framework/src/ui/listview.ts rename to js-framework/src/ui/list.ts diff --git a/js-framework/src/ui/slider.ts b/js-framework/src/ui/slider.ts new file mode 100644 index 00000000..fe14c933 --- /dev/null +++ b/js-framework/src/ui/slider.ts @@ -0,0 +1,72 @@ +import { Superview, View, LayoutSpec, Property, IView } from "./view"; +import { Stack } from "./layout"; + +export function slideItem(item: View) { + return (new SlideItem).also((it) => { + it.layoutConfig = { + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + it.addChild(item) + }) +} + +export class SlideItem extends Stack { + /** + * Set to reuse native view + */ + @Property + identifier?: string +} + +export interface ISlider extends IView { + renderPage: (index: number) => SlideItem + itemCount: number + batchCount?: number +} + +export class Slider extends Superview implements ISlider { + private cachedViews: Map = new Map + + private ignoreDirtyCallOnce = false + + allSubviews() { + return this.cachedViews.values() + } + @Property + itemCount = 0 + + @Property + renderPage!: (index: number) => SlideItem + + @Property + batchCount = 3 + + + private getItem(itemIdx: number) { + let view = this.cachedViews.get(`${itemIdx}`) + if (view === undefined) { + view = this.renderPage(itemIdx) + view.superview = this + this.cachedViews.set(`${itemIdx}`, view) + } + return view + } + + isDirty() { + if (this.ignoreDirtyCallOnce) { + this.ignoreDirtyCallOnce = false + //Ignore the dirty call once. + return false + } + return super.isDirty() + } + + private renderBunchedItems(start: number, length: number) { + this.ignoreDirtyCallOnce = true; + return new Array(Math.min(length, this.itemCount - start)).fill(0).map((_, idx) => { + const slideItem = this.getItem(start + idx) + return slideItem.toModel() + }) + } +} \ No newline at end of file