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.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); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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/NavbarDemo', | ||||
|     'src/RefreshableDemo', | ||||
|     'src/FlowLayoutDemo', | ||||
| ] | ||||
| @@ -15,9 +15,9 @@ export default bundles.map(bundle => { | ||||
|             commonjs() | ||||
|         ], | ||||
|         external: ['reflect-metadata', 'doric'], | ||||
|         onwarn: function(warning) { | ||||
|             if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; } | ||||
|             console.warn( warning.message ); | ||||
|         onwarn: function (warning) { | ||||
|             if (warning.code === 'THIS_IS_UNDEFINED') { return; } | ||||
|             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; | ||||
| } | ||||
| 
 | ||||
| - (BOOL)isSimulator { | ||||
|     return TARGET_OS_SIMULATOR == 1; | ||||
| } | ||||
| 
 | ||||
| - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | ||||
|     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; | ||||
|     } | ||||
|     NSString *file = self.demoFilePaths[(NSUInteger) indexPath.row]; | ||||
|   | ||||
| @@ -38,6 +38,8 @@ | ||||
| #import "DoricNavigatorPlugin.h" | ||||
| #import "DoricNavBarPlugin.h" | ||||
| #import "DoricRefreshableNode.h" | ||||
| #import "DoricFlowLayoutItemNode.h" | ||||
| #import "DoricFlowLayoutNode.h" | ||||
| 
 | ||||
| @interface DoricRegistry () | ||||
| 
 | ||||
| @@ -78,6 +80,8 @@ - (void)innerRegister { | ||||
|     [self registerViewNode:DoricSliderNode.class withName:@"Slider"]; | ||||
|     [self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"]; | ||||
|     [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 { | ||||
|   | ||||
| @@ -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. | ||||
| // | ||||
|   | ||||
| @@ -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. | ||||
| // | ||||
|   | ||||
| @@ -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. | ||||
| // | ||||
|   | ||||
| @@ -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. | ||||
| // | ||||
|   | ||||
							
								
								
									
										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 "DoricSlideItemNode.h" | ||||
| 
 | ||||
| @interface DoricCollectionViewCell : UICollectionViewCell | ||||
| @interface DoricSliderViewCell : UICollectionViewCell | ||||
| @property(nonatomic, strong) DoricSlideItemNode *doricSlideItemNode; | ||||
| @end | ||||
| 
 | ||||
| @implementation DoricCollectionViewCell | ||||
| @implementation DoricSliderViewCell | ||||
| @end | ||||
| 
 | ||||
| @interface DoricSliderNode () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> | ||||
| @@ -37,18 +37,20 @@ @interface DoricSliderNode () <UICollectionViewDataSource, UICollectionViewDeleg | ||||
| @property(nonatomic, assign) NSUInteger batchCount; | ||||
| @end | ||||
| 
 | ||||
| @interface DoricCollectionView : UICollectionView | ||||
| @interface DoricSliderView : UICollectionView | ||||
| @end | ||||
| 
 | ||||
| @implementation DoricCollectionView | ||||
| @implementation DoricSliderView | ||||
| - (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(size.width, size.height); | ||||
|         return CGSizeMake(width, height); | ||||
|     } | ||||
|     return size; | ||||
| } | ||||
| @@ -72,14 +74,14 @@ - (UICollectionView *)build { | ||||
|     UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; | ||||
|     [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; | ||||
| 
 | ||||
|     return [[[DoricCollectionView alloc] initWithFrame:CGRectZero | ||||
|                                   collectionViewLayout:flowLayout] | ||||
|     return [[[DoricSliderView alloc] initWithFrame:CGRectZero | ||||
|                               collectionViewLayout:flowLayout] | ||||
|             also:^(UICollectionView *it) { | ||||
|                 it.backgroundColor = [UIColor whiteColor]; | ||||
|                 it.pagingEnabled = YES; | ||||
|                 it.delegate = 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; | ||||
|     NSDictionary *model = [self itemModelAt:position]; | ||||
|     NSDictionary *props = model[@"props"]; | ||||
|     DoricCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; | ||||
|     DoricSliderViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; | ||||
|     if (!cell.doricSlideItemNode) { | ||||
|         DoricSlideItemNode *slideItemNode = [[DoricSlideItemNode alloc] initWithContext:self.doricContext]; | ||||
|         [slideItemNode initWithSuperNode:self]; | ||||
| @@ -159,8 +161,8 @@ - (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { | ||||
|     __block DoricViewNode *ret = nil; | ||||
|     [self.doricContext.driver ensureSyncInMainQueue:^{ | ||||
|         for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { | ||||
|             if ([collectionViewCell isKindOfClass:[DoricCollectionViewCell class]]) { | ||||
|                 DoricSlideItemNode *node = ((DoricCollectionViewCell *) collectionViewCell).doricSlideItemNode; | ||||
|             if ([collectionViewCell isKindOfClass:[DoricSliderViewCell class]]) { | ||||
|                 DoricSlideItemNode *node = ((DoricSliderViewCell *) collectionViewCell).doricSlideItemNode; | ||||
|                 if ([viewId isEqualToString:node.viewId]) { | ||||
|                     ret = node; | ||||
|                     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 './scroller' | ||||
| export * from './refreshable' | ||||
| export * from './flowlayout' | ||||
		Reference in New Issue
	
	Block a user