diff --git a/Android/doric/src/main/java/pub/doric/DoricRegistry.java b/Android/doric/src/main/java/pub/doric/DoricRegistry.java index 9919509f..b15f70fa 100644 --- a/Android/doric/src/main/java/pub/doric/DoricRegistry.java +++ b/Android/doric/src/main/java/pub/doric/DoricRegistry.java @@ -29,6 +29,8 @@ import pub.doric.refresh.RefreshableNode; import pub.doric.shader.HLayoutNode; import pub.doric.shader.ImageNode; import pub.doric.shader.ScrollerNode; +import pub.doric.shader.flowlayout.FlowLayoutItemNode; +import pub.doric.shader.flowlayout.FlowLayoutNode; import pub.doric.shader.list.ListItemNode; import pub.doric.shader.list.ListNode; import pub.doric.shader.RootNode; @@ -98,6 +100,8 @@ public class DoricRegistry { this.registerViewNode(SliderNode.class); this.registerViewNode(SlideItemNode.class); this.registerViewNode(RefreshableNode.class); + this.registerViewNode(FlowLayoutNode.class); + this.registerViewNode(FlowLayoutItemNode.class); initRegistry(this); } diff --git a/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowAdapter.java b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowAdapter.java new file mode 100644 index 00000000..00c2f9fa --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowAdapter.java @@ -0,0 +1,137 @@ +/* + * 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.flowlayout; + +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 FlowAdapter extends RecyclerView.Adapter { + + private final FlowLayoutNode flowLayoutNode; + String renderItemFuncId; + int itemCount = 0; + int batchCount = 15; + SparseArray itemValues = new SparseArray<>(); + + FlowAdapter(FlowLayoutNode flowLayoutNode) { + this.flowLayoutNode = flowLayoutNode; + } + + @NonNull + @Override + public DoricViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + FlowLayoutItemNode node = (FlowLayoutItemNode) ViewNode.create(flowLayoutNode.getDoricContext(), "FlowLayoutItem"); + node.init(flowLayoutNode); + return new DoricViewHolder(node, node.getNodeView()); + } + + @Override + public void onBindViewHolder(@NonNull DoricViewHolder holder, int position) { + JSValue jsValue = getItemModel(position); + if (jsValue.isObject()) { + JSObject jsObject = jsValue.asObject(); + holder.flowLayoutItemNode.setId(jsObject.getProperty("id").asString().value()); + holder.flowLayoutItemNode.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 = flowLayoutNode.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); + flowLayoutNode.setSubModel(itemId, itemModel); + } + return flowLayoutNode.getSubModel(itemValues.get(position)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return new JSNull(); + } else { + JSObject childModel = flowLayoutNode.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 { + FlowLayoutItemNode flowLayoutItemNode; + + DoricViewHolder(FlowLayoutItemNode node, @NonNull View itemView) { + super(itemView); + flowLayoutItemNode = node; + } + } +} diff --git a/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutItemNode.java b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutItemNode.java new file mode 100644 index 00000000..643f9b87 --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutItemNode.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.flowlayout; + +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 = "FlowLayoutItem") +public class FlowLayoutItemNode extends StackNode { + public String identifier = ""; + + public FlowLayoutItemNode(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); + getNodeView().getLayoutParams().width = getLayoutParams().width; + getNodeView().getLayoutParams().height = getLayoutParams().height; + } +} diff --git a/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutNode.java b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutNode.java new file mode 100644 index 00000000..f2b46b09 --- /dev/null +++ b/Android/doric/src/main/java/pub/doric/shader/flowlayout/FlowLayoutNode.java @@ -0,0 +1,146 @@ +/* + * 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.flowlayout; + +import android.graphics.Rect; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; + +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; +import pub.doric.utils.DoricUtils; + +/** + * @Description: pub.doric.shader.flowlayout + * @Author: pengfei.zhou + * @CreateDate: 2019-11-28 + */ +@DoricPlugin(name = "FlowLayout") +public class FlowLayoutNode extends SuperNode { + private final FlowAdapter flowAdapter; + private final StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager( + 2, + StaggeredGridLayoutManager.VERTICAL); + private int columnSpace = 0; + private int rowSpace = 0; + private final RecyclerView.ItemDecoration spacingItemDecoration = new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { + outRect.set(columnSpace / 2, rowSpace / 2, columnSpace / 2, rowSpace / 2); + } + }; + + public FlowLayoutNode(DoricContext doricContext) { + super(doricContext); + this.flowAdapter = new FlowAdapter(this); + } + + @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; + } + FlowAdapter.DoricViewHolder viewHolder = (FlowAdapter.DoricViewHolder) mView.getChildViewHolder(view); + if (id.equals(viewHolder.flowLayoutItemNode.getId())) { + return viewHolder.flowLayoutItemNode; + } + } + return null; + } + + @Override + protected void blend(RecyclerView view, String name, JSValue prop) { + switch (name) { + case "columnSpace": + columnSpace = DoricUtils.dp2px(prop.asNumber().toFloat()); + mView.setPadding(-columnSpace / 2, mView.getPaddingTop(), -columnSpace / 2, mView.getPaddingBottom()); + break; + case "rowSpace": + rowSpace = DoricUtils.dp2px(prop.asNumber().toFloat()); + mView.setPadding(mView.getPaddingLeft(), -rowSpace / 2, mView.getPaddingRight(), -rowSpace / 2); + break; + case "columnCount": + staggeredGridLayoutManager.setSpanCount(prop.asNumber().toInt()); + break; + case "itemCount": + this.flowAdapter.itemCount = prop.asNumber().toInt(); + break; + case "renderItem": + this.flowAdapter.renderItemFuncId = prop.asString().value(); + // If reset renderItem,should reset native cache. + this.flowAdapter.itemValues.clear(); + clearSubModel(); + break; + case "batchCount": + this.flowAdapter.batchCount = prop.asNumber().toInt(); + break; + default: + super.blend(view, name, prop); + break; + } + } + + @Override + public void blend(JSObject jsObject) { + super.blend(jsObject); + if (mView != null) { + mView.post(new Runnable() { + @Override + public void run() { + flowAdapter.notifyDataSetChanged(); + } + }); + } + } + + @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()); + } else { + JSObject oldModel = getSubModel(viewId); + if (oldModel != null) { + recursiveMixin(subProperties, oldModel); + } + flowAdapter.blendSubNode(subProperties); + } + } + + @Override + protected RecyclerView build() { + RecyclerView recyclerView = new RecyclerView(getContext()); + recyclerView.setLayoutManager(staggeredGridLayoutManager); + recyclerView.setAdapter(flowAdapter); + recyclerView.addItemDecoration(spacingItemDecoration); + return recyclerView; + } +}