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 eb9886f9..ad02e812 100644 --- a/Android/app/src/main/java/pub/doric/demo/MainActivity.java +++ b/Android/app/src/main/java/pub/doric/demo/MainActivity.java @@ -29,6 +29,8 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import org.w3c.dom.Text; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -54,7 +56,9 @@ public class MainActivity extends AppCompatActivity { }); swipeLayout.setBackgroundColor(Color.YELLOW); swipeLayout.getRefreshView().setBackgroundColor(Color.RED); - swipeLayout.setPullDownHeight(300); + TextView textView = new TextView(this); + textView.setText("This is header"); + swipeLayout.getRefreshView().setContent(textView); RecyclerView recyclerView = findViewById(R.id.root); recyclerView.setBackgroundColor(Color.WHITE); recyclerView.setLayoutManager(new LinearLayoutManager(this)); diff --git a/Android/doric/src/main/java/pub/doric/DoricRegistry.java b/Android/doric/src/main/java/pub/doric/DoricRegistry.java index 12722dc4..c41c1b5a 100644 --- a/Android/doric/src/main/java/pub/doric/DoricRegistry.java +++ b/Android/doric/src/main/java/pub/doric/DoricRegistry.java @@ -25,6 +25,7 @@ import pub.doric.plugin.NavigatorPlugin; import pub.doric.plugin.NetworkPlugin; import pub.doric.plugin.ShaderPlugin; import pub.doric.plugin.StoragePlugin; +import pub.doric.pullable.RefreshableNode; import pub.doric.shader.HLayoutNode; import pub.doric.shader.ImageNode; import pub.doric.shader.ScrollerNode; @@ -96,6 +97,7 @@ public class DoricRegistry { this.registerViewNode(ScrollerNode.class); this.registerViewNode(SliderNode.class); this.registerViewNode(SlideItemNode.class); + this.registerViewNode(RefreshableNode.class); initRegistry(this); } diff --git a/Android/doric/src/main/java/pub/doric/pullable/DoricSwipeLayout.java b/Android/doric/src/main/java/pub/doric/pullable/DoricSwipeLayout.java index ef845880..a83ca478 100644 --- a/Android/doric/src/main/java/pub/doric/pullable/DoricSwipeLayout.java +++ b/Android/doric/src/main/java/pub/doric/pullable/DoricSwipeLayout.java @@ -287,7 +287,7 @@ public class DoricSwipeLayout extends ViewGroup implements NestedScrollingParent private void createProgressView() { mRefreshView = new DoricRefreshView(getContext()); - ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, 0); + ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); addView(mRefreshView, layoutParams); } @@ -441,8 +441,16 @@ public class DoricSwipeLayout extends ViewGroup implements NestedScrollingParent getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); - mRefreshView.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(mPullDownHeight, MeasureSpec.EXACTLY)); + mRefreshView.measure( + MeasureSpec.makeMeasureSpec( + getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec( + (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) / 3, + MeasureSpec.UNSPECIFIED)); + if (mPullDownHeight != mRefreshView.getMeasuredHeight()) { + setPullDownHeight(mRefreshView.getMeasuredHeight()); + } mCircleViewIndex = -1; // Get the index of the circleview. for (int index = 0; index < getChildCount(); index++) { diff --git a/Android/doric/src/main/java/pub/doric/pullable/RefreshableNode.java b/Android/doric/src/main/java/pub/doric/pullable/RefreshableNode.java new file mode 100644 index 00000000..37fbb31a --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/pullable/RefreshableNode.java @@ -0,0 +1,165 @@ +package pub.doric.pullable; + +import com.github.pengfeizhou.jscore.JSObject; +import com.github.pengfeizhou.jscore.JSValue; +import com.github.pengfeizhou.jscore.JavaValue; + +import pub.doric.DoricContext; +import pub.doric.extension.bridge.DoricMethod; +import pub.doric.extension.bridge.DoricPlugin; +import pub.doric.extension.bridge.DoricPromise; +import pub.doric.shader.SuperNode; +import pub.doric.shader.ViewNode; + +/** + * @Description: pub.doric.pullable + * @Author: pengfei.zhou + * @CreateDate: 2019-11-26 + */ +@DoricPlugin(name = "Refreshable") +public class RefreshableNode extends SuperNode { + + private String mContentViewId; + private ViewNode mContentNode; + + private String mHeaderViewId; + private ViewNode mHeaderNode; + + public RefreshableNode(DoricContext doricContext) { + super(doricContext); + } + + + @Override + protected DoricSwipeLayout build() { + return new DoricSwipeLayout(getContext()); + } + + @Override + protected void blend(DoricSwipeLayout view, String name, JSValue prop) { + if ("content".equals(name)) { + mContentViewId = prop.asString().value(); + } else if ("header".equals(name)) { + mHeaderViewId = prop.asString().value(); + } else { + super.blend(view, name, prop); + } + } + + @Override + public void blend(JSObject jsObject) { + super.blend(jsObject); + blendContentNode(); + blendHeadNode(); + } + + + private void blendContentNode() { + JSObject contentModel = getSubModel(mContentViewId); + if (contentModel == null) { + return; + } + String viewId = contentModel.getProperty("id").asString().value(); + String type = contentModel.getProperty("type").asString().value(); + JSObject props = contentModel.getProperty("props").asObject(); + if (mContentNode != null) { + if (mContentNode.getId().equals(viewId)) { + //skip + } else { + if (mReusable && type.equals(mContentNode.getType())) { + mContentNode.setId(viewId); + mContentNode.blend(props); + } else { + mView.removeAllViews(); + mContentNode = ViewNode.create(getDoricContext(), type); + mContentNode.setId(viewId); + mContentNode.init(this); + mContentNode.blend(props); + mView.addView(mContentNode.getDoricLayer()); + } + } + } else { + mContentNode = ViewNode.create(getDoricContext(), type); + mContentNode.setId(viewId); + mContentNode.init(this); + mContentNode.blend(props); + mView.addView(mContentNode.getDoricLayer()); + } + } + + private void blendHeadNode() { + JSObject headerModel = getSubModel(mHeaderViewId); + if (headerModel == null) { + return; + } + String viewId = headerModel.getProperty("id").asString().value(); + String type = headerModel.getProperty("type").asString().value(); + JSObject props = headerModel.getProperty("props").asObject(); + if (mHeaderNode != null) { + if (mHeaderNode.getId().equals(viewId)) { + //skip + } else { + if (mReusable && type.equals(mHeaderNode.getType())) { + mHeaderNode.setId(viewId); + mHeaderNode.blend(props); + } else { + mHeaderNode = ViewNode.create(getDoricContext(), type); + mHeaderNode.setId(viewId); + mHeaderNode.init(this); + mHeaderNode.blend(props); + mView.getRefreshView().setContent(mHeaderNode.getDoricLayer()); + } + } + } else { + mHeaderNode = ViewNode.create(getDoricContext(), type); + mHeaderNode.setId(viewId); + mHeaderNode.init(this); + mHeaderNode.blend(props); + mView.getRefreshView().setContent(mHeaderNode.getDoricLayer()); + } + } + + @Override + public ViewNode getSubNodeById(String id) { + if (id.equals(mContentViewId)) { + return mContentNode; + } + if (id.equals(mHeaderViewId)) { + return mHeaderNode; + } + return null; + } + + @Override + protected void blendSubNode(JSObject subProperties) { + String viewId = subProperties.getProperty("id").asString().value(); + ViewNode node = getSubNodeById(viewId); + if (node != null) { + node.blend(subProperties.getProperty("props").asObject()); + } + } + + @DoricMethod + public void setRefreshable(JSValue jsValue, DoricPromise doricPromise) { + boolean refreshable = jsValue.asBoolean().value(); + this.mView.setEnabled(refreshable); + doricPromise.resolve(); + } + + @DoricMethod + public void setRefreshing(JSValue jsValue, DoricPromise doricPromise) { + boolean refreshing = jsValue.asBoolean().value(); + this.mView.setRefreshing(refreshing); + doricPromise.resolve(); + } + + @DoricMethod + public void isRefreshable(DoricPromise doricPromise) { + doricPromise.resolve(new JavaValue(this.mView.isEnabled())); + } + + @DoricMethod + public void isRefreshing(DoricPromise doricPromise) { + doricPromise.resolve(new JavaValue(this.mView.isRefreshing())); + } +} diff --git a/demo/index.ts b/demo/index.ts index 6d413654..7b1bac66 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -12,4 +12,5 @@ export default [ 'src/StorageDemo', 'src/NavigatorDemo', 'src/NavbarDemo', + 'src/RefreshableDemo', ] \ No newline at end of file diff --git a/demo/src/RefreshableDemo.ts b/demo/src/RefreshableDemo.ts new file mode 100644 index 00000000..24d83750 --- /dev/null +++ b/demo/src/RefreshableDemo.ts @@ -0,0 +1,72 @@ +import { refreshable, Group, Panel, navbar, text, gravity, Color, Stack, LayoutSpec, list, NativeCall, listItem, log, vlayout, Gravity, hlayout, Text, scroller, layoutConfig, image, IView, IVLayout, ScaleType, modal, IText, network, navigator } from "doric"; +import { title, label, colors } from "./utils"; + +@Entry +class RefreshableDemo extends Panel { + build(rootView: Group): void { + let refreshView = refreshable({ + layoutConfig: layoutConfig().atmost(), + header: text({ + text: "This is Header", + width: 100, + height: 100, + layoutConfig: layoutConfig().exactly(), + }), + content: scroller(vlayout([ + title("Refreshable Demo"), + label('start Refresh').apply({ + width: 300, + height: 50, + bgColor: colors[0], + textSize: 30, + textColor: Color.WHITE, + layoutConfig: layoutConfig().exactly(), + onClick: () => { + refreshView.setRefreshing(context, true) + } + } as IText), + label('stop Refresh').apply({ + width: 300, + height: 50, + bgColor: colors[0], + textSize: 30, + textColor: Color.WHITE, + layoutConfig: layoutConfig().exactly(), + onClick: () => { + refreshView.setRefreshing(context, false) + } + } as IText), + + label('Enable Refresh').apply({ + width: 300, + height: 50, + bgColor: colors[0], + textSize: 30, + textColor: Color.WHITE, + layoutConfig: layoutConfig().exactly(), + onClick: () => { + refreshView.setRefreshable(context, true) + } + } as IText), + + label('Disable Refresh').apply({ + width: 300, + height: 50, + bgColor: colors[0], + textSize: 30, + textColor: Color.WHITE, + layoutConfig: layoutConfig().exactly(), + onClick: () => { + refreshView.setRefreshable(context, false) + } + } as IText), + ]).apply({ + layoutConfig: layoutConfig().atmost().h(LayoutSpec.WRAP_CONTENT), + gravity: gravity().center(), + space: 10, + } as IVLayout)).apply({ + layoutConfig: layoutConfig().atmost(), + }) + }).in(rootView) + } +} \ No newline at end of file diff --git a/js-framework/index.ts b/js-framework/index.ts index 8a5de55b..6fa48cae 100644 --- a/js-framework/index.ts +++ b/js-framework/index.ts @@ -21,6 +21,7 @@ export * from "./src/ui/scroller" export * from "./src/ui/widgets" export * from "./src/ui/panel" export * from "./src/ui/declarative" +export * from "./src/ui/refreshable" export * from "./src/util/color" export * from './src/util/log' export * from './src/util/types' diff --git a/js-framework/src/ui/refreshable.ts b/js-framework/src/ui/refreshable.ts new file mode 100644 index 00000000..f1ca4704 --- /dev/null +++ b/js-framework/src/ui/refreshable.ts @@ -0,0 +1,60 @@ +import { View, Property, Superview, IView } from "./view"; +import { List } from "./list"; +import { Scroller } from "./scroller"; +import { BridgeContext } from "../runtime/global"; +import { layoutConfig } from "./declarative"; + +export interface IRefreshable extends IView { + content: List | Scroller + header?: View + onRefresh?: () => void +} + +export class Refreshable extends Superview implements IRefreshable { + + content!: List | Scroller + + header?: View + + @Property + onRefresh?: () => void + + allSubviews() { + const ret: View[] = [this.content] + if (this.header) { + ret.push(this.header) + } + return ret + } + + setRefreshable(context: BridgeContext, refreshable: boolean) { + return this.nativeChannel(context, 'setRefreshable')(refreshable) + } + + setRefreshing(context: BridgeContext, refreshing: boolean) { + return this.nativeChannel(context, 'setRefreshing')(refreshing) + } + + isRefreshable(context: BridgeContext) { + return this.nativeChannel(context, 'isRefreshable')() as Promise + } + + isRefreshing(context: BridgeContext) { + return this.nativeChannel(context, 'isRefreshing')() as Promise + } + + toModel() { + this.dirtyProps.content = this.content.viewId + this.dirtyProps.header = (this.header || {}).viewId + return super.toModel() + } +} + +export function refreshable(config: IRefreshable) { + const ret = new Refreshable + ret.layoutConfig = layoutConfig().wrap() + for (let key in config) { + Reflect.set(ret, key, Reflect.get(config, key, config), ret) + } + return ret +} diff --git a/js-framework/src/ui/view.ts b/js-framework/src/ui/view.ts index 893bf4dc..baa1595f 100644 --- a/js-framework/src/ui/view.ts +++ b/js-framework/src/ui/view.ts @@ -266,7 +266,7 @@ export abstract class View implements Modeling, IView { nativeChannel(context: any, name: string) { let thisView: View | undefined = this - return function (...args: any) { + return function (args: any = undefined) { const func = context.shader.command const viewIds = [] while (thisView != undefined) {