Merge branch 'feature/dev' into 'master'
Feature/dev See merge request !32
This commit is contained in:
		| @@ -29,6 +29,8 @@ import pub.doric.refresh.RefreshableNode; | |||||||
| import pub.doric.shader.HLayoutNode; | import pub.doric.shader.HLayoutNode; | ||||||
| import pub.doric.shader.ImageNode; | import pub.doric.shader.ImageNode; | ||||||
| import pub.doric.shader.ScrollerNode; | 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.ListItemNode; | ||||||
| import pub.doric.shader.list.ListNode; | import pub.doric.shader.list.ListNode; | ||||||
| import pub.doric.shader.RootNode; | import pub.doric.shader.RootNode; | ||||||
| @@ -98,6 +100,8 @@ public class DoricRegistry { | |||||||
|         this.registerViewNode(SliderNode.class); |         this.registerViewNode(SliderNode.class); | ||||||
|         this.registerViewNode(SlideItemNode.class); |         this.registerViewNode(SlideItemNode.class); | ||||||
|         this.registerViewNode(RefreshableNode.class); |         this.registerViewNode(RefreshableNode.class); | ||||||
|  |         this.registerViewNode(FlowLayoutNode.class); | ||||||
|  |         this.registerViewNode(FlowLayoutItemNode.class); | ||||||
|         initRegistry(this); |         initRegistry(this); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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<FlowAdapter.DoricViewHolder> { | ||||||
|  |  | ||||||
|  |     private final FlowLayoutNode flowLayoutNode; | ||||||
|  |     String renderItemFuncId; | ||||||
|  |     int itemCount = 0; | ||||||
|  |     int batchCount = 15; | ||||||
|  |     SparseArray<String> 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<JSDecoder> 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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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<RecyclerView> { | ||||||
|  |     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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -13,4 +13,5 @@ export default [ | |||||||
|     'src/NavigatorDemo', |     'src/NavigatorDemo', | ||||||
|     'src/NavbarDemo', |     'src/NavbarDemo', | ||||||
|     'src/RefreshableDemo', |     'src/RefreshableDemo', | ||||||
|  |     'src/FlowLayoutDemo', | ||||||
| ] | ] | ||||||
| @@ -15,9 +15,9 @@ export default bundles.map(bundle => { | |||||||
|             commonjs() |             commonjs() | ||||||
|         ], |         ], | ||||||
|         external: ['reflect-metadata', 'doric'], |         external: ['reflect-metadata', 'doric'], | ||||||
|         onwarn: function(warning) { |         onwarn: function (warning) { | ||||||
|             if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; } |             if (warning.code === 'THIS_IS_UNDEFINED') { return; } | ||||||
|             console.warn( warning.message ); |             console.warn(warning.message); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| }) | }) | ||||||
							
								
								
									
										41
									
								
								demo/src/FlowLayoutDemo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								demo/src/FlowLayoutDemo.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | import { Group, Panel, flowlayout, layoutConfig, FlowLayoutItem, text, Color, IFlowLayout, LayoutSpec, Gravity } from "doric"; | ||||||
|  | import { colors, label } from "./utils"; | ||||||
|  |  | ||||||
|  | const imageUrls = [ | ||||||
|  |     'http://b.hiphotos.baidu.com/image/pic/item/908fa0ec08fa513db777cf78376d55fbb3fbd9b3.jpg', | ||||||
|  |     'http://f.hiphotos.baidu.com/image/pic/item/0e2442a7d933c8956c0e8eeadb1373f08202002a.jpg', | ||||||
|  |     'http://f.hiphotos.baidu.com/image/pic/item/b151f8198618367aa7f3cc7424738bd4b31ce525.jpg', | ||||||
|  |     'http://b.hiphotos.baidu.com/image/pic/item/0eb30f2442a7d9337119f7dba74bd11372f001e0.jpg', | ||||||
|  |     'http://a.hiphotos.baidu.com/image/h%3D300/sign=b38f3fc35b0fd9f9bf175369152cd42b/9a504fc2d5628535bdaac29e9aef76c6a6ef63c2.jpg', | ||||||
|  |     'http://h.hiphotos.baidu.com/image/pic/item/810a19d8bc3eb1354c94a704ac1ea8d3fd1f4439.jpg', | ||||||
|  |     'http://calonye.com/wp-content/uploads/2015/08/0-wx_fmtgiftpwebpwxfrom5wx_lazy1-9.gif', | ||||||
|  |     'http://hbimg.b0.upaiyun.com/ca29ea125b7f2d580f573e48eb594b1ef509757f34a08-m0hK45_fw658', | ||||||
|  |     'https://misc.aotu.io/ONE-SUNDAY/SteamEngine.png', | ||||||
|  | ] | ||||||
|  | @Entry | ||||||
|  | class FlowDemo extends Panel { | ||||||
|  |     build(rootView: Group): void { | ||||||
|  |         flowlayout({ | ||||||
|  |             layoutConfig: layoutConfig().atmost(), | ||||||
|  |             itemCount: 500, | ||||||
|  |             columnCount: 3, | ||||||
|  |             columnSpace: 10, | ||||||
|  |             rowSpace: 10, | ||||||
|  |             renderItem: (idx) => { | ||||||
|  |                 return new FlowLayoutItem().apply({ | ||||||
|  |                     bgColor: colors[idx % colors.length], | ||||||
|  |                     height: 50 + (idx % 3) * 20, | ||||||
|  |                     layoutConfig: layoutConfig().w(LayoutSpec.AT_MOST), | ||||||
|  |                 }).also(it => { | ||||||
|  |                     it.addChild(text({ | ||||||
|  |                         text: `${idx}`, | ||||||
|  |                         textColor: Color.WHITE, | ||||||
|  |                         textSize: 20, | ||||||
|  |                         layoutConfig: layoutConfig().wrap().a(Gravity.Center) | ||||||
|  |                     })) | ||||||
|  |                 }) | ||||||
|  |             }, | ||||||
|  |         }) | ||||||
|  |             .in(rootView) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -53,9 +53,19 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N | |||||||
|     return cell; |     return cell; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | - (BOOL)isSimulator { | ||||||
|  |     return TARGET_OS_SIMULATOR == 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | ||||||
|     if (indexPath.row == 0) { |     if (indexPath.row == 0) { | ||||||
|         [self.navigationController pushViewController:[QRScanViewController new] animated:NO]; |         if (self.isSimulator) { | ||||||
|  |             NSString *result = @"127.0.0.1"; | ||||||
|  |             [[DoricDriver instance] connectDevKit:[NSString stringWithFormat:@"ws://%@:7777", result]]; | ||||||
|  |             ShowToast([NSString stringWithFormat:@"Connected to %@", result], BOTTOM); | ||||||
|  |         } else { | ||||||
|  |             [self.navigationController pushViewController:[QRScanViewController new] animated:NO]; | ||||||
|  |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     NSString *file = self.demoFilePaths[(NSUInteger) indexPath.row]; |     NSString *file = self.demoFilePaths[(NSUInteger) indexPath.row]; | ||||||
|   | |||||||
| @@ -38,6 +38,8 @@ | |||||||
| #import "DoricNavigatorPlugin.h" | #import "DoricNavigatorPlugin.h" | ||||||
| #import "DoricNavBarPlugin.h" | #import "DoricNavBarPlugin.h" | ||||||
| #import "DoricRefreshableNode.h" | #import "DoricRefreshableNode.h" | ||||||
|  | #import "DoricFlowLayoutItemNode.h" | ||||||
|  | #import "DoricFlowLayoutNode.h" | ||||||
| 
 | 
 | ||||||
| @interface DoricRegistry () | @interface DoricRegistry () | ||||||
| 
 | 
 | ||||||
| @@ -78,6 +80,8 @@ - (void)innerRegister { | |||||||
|     [self registerViewNode:DoricSliderNode.class withName:@"Slider"]; |     [self registerViewNode:DoricSliderNode.class withName:@"Slider"]; | ||||||
|     [self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"]; |     [self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"]; | ||||||
|     [self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"]; |     [self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"]; | ||||||
|  |     [self registerViewNode:DoricFlowLayoutItemNode.class withName:@"FlowLayoutItem"]; | ||||||
|  |     [self registerViewNode:DoricFlowLayoutNode.class withName:@"FlowLayout"]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| - (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name { | - (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name { | ||||||
|   | |||||||
| @@ -1,3 +1,18 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
| // | // | ||||||
| // Created by pengfei.zhou on 2019/11/26. | // Created by pengfei.zhou on 2019/11/26. | ||||||
| // | // | ||||||
|   | |||||||
| @@ -1,3 +1,18 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
| // | // | ||||||
| // Created by pengfei.zhou on 2019/11/26. | // Created by pengfei.zhou on 2019/11/26. | ||||||
| // | // | ||||||
|   | |||||||
| @@ -1,3 +1,18 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
| // | // | ||||||
| // Created by pengfei.zhou on 2019/11/26. | // Created by pengfei.zhou on 2019/11/26. | ||||||
| // | // | ||||||
|   | |||||||
| @@ -1,3 +1,18 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
| // | // | ||||||
| // Created by pengfei.zhou on 2019/11/26. | // Created by pengfei.zhou on 2019/11/26. | ||||||
| // | // | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | // | ||||||
|  | // Created by pengfei.zhou on 2019/11/28. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | #import "DoricStackNode.h" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @interface DoricFlowLayoutItemNode : DoricStackNode | ||||||
|  | @end | ||||||
							
								
								
									
										48
									
								
								iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								iOS/Pod/Classes/Shader/DoricFlowLayoutItemNode.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | // | ||||||
|  | // Created by pengfei.zhou on 2019/11/28. | ||||||
|  | // | ||||||
|  | 
 | ||||||
|  | #import "DoricFlowLayoutItemNode.h" | ||||||
|  | 
 | ||||||
|  | @interface DoricFlowLayoutItemView : DoricStackView | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation DoricFlowLayoutItemView | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface DoricFlowLayoutItemNode () | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @implementation DoricFlowLayoutItemNode | ||||||
|  | - (instancetype)initWithContext:(DoricContext *)doricContext { | ||||||
|  |     if (self = [super initWithContext:doricContext]) { | ||||||
|  |         self.reusable = YES; | ||||||
|  |     } | ||||||
|  |     return self; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)initWithSuperNode:(DoricSuperNode *)superNode { | ||||||
|  |     [super initWithSuperNode:superNode]; | ||||||
|  |     self.reusable = YES; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (DoricStackView *)build { | ||||||
|  |     return [DoricFlowLayoutItemView new]; | ||||||
|  | } | ||||||
|  | @end | ||||||
							
								
								
									
										24
									
								
								iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								iOS/Pod/Classes/Shader/DoricFlowLayoutNode.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | // | ||||||
|  | // Created by pengfei.zhou on 2019/11/28. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | #import "DoricSuperNode.h" | ||||||
|  |  | ||||||
|  | @interface DoricFlowLayoutNode : DoricSuperNode<UICollectionView *> | ||||||
|  | @end | ||||||
							
								
								
									
										320
									
								
								iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								iOS/Pod/Classes/Shader/DoricFlowLayoutNode.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,320 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | // | ||||||
|  | // Created by pengfei.zhou on 2019/11/28. | ||||||
|  | // | ||||||
|  | 
 | ||||||
|  | #import "DoricFlowLayoutNode.h" | ||||||
|  | #import "DoricFlowLayoutItemNode.h" | ||||||
|  | #import "DoricExtensions.h" | ||||||
|  | #import <JavaScriptCore/JavaScriptCore.h> | ||||||
|  | 
 | ||||||
|  | @protocol DoricFlowLayoutDelegate | ||||||
|  | - (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath; | ||||||
|  | 
 | ||||||
|  | - (CGFloat)doricFlowLayoutColumnSpace; | ||||||
|  | 
 | ||||||
|  | - (CGFloat)doricFlowLayoutRowSpace; | ||||||
|  | 
 | ||||||
|  | - (NSInteger)doricFlowLayoutColumnCount; | ||||||
|  | 
 | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface DoricFlowLayout : UICollectionViewLayout | ||||||
|  | @property(nonatomic, readonly) NSInteger columnCount; | ||||||
|  | @property(nonatomic, readonly) CGFloat columnSpace; | ||||||
|  | @property(nonatomic, readonly) CGFloat rowSpace; | ||||||
|  | @property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSNumber *> *columnHeightInfo; | ||||||
|  | @property(nonatomic, weak) id <DoricFlowLayoutDelegate> delegate; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation DoricFlowLayout | ||||||
|  | - (instancetype)init { | ||||||
|  |     if (self = [super init]) { | ||||||
|  |         _columnHeightInfo = [NSMutableDictionary new]; | ||||||
|  |     } | ||||||
|  |     return self; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (NSInteger)columnCount { | ||||||
|  |     return self.delegate.doricFlowLayoutColumnCount ?: 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (CGFloat)columnSpace { | ||||||
|  |     return self.delegate.doricFlowLayoutColumnSpace ?: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (CGFloat)rowSpace { | ||||||
|  |     return self.delegate.doricFlowLayoutRowSpace ?: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { | ||||||
|  |     return YES; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)prepareLayout { | ||||||
|  |     [super prepareLayout]; | ||||||
|  |     for (int i = 0; i < self.columnCount; i++) { | ||||||
|  |         self.columnHeightInfo[@(i)] = @(0); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { | ||||||
|  |     for (int i = 0; i < self.columnCount; i++) { | ||||||
|  |         self.columnHeightInfo[@(i)] = @(0); | ||||||
|  |     } | ||||||
|  |     NSMutableArray *array = [NSMutableArray array]; | ||||||
|  |     NSInteger count = [self.collectionView numberOfItemsInSection:0]; | ||||||
|  |     for (int i = 0; i < count; i++) { | ||||||
|  |         UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; | ||||||
|  |         [array addObject:attrs]; | ||||||
|  |     } | ||||||
|  |     return array; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { | ||||||
|  |     NSNumber *minYOfColumn = @(0); | ||||||
|  |     for (NSNumber *key in self.columnHeightInfo.allKeys) { | ||||||
|  |         if ([self.columnHeightInfo[key] floatValue] < [self.columnHeightInfo[minYOfColumn] floatValue]) { | ||||||
|  |             minYOfColumn = key; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CGFloat width = (self.collectionView.width - self.columnSpace * (self.columnCount - 1)) / self.columnCount; | ||||||
|  |     CGFloat height = [self.delegate doricFlowLayoutItemHeightAtIndexPath:indexPath]; | ||||||
|  |     CGFloat x = (width + self.columnSpace) * [minYOfColumn integerValue]; | ||||||
|  |     CGFloat y = self.rowSpace + [self.columnHeightInfo[minYOfColumn] floatValue]; | ||||||
|  | 
 | ||||||
|  |     self.columnHeightInfo[minYOfColumn] = @(y + height); | ||||||
|  | 
 | ||||||
|  |     UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; | ||||||
|  |     attrs.frame = CGRectMake(x, y, width, height); | ||||||
|  |     return attrs; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (CGSize)collectionViewContentSize { | ||||||
|  |     CGFloat width = self.collectionView.width; | ||||||
|  |     CGFloat height = 0; | ||||||
|  |     for (NSNumber *column in self.columnHeightInfo.allValues) { | ||||||
|  |         height = MAX(height, [column floatValue]); | ||||||
|  |     } | ||||||
|  |     return CGSizeMake(width, height); | ||||||
|  | } | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface DoricFlowLayoutViewCell : UICollectionViewCell | ||||||
|  | @property(nonatomic, strong) DoricFlowLayoutItemNode *viewNode; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation DoricFlowLayoutViewCell | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface DoricFlowLayoutView : UICollectionView | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation DoricFlowLayoutView | ||||||
|  | - (CGSize)sizeThatFits:(CGSize)size { | ||||||
|  |     if (self.subviews.count > 0) { | ||||||
|  |         CGFloat width = size.width; | ||||||
|  |         CGFloat height = size.height; | ||||||
|  |         for (UIView *child in self.subviews) { | ||||||
|  |             CGSize childSize = [child measureSize:size]; | ||||||
|  |             width = MAX(childSize.width, width); | ||||||
|  |             height = MAX(childSize.height, height); | ||||||
|  |         } | ||||||
|  |         return CGSizeMake(width, height); | ||||||
|  |     } | ||||||
|  |     return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)layoutSelf:(CGSize)targetSize { | ||||||
|  |     [super layoutSelf:targetSize]; | ||||||
|  |     [self reloadData]; | ||||||
|  | } | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface DoricFlowLayoutNode () <UICollectionViewDataSource, UICollectionViewDelegate, DoricFlowLayoutDelegate> | ||||||
|  | @property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSString *> *itemViewIds; | ||||||
|  | @property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSValue *> *itemSizeInfo; | ||||||
|  | @property(nonatomic, assign) NSUInteger itemCount; | ||||||
|  | @property(nonatomic, assign) NSUInteger batchCount; | ||||||
|  | @property(nonatomic, assign) NSUInteger columnCount; | ||||||
|  | @property(nonatomic, assign) CGFloat columnSpace; | ||||||
|  | @property(nonatomic, assign) CGFloat rowSpace; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation DoricFlowLayoutNode | ||||||
|  | - (instancetype)initWithContext:(DoricContext *)doricContext { | ||||||
|  |     if (self = [super initWithContext:doricContext]) { | ||||||
|  |         _itemViewIds = [NSMutableDictionary new]; | ||||||
|  |         _itemSizeInfo = [NSMutableDictionary new]; | ||||||
|  |         _batchCount = 15; | ||||||
|  |         _columnCount = 2; | ||||||
|  |     } | ||||||
|  |     return self; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (UICollectionView *)build { | ||||||
|  |     DoricFlowLayout *flowLayout = [[DoricFlowLayout alloc] init]; | ||||||
|  |     flowLayout.delegate = self; | ||||||
|  |     return [[[DoricFlowLayoutView alloc] initWithFrame:CGRectZero | ||||||
|  |                                   collectionViewLayout:flowLayout] | ||||||
|  |             also:^(UICollectionView *it) { | ||||||
|  |                 it.backgroundColor = [UIColor whiteColor]; | ||||||
|  |                 it.pagingEnabled = YES; | ||||||
|  |                 it.delegate = self; | ||||||
|  |                 it.dataSource = self; | ||||||
|  |                 [it registerClass:[DoricFlowLayoutViewCell class] forCellWithReuseIdentifier:@"doricCell"]; | ||||||
|  |             }]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop { | ||||||
|  |     if ([@"columnSpace" isEqualToString:name]) { | ||||||
|  |         self.columnSpace = [prop floatValue]; | ||||||
|  |         [self.view.collectionViewLayout invalidateLayout]; | ||||||
|  |     } else if ([@"rowSpace" isEqualToString:name]) { | ||||||
|  |         self.rowSpace = [prop floatValue]; | ||||||
|  |         [self.view.collectionViewLayout invalidateLayout]; | ||||||
|  |     } else if ([@"columnCount" isEqualToString:name]) { | ||||||
|  |         self.columnCount = [prop unsignedIntegerValue]; | ||||||
|  |         [self.view reloadData]; | ||||||
|  |         [self.view.collectionViewLayout invalidateLayout]; | ||||||
|  |     } else if ([@"itemCount" isEqualToString:name]) { | ||||||
|  |         self.itemCount = [prop unsignedIntegerValue]; | ||||||
|  |         [self.view reloadData]; | ||||||
|  |     } else if ([@"renderItem" isEqualToString:name]) { | ||||||
|  |         [self.itemViewIds removeAllObjects]; | ||||||
|  |         [self clearSubModel]; | ||||||
|  |         [self.view reloadData]; | ||||||
|  |     } else if ([@"batchCount" isEqualToString:name]) { | ||||||
|  |         self.batchCount = [prop unsignedIntegerValue]; | ||||||
|  |     } else { | ||||||
|  |         [super blendView:view forPropName:name propValue:prop]; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (NSDictionary *)itemModelAt:(NSUInteger)position { | ||||||
|  |     NSString *viewId = self.itemViewIds[@(position)]; | ||||||
|  |     if (viewId && viewId.length > 0) { | ||||||
|  |         return [self subModelOf:viewId]; | ||||||
|  |     } else { | ||||||
|  |         DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil]; | ||||||
|  |         JSValue *models = [result waitUntilResult]; | ||||||
|  |         NSArray *array = [models toArray]; | ||||||
|  |         [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { | ||||||
|  |             NSString *thisViewId = obj[@"id"]; | ||||||
|  |             [self setSubModel:obj in:thisViewId]; | ||||||
|  |             NSUInteger pos = position + idx; | ||||||
|  |             self.itemViewIds[@(pos)] = thisViewId; | ||||||
|  |         }]; | ||||||
|  |         return array[0]; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { | ||||||
|  |     __block DoricViewNode *ret = nil; | ||||||
|  |     [self.doricContext.driver ensureSyncInMainQueue:^{ | ||||||
|  |         for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { | ||||||
|  |             if ([collectionViewCell isKindOfClass:[DoricFlowLayoutViewCell class]]) { | ||||||
|  |                 DoricFlowLayoutItemNode *node = ((DoricFlowLayoutViewCell *) collectionViewCell).viewNode; | ||||||
|  |                 if ([viewId isEqualToString:node.viewId]) { | ||||||
|  |                     ret = node; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }]; | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)blendSubNode:(NSDictionary *)subModel { | ||||||
|  |     NSString *viewId = subModel[@"id"]; | ||||||
|  |     DoricViewNode *viewNode = [self subNodeWithViewId:viewId]; | ||||||
|  |     if (viewNode) { | ||||||
|  |         [viewNode blend:subModel[@"props"]]; | ||||||
|  |     } else { | ||||||
|  |         NSMutableDictionary *model = [[self subModelOf:viewId] mutableCopy]; | ||||||
|  |         [self recursiveMixin:subModel to:model]; | ||||||
|  |         [self setSubModel:model in:viewId]; | ||||||
|  |     } | ||||||
|  |     [self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { | ||||||
|  |         if ([viewId isEqualToString:obj]) { | ||||||
|  |             *stop = YES; | ||||||
|  |             NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0]; | ||||||
|  |             [UIView performWithoutAnimation:^{ | ||||||
|  |                 [self.view reloadItemsAtIndexPaths:@[indexPath]]; | ||||||
|  |             }]; | ||||||
|  |         } | ||||||
|  |     }]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)callItem:(NSUInteger)position size:(CGSize)size { | ||||||
|  |     NSValue *old = self.itemSizeInfo[@(position)]; | ||||||
|  |     if (old && CGSizeEqualToSize([old CGSizeValue], size)) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     self.itemSizeInfo[@(position)] = [NSValue valueWithCGSize:size]; | ||||||
|  |     [self.view.collectionViewLayout invalidateLayout]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { | ||||||
|  |     return self.itemCount; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { | ||||||
|  |     NSUInteger position = (NSUInteger) indexPath.row; | ||||||
|  |     NSDictionary *model = [self itemModelAt:position]; | ||||||
|  |     NSDictionary *props = model[@"props"]; | ||||||
|  |     DoricFlowLayoutViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; | ||||||
|  |     if (!cell.viewNode) { | ||||||
|  |         DoricFlowLayoutItemNode *itemNode = [[DoricFlowLayoutItemNode alloc] initWithContext:self.doricContext]; | ||||||
|  |         [itemNode initWithSuperNode:self]; | ||||||
|  |         cell.viewNode = itemNode; | ||||||
|  |         [cell.contentView addSubview:itemNode.view]; | ||||||
|  |     } | ||||||
|  |     DoricFlowLayoutItemNode *node = cell.viewNode; | ||||||
|  |     node.viewId = model[@"id"]; | ||||||
|  |     [node blend:props]; | ||||||
|  |     CGFloat width = (collectionView.width - (self.columnCount - 1) * self.columnSpace) / self.columnCount; | ||||||
|  |     CGSize size = [node.view measureSize:CGSizeMake(width, collectionView.height)]; | ||||||
|  |     [node.view layoutSelf:size]; | ||||||
|  |     [self callItem:position size:size]; | ||||||
|  |     return cell; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath { | ||||||
|  |     NSUInteger position = (NSUInteger) indexPath.row; | ||||||
|  |     NSValue *value = self.itemSizeInfo[@(position)]; | ||||||
|  |     if (value) { | ||||||
|  |         return [value CGSizeValue].height; | ||||||
|  |     } else { | ||||||
|  |         return 100; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (CGFloat)doricFlowLayoutColumnSpace { | ||||||
|  |     return self.columnSpace; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (CGFloat)doricFlowLayoutRowSpace { | ||||||
|  |     return self.rowSpace; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (NSInteger)doricFlowLayoutColumnCount { | ||||||
|  |     return self.columnCount; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @end | ||||||
| @@ -24,11 +24,11 @@ | |||||||
| #import "Doric.h" | #import "Doric.h" | ||||||
| #import "DoricSlideItemNode.h" | #import "DoricSlideItemNode.h" | ||||||
| 
 | 
 | ||||||
| @interface DoricCollectionViewCell : UICollectionViewCell | @interface DoricSliderViewCell : UICollectionViewCell | ||||||
| @property(nonatomic, strong) DoricSlideItemNode *doricSlideItemNode; | @property(nonatomic, strong) DoricSlideItemNode *doricSlideItemNode; | ||||||
| @end | @end | ||||||
| 
 | 
 | ||||||
| @implementation DoricCollectionViewCell | @implementation DoricSliderViewCell | ||||||
| @end | @end | ||||||
| 
 | 
 | ||||||
| @interface DoricSliderNode () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> | @interface DoricSliderNode () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> | ||||||
| @@ -37,18 +37,20 @@ @interface DoricSliderNode () <UICollectionViewDataSource, UICollectionViewDeleg | |||||||
| @property(nonatomic, assign) NSUInteger batchCount; | @property(nonatomic, assign) NSUInteger batchCount; | ||||||
| @end | @end | ||||||
| 
 | 
 | ||||||
| @interface DoricCollectionView : UICollectionView | @interface DoricSliderView : UICollectionView | ||||||
| @end | @end | ||||||
| 
 | 
 | ||||||
| @implementation DoricCollectionView | @implementation DoricSliderView | ||||||
| - (CGSize)sizeThatFits:(CGSize)size { | - (CGSize)sizeThatFits:(CGSize)size { | ||||||
|     if (self.subviews.count > 0) { |     if (self.subviews.count > 0) { | ||||||
|  |         CGFloat width = size.width; | ||||||
|         CGFloat height = size.height; |         CGFloat height = size.height; | ||||||
|         for (UIView *child in self.subviews) { |         for (UIView *child in self.subviews) { | ||||||
|             CGSize childSize = [child measureSize:size]; |             CGSize childSize = [child measureSize:size]; | ||||||
|  |             width = MAX(childSize.width, width); | ||||||
|             height = MAX(childSize.height, height); |             height = MAX(childSize.height, height); | ||||||
|         } |         } | ||||||
|         return CGSizeMake(size.width, size.height); |         return CGSizeMake(width, height); | ||||||
|     } |     } | ||||||
|     return size; |     return size; | ||||||
| } | } | ||||||
| @@ -72,14 +74,14 @@ - (UICollectionView *)build { | |||||||
|     UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; |     UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; | ||||||
|     [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; |     [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; | ||||||
| 
 | 
 | ||||||
|     return [[[DoricCollectionView alloc] initWithFrame:CGRectZero |     return [[[DoricSliderView alloc] initWithFrame:CGRectZero | ||||||
|                                   collectionViewLayout:flowLayout] |                               collectionViewLayout:flowLayout] | ||||||
|             also:^(UICollectionView *it) { |             also:^(UICollectionView *it) { | ||||||
|                 it.backgroundColor = [UIColor whiteColor]; |                 it.backgroundColor = [UIColor whiteColor]; | ||||||
|                 it.pagingEnabled = YES; |                 it.pagingEnabled = YES; | ||||||
|                 it.delegate = self; |                 it.delegate = self; | ||||||
|                 it.dataSource = self; |                 it.dataSource = self; | ||||||
|                 [it registerClass:[DoricCollectionViewCell class] forCellWithReuseIdentifier:@"doricCell"]; |                 [it registerClass:[DoricSliderViewCell class] forCellWithReuseIdentifier:@"doricCell"]; | ||||||
|             }]; |             }]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -118,7 +120,7 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection | |||||||
|     NSUInteger position = (NSUInteger) indexPath.row; |     NSUInteger position = (NSUInteger) indexPath.row; | ||||||
|     NSDictionary *model = [self itemModelAt:position]; |     NSDictionary *model = [self itemModelAt:position]; | ||||||
|     NSDictionary *props = model[@"props"]; |     NSDictionary *props = model[@"props"]; | ||||||
|     DoricCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; |     DoricSliderViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; | ||||||
|     if (!cell.doricSlideItemNode) { |     if (!cell.doricSlideItemNode) { | ||||||
|         DoricSlideItemNode *slideItemNode = [[DoricSlideItemNode alloc] initWithContext:self.doricContext]; |         DoricSlideItemNode *slideItemNode = [[DoricSlideItemNode alloc] initWithContext:self.doricContext]; | ||||||
|         [slideItemNode initWithSuperNode:self]; |         [slideItemNode initWithSuperNode:self]; | ||||||
| @@ -159,8 +161,8 @@ - (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { | |||||||
|     __block DoricViewNode *ret = nil; |     __block DoricViewNode *ret = nil; | ||||||
|     [self.doricContext.driver ensureSyncInMainQueue:^{ |     [self.doricContext.driver ensureSyncInMainQueue:^{ | ||||||
|         for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { |         for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { | ||||||
|             if ([collectionViewCell isKindOfClass:[DoricCollectionViewCell class]]) { |             if ([collectionViewCell isKindOfClass:[DoricSliderViewCell class]]) { | ||||||
|                 DoricSlideItemNode *node = ((DoricCollectionViewCell *) collectionViewCell).doricSlideItemNode; |                 DoricSlideItemNode *node = ((DoricSliderViewCell *) collectionViewCell).doricSlideItemNode; | ||||||
|                 if ([viewId isEqualToString:node.viewId]) { |                 if ([viewId isEqualToString:node.viewId]) { | ||||||
|                     ret = node; |                     ret = node; | ||||||
|                     break; |                     break; | ||||||
|   | |||||||
							
								
								
									
										112
									
								
								js-framework/src/widget/flowlayout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								js-framework/src/widget/flowlayout.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | |||||||
|  | /* | ||||||
|  |  * 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 { Stack } from './layouts' | ||||||
|  | import { Property, IView, Superview, View } from '../ui/view' | ||||||
|  | import { layoutConfig } from '../util/index.util' | ||||||
|  |  | ||||||
|  | export class FlowLayoutItem extends Stack { | ||||||
|  |     /** | ||||||
|  |     * Set to reuse native view | ||||||
|  |     */ | ||||||
|  |     @Property | ||||||
|  |     identifier?: string | ||||||
|  | } | ||||||
|  | export interface IFlowLayout extends IView { | ||||||
|  |     renderItem: (index: number) => FlowLayoutItem | ||||||
|  |  | ||||||
|  |     itemCount: number | ||||||
|  |  | ||||||
|  |     batchCount?: number | ||||||
|  |  | ||||||
|  |     columnCount?: number | ||||||
|  |  | ||||||
|  |     columnSpace?: number | ||||||
|  |  | ||||||
|  |     rowSpace?: number | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class FlowLayout extends Superview implements IFlowLayout { | ||||||
|  |     private cachedViews: Map<string, FlowLayoutItem> = new Map | ||||||
|  |     private ignoreDirtyCallOnce = false | ||||||
|  |  | ||||||
|  |     allSubviews() { | ||||||
|  |         return this.cachedViews.values() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Property | ||||||
|  |     columnCount = 2 | ||||||
|  |  | ||||||
|  |     @Property | ||||||
|  |     columnSpace?: number | ||||||
|  |  | ||||||
|  |     @Property | ||||||
|  |     rowSpace?: number | ||||||
|  |  | ||||||
|  |     @Property | ||||||
|  |     itemCount = 0 | ||||||
|  |  | ||||||
|  |     @Property | ||||||
|  |     renderItem!: (index: number) => FlowLayoutItem | ||||||
|  |  | ||||||
|  |     @Property | ||||||
|  |     batchCount = 15 | ||||||
|  |  | ||||||
|  |     reset() { | ||||||
|  |         this.cachedViews.clear() | ||||||
|  |         this.itemCount = 0 | ||||||
|  |     } | ||||||
|  |     private getItem(itemIdx: number) { | ||||||
|  |         let view = this.cachedViews.get(`${itemIdx}`) | ||||||
|  |         if (view === undefined) { | ||||||
|  |             view = this.renderItem(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 listItem = this.getItem(start + idx) | ||||||
|  |             return listItem.toModel() | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function flowlayout(config: IFlowLayout) { | ||||||
|  |     const ret = new FlowLayout | ||||||
|  |     for (let key in config) { | ||||||
|  |         Reflect.set(ret, key, Reflect.get(config, key, config), ret) | ||||||
|  |     } | ||||||
|  |     return ret | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function flowItem(item: View) { | ||||||
|  |     return (new FlowLayoutItem).also((it) => { | ||||||
|  |         it.layoutConfig = layoutConfig().wrap() | ||||||
|  |         it.addChild(item) | ||||||
|  |     }) | ||||||
|  | } | ||||||
| @@ -20,3 +20,4 @@ export * from './list' | |||||||
| export * from './slider' | export * from './slider' | ||||||
| export * from './scroller' | export * from './scroller' | ||||||
| export * from './refreshable' | export * from './refreshable' | ||||||
|  | export * from './flowlayout' | ||||||
		Reference in New Issue
	
	Block a user