feat:Fix List call onLoadMore multi times
This commit is contained in:
		| @@ -45,6 +45,7 @@ class ListAdapter extends RecyclerView.Adapter<ListAdapter.DoricViewHolder> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private int itemCount = 0; |     private int itemCount = 0; | ||||||
|  |     private int loadAnchor = 0; | ||||||
|  |  | ||||||
|     @NonNull |     @NonNull | ||||||
|     @Override |     @Override | ||||||
| @@ -62,8 +63,8 @@ class ListAdapter extends RecyclerView.Adapter<ListAdapter.DoricViewHolder> { | |||||||
|             holder.listItemNode.setId(jsObject.getProperty("id").asString().value()); |             holder.listItemNode.setId(jsObject.getProperty("id").asString().value()); | ||||||
|             holder.listItemNode.blend(jsObject.getProperty("props").asObject()); |             holder.listItemNode.blend(jsObject.getProperty("props").asObject()); | ||||||
|         } |         } | ||||||
|         if (position >= this.listNode.itemCount) { |         if (position >= this.listNode.itemCount && !TextUtils.isEmpty(this.listNode.onLoadMoreFuncId)) { | ||||||
|             this.listNode.callJSResponse(this.listNode.onLoadMoreFuncId); |             callLoadMore(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -142,6 +143,13 @@ class ListAdapter extends RecyclerView.Adapter<ListAdapter.DoricViewHolder> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private void callLoadMore() { | ||||||
|  |         if (loadAnchor != itemCount) { | ||||||
|  |             loadAnchor = itemCount; | ||||||
|  |             this.listNode.callJSResponse(this.listNode.onLoadMoreFuncId); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     static class DoricViewHolder extends RecyclerView.ViewHolder { |     static class DoricViewHolder extends RecyclerView.ViewHolder { | ||||||
|         ListItemNode listItemNode; |         ListItemNode listItemNode; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ | |||||||
| package pub.doric.shader.list; | package pub.doric.shader.list; | ||||||
|  |  | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.util.Log; |  | ||||||
| import android.util.SparseArray; | import android.util.SparseArray; | ||||||
| import android.view.View; | import android.view.View; | ||||||
|  |  | ||||||
| @@ -63,7 +62,6 @@ public class ListNode extends SuperNode<RecyclerView> implements IDoricScrollabl | |||||||
|     private String onScrollFuncId; |     private String onScrollFuncId; | ||||||
|     private String onScrollEndFuncId; |     private String onScrollEndFuncId; | ||||||
|     private final DoricJSDispatcher jsDispatcher = new DoricJSDispatcher(); |     private final DoricJSDispatcher jsDispatcher = new DoricJSDispatcher(); | ||||||
|  |  | ||||||
|     public ListNode(DoricContext doricContext) { |     public ListNode(DoricContext doricContext) { | ||||||
|         super(doricContext); |         super(doricContext); | ||||||
|         this.listAdapter = new ListAdapter(this); |         this.listAdapter = new ListAdapter(this); | ||||||
|   | |||||||
| @@ -1,10 +1,37 @@ | |||||||
| import { Group, Panel, List, text, gravity, Color, LayoutSpec, list, listItem, log, vlayout, Gravity, hlayout, Text, refreshable, Refreshable, ListItem, layoutConfig } from "doric"; | import { Group, Panel, List, text, gravity, Color, LayoutSpec, list, listItem, log, vlayout, Gravity, hlayout, Text, refreshable, Refreshable, ListItem, layoutConfig, ViewHolder, ViewModel, VMPanel, loge } from "doric"; | ||||||
| import { rotatedArrow, colors } from "./utils"; |  | ||||||
| @Entry | interface ItemModel { | ||||||
| class ListPanel extends Panel { |     text: string | ||||||
|     build(rootView: Group): void { | } | ||||||
|         let refreshView: Refreshable |  | ||||||
|         let offset = Math.ceil(Math.random() * colors.length) | interface ListModel { | ||||||
|  |     end: boolean | ||||||
|  |     offset: number | ||||||
|  |     data: ItemModel[] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function loadData(offset: number): Promise<{ | ||||||
|  |     isEnd: boolean, | ||||||
|  |     data: ItemModel[] | ||||||
|  | }> { | ||||||
|  |     return new Promise<{ | ||||||
|  |         isEnd: boolean, | ||||||
|  |         data: ItemModel[] | ||||||
|  |     }>(resolve => { | ||||||
|  |         setTimeout(() => { | ||||||
|  |             resolve({ | ||||||
|  |                 isEnd: offset > 100, | ||||||
|  |                 data: new Array(5).fill(offset).map((e, idx) => { | ||||||
|  |                     return { text: `Item: ${e + idx}` } | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |         }, 1000) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ListVH extends ViewHolder { | ||||||
|  |     list!: List | ||||||
|  |     build(root: Group) { | ||||||
|         vlayout( |         vlayout( | ||||||
|             [ |             [ | ||||||
|                 text({ |                 text({ | ||||||
| @@ -19,106 +46,83 @@ class ListPanel extends Panel { | |||||||
|                     textAlignment: gravity().center(), |                     textAlignment: gravity().center(), | ||||||
|                     height: 50, |                     height: 50, | ||||||
|                 }), |                 }), | ||||||
|                 refreshView = refreshable({ |                 this.list = list({ | ||||||
|                     onRefresh: () => { |                     itemCount: 0, | ||||||
|                         refreshView.setRefreshing(context, false).then(() => { |                     layoutConfig: { | ||||||
|                             (refreshView.content as List).also(it => { |                         widthSpec: LayoutSpec.MOST, | ||||||
|                                 it.reset() |                         heightSpec: LayoutSpec.JUST, | ||||||
|                                 offset = Math.ceil(Math.random() * colors.length) |                         weight: 1 | ||||||
|                                 it.itemCount = 40 |  | ||||||
|                                 it.loadMore = true |  | ||||||
|                                 it.onLoadMore = () => { |  | ||||||
|                                     setTimeout(() => { |  | ||||||
|                                         it.itemCount += 10 |  | ||||||
|                                     }, 1000) |  | ||||||
|                                 } |  | ||||||
|                                 it.loadMoreView = listItem(text({ |  | ||||||
|                                     text: "Loading", |  | ||||||
|                                     layoutConfig: layoutConfig().most().configHeight(LayoutSpec.JUST).configAlignment(Gravity.Center), |  | ||||||
|                                     height: 50, |  | ||||||
|                                 })) |  | ||||||
|                                 it.renderItem = (idx: number) => { |  | ||||||
|                                     let counter!: Text |  | ||||||
|                                     return listItem( |  | ||||||
|                                         hlayout([ |  | ||||||
|                                             text({ |  | ||||||
|                                                 layoutConfig: { |  | ||||||
|                                                     widthSpec: LayoutSpec.FIT, |  | ||||||
|                                                     heightSpec: LayoutSpec.JUST, |  | ||||||
|                                                     alignment: gravity().center(), |  | ||||||
|                                                 }, |  | ||||||
|                                                 text: `Cell At Line ${idx}`, |  | ||||||
|                                                 textAlignment: gravity().center(), |  | ||||||
|                                                 textColor: Color.parse("#ffffff"), |  | ||||||
|                                                 textSize: 20, |  | ||||||
|                                                 height: 50, |  | ||||||
|                                             }), |  | ||||||
|                                             text({ |  | ||||||
|                                                 textColor: Color.parse("#ffffff"), |  | ||||||
|                                                 textSize: 20, |  | ||||||
|                                                 text: "", |  | ||||||
|                                             }).also(it => { |  | ||||||
|                                                 counter = it |  | ||||||
|                                                 it.layoutConfig = { |  | ||||||
|                                                     widthSpec: LayoutSpec.FIT, |  | ||||||
|                                                     heightSpec: LayoutSpec.FIT, |  | ||||||
|                                                     margin: { |  | ||||||
|                                                         left: 10, |  | ||||||
|                                                     } |  | ||||||
|                                                 } |  | ||||||
|                                             }) |  | ||||||
|                                         ]).also(it => { |  | ||||||
|                                             it.layoutConfig = { |  | ||||||
|                                                 widthSpec: LayoutSpec.MOST, |  | ||||||
|                                                 heightSpec: LayoutSpec.FIT, |  | ||||||
|                                                 margin: { |  | ||||||
|                                                     bottom: 2, |  | ||||||
|                                                 } |  | ||||||
|                                             } |  | ||||||
|                                             it.gravity = gravity().center() |  | ||||||
|                                             it.backgroundColor = colors[(idx + offset) % colors.length] |  | ||||||
|                                             let clicked = 0 |  | ||||||
|                                             it.onClick = () => { |  | ||||||
|                                                 counter.text = `Item Clicked ${++clicked}` |  | ||||||
|                                             } |  | ||||||
|                                         }) |  | ||||||
|                                     ).also(it => { |  | ||||||
|                                         it.layoutConfig = { |  | ||||||
|                                             widthSpec: LayoutSpec.MOST, |  | ||||||
|                                             heightSpec: LayoutSpec.FIT, |  | ||||||
|                                         } |  | ||||||
|                                         it.onClick = () => { |  | ||||||
|                                             log(`Click item at ${idx}`) |  | ||||||
|                                             it.height += 10 |  | ||||||
|                                             it.nativeChannel(context, "getWidth")().then( |  | ||||||
|                                                 resolve => { |  | ||||||
|                                                     log(`resolve,${resolve}`) |  | ||||||
|                                                 }, |  | ||||||
|                                                 reject => { |  | ||||||
|                                                     log(`reject,${reject}`) |  | ||||||
|                                                 }) |  | ||||||
|                                         } |  | ||||||
|                                     }) |  | ||||||
|                                 } |  | ||||||
|                             }) |  | ||||||
|                         }) |  | ||||||
|                     }, |                     }, | ||||||
|                     header: rotatedArrow(), |                 }) | ||||||
|                     content: list({ |  | ||||||
|                         itemCount: 0, |  | ||||||
|                         renderItem: () => new ListItem, |  | ||||||
|                         layoutConfig: { |  | ||||||
|                             widthSpec: LayoutSpec.MOST, |  | ||||||
|                             heightSpec: LayoutSpec.MOST, |  | ||||||
|                         }, |  | ||||||
|                     }), |  | ||||||
|                 }), |  | ||||||
|  |  | ||||||
|             ], |             ], | ||||||
|             { |             { | ||||||
|                 layoutConfig: layoutConfig().most(), |                 layoutConfig: layoutConfig().most(), | ||||||
|                 backgroundColor: Color.WHITE |                 backgroundColor: Color.WHITE | ||||||
|             }).in(rootView) |             }).in(root) | ||||||
|         refreshView.backgroundColor = Color.YELLOW |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ListVM extends ViewModel<ListModel, ListVH> { | ||||||
|  |     onAttached(state: ListModel, vh: ListVH) { | ||||||
|  |         vh.list.apply({ | ||||||
|  |             renderItem: (index) => { | ||||||
|  |                 const data = state.data[index] | ||||||
|  |                 return listItem(text({ | ||||||
|  |                     text: data.text, | ||||||
|  |                     textSize: 20, | ||||||
|  |                     layoutConfig: { | ||||||
|  |                         widthSpec: LayoutSpec.MOST, | ||||||
|  |                         heightSpec: LayoutSpec.JUST, | ||||||
|  |                     }, | ||||||
|  |                     height: 50 | ||||||
|  |                 }), { | ||||||
|  |                     layoutConfig: { | ||||||
|  |                         widthSpec: LayoutSpec.MOST, | ||||||
|  |                         heightSpec: LayoutSpec.FIT, | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  |             }, | ||||||
|  |             onLoadMore: async () => { | ||||||
|  |                 loge(`LoadMore,offset:${state.offset}`) | ||||||
|  |                 const ret = await loadData(state.offset) | ||||||
|  |                 this.updateState(state => { | ||||||
|  |                     state.end = ret.isEnd | ||||||
|  |                     state.data = state.data.concat(ret.data) | ||||||
|  |                     state.offset = state.data.length | ||||||
|  |                 }) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         loadData(state.offset).then(ret => { | ||||||
|  |             this.updateState(state => { | ||||||
|  |                 state.end = ret.isEnd | ||||||
|  |                 state.data = state.data.concat(ret.data) | ||||||
|  |                 state.offset = state.data.length | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onBind(state: ListModel, vh: ListVH) { | ||||||
|  |         vh.list.apply({ | ||||||
|  |             itemCount: state.data.length, | ||||||
|  |             loadMore: !state.end | ||||||
|  |         }) | ||||||
|  |         loge(`onBind,itemCount:${vh.list.itemCount}`) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Entry | ||||||
|  | class ListPanel extends VMPanel<ListModel, ListVH> { | ||||||
|  |     getViewModelClass() { | ||||||
|  |         return ListVM | ||||||
|  |     } | ||||||
|  |     getViewHolderClass() { | ||||||
|  |         return ListVH | ||||||
|  |     } | ||||||
|  |     getState() { | ||||||
|  |         return { | ||||||
|  |             end: true, | ||||||
|  |             data: [], | ||||||
|  |             offset: 0 | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										124
									
								
								doric-demo/src/RefreshDemo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								doric-demo/src/RefreshDemo.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | import { Group, Panel, List, text, gravity, Color, LayoutSpec, list, listItem, log, vlayout, Gravity, hlayout, Text, refreshable, Refreshable, ListItem, layoutConfig } from "doric"; | ||||||
|  | import { rotatedArrow, colors } from "./utils"; | ||||||
|  | @Entry | ||||||
|  | class ListPanel extends Panel { | ||||||
|  |     build(rootView: Group): void { | ||||||
|  |         let refreshView: Refreshable | ||||||
|  |         let offset = Math.ceil(Math.random() * colors.length) | ||||||
|  |         vlayout( | ||||||
|  |             [ | ||||||
|  |                 text({ | ||||||
|  |                     text: "ListDemo", | ||||||
|  |                     layoutConfig: { | ||||||
|  |                         widthSpec: LayoutSpec.MOST, | ||||||
|  |                         heightSpec: LayoutSpec.JUST, | ||||||
|  |                     }, | ||||||
|  |                     textSize: 30, | ||||||
|  |                     textColor: Color.parse("#535c68"), | ||||||
|  |                     backgroundColor: Color.parse("#dff9fb"), | ||||||
|  |                     textAlignment: gravity().center(), | ||||||
|  |                     height: 50, | ||||||
|  |                 }), | ||||||
|  |                 refreshView = refreshable({ | ||||||
|  |                     onRefresh: () => { | ||||||
|  |                         refreshView.setRefreshing(context, false).then(() => { | ||||||
|  |                             (refreshView.content as List).also(it => { | ||||||
|  |                                 it.reset() | ||||||
|  |                                 offset = Math.ceil(Math.random() * colors.length) | ||||||
|  |                                 it.itemCount = 40 | ||||||
|  |                                 it.loadMore = true | ||||||
|  |                                 it.onLoadMore = () => { | ||||||
|  |                                     setTimeout(() => { | ||||||
|  |                                         it.itemCount += 10 | ||||||
|  |                                     }, 1000) | ||||||
|  |                                 } | ||||||
|  |                                 it.loadMoreView = listItem(text({ | ||||||
|  |                                     text: "Loading", | ||||||
|  |                                     layoutConfig: layoutConfig().most().configHeight(LayoutSpec.JUST).configAlignment(Gravity.Center), | ||||||
|  |                                     height: 50, | ||||||
|  |                                 })) | ||||||
|  |                                 it.renderItem = (idx: number) => { | ||||||
|  |                                     let counter!: Text | ||||||
|  |                                     return listItem( | ||||||
|  |                                         hlayout([ | ||||||
|  |                                             text({ | ||||||
|  |                                                 layoutConfig: { | ||||||
|  |                                                     widthSpec: LayoutSpec.FIT, | ||||||
|  |                                                     heightSpec: LayoutSpec.JUST, | ||||||
|  |                                                     alignment: gravity().center(), | ||||||
|  |                                                 }, | ||||||
|  |                                                 text: `Cell At Line ${idx}`, | ||||||
|  |                                                 textAlignment: gravity().center(), | ||||||
|  |                                                 textColor: Color.parse("#ffffff"), | ||||||
|  |                                                 textSize: 20, | ||||||
|  |                                                 height: 50, | ||||||
|  |                                             }), | ||||||
|  |                                             text({ | ||||||
|  |                                                 textColor: Color.parse("#ffffff"), | ||||||
|  |                                                 textSize: 20, | ||||||
|  |                                                 text: "", | ||||||
|  |                                             }).also(it => { | ||||||
|  |                                                 counter = it | ||||||
|  |                                                 it.layoutConfig = { | ||||||
|  |                                                     widthSpec: LayoutSpec.FIT, | ||||||
|  |                                                     heightSpec: LayoutSpec.FIT, | ||||||
|  |                                                     margin: { | ||||||
|  |                                                         left: 10, | ||||||
|  |                                                     } | ||||||
|  |                                                 } | ||||||
|  |                                             }) | ||||||
|  |                                         ]).also(it => { | ||||||
|  |                                             it.layoutConfig = { | ||||||
|  |                                                 widthSpec: LayoutSpec.MOST, | ||||||
|  |                                                 heightSpec: LayoutSpec.FIT, | ||||||
|  |                                                 margin: { | ||||||
|  |                                                     bottom: 2, | ||||||
|  |                                                 } | ||||||
|  |                                             } | ||||||
|  |                                             it.gravity = gravity().center() | ||||||
|  |                                             it.backgroundColor = colors[(idx + offset) % colors.length] | ||||||
|  |                                             let clicked = 0 | ||||||
|  |                                             it.onClick = () => { | ||||||
|  |                                                 counter.text = `Item Clicked ${++clicked}` | ||||||
|  |                                             } | ||||||
|  |                                         }) | ||||||
|  |                                     ).also(it => { | ||||||
|  |                                         it.layoutConfig = { | ||||||
|  |                                             widthSpec: LayoutSpec.MOST, | ||||||
|  |                                             heightSpec: LayoutSpec.FIT, | ||||||
|  |                                         } | ||||||
|  |                                         it.onClick = () => { | ||||||
|  |                                             log(`Click item at ${idx}`) | ||||||
|  |                                             it.height += 10 | ||||||
|  |                                             it.nativeChannel(context, "getWidth")().then( | ||||||
|  |                                                 resolve => { | ||||||
|  |                                                     log(`resolve,${resolve}`) | ||||||
|  |                                                 }, | ||||||
|  |                                                 reject => { | ||||||
|  |                                                     log(`reject,${reject}`) | ||||||
|  |                                                 }) | ||||||
|  |                                         } | ||||||
|  |                                     }) | ||||||
|  |                                 } | ||||||
|  |                             }) | ||||||
|  |                         }) | ||||||
|  |                     }, | ||||||
|  |                     header: rotatedArrow(), | ||||||
|  |                     content: list({ | ||||||
|  |                         itemCount: 0, | ||||||
|  |                         renderItem: () => new ListItem, | ||||||
|  |                         layoutConfig: { | ||||||
|  |                             widthSpec: LayoutSpec.MOST, | ||||||
|  |                             heightSpec: LayoutSpec.MOST, | ||||||
|  |                         }, | ||||||
|  |                     }), | ||||||
|  |                 }), | ||||||
|  |  | ||||||
|  |             ], | ||||||
|  |             { | ||||||
|  |                 layoutConfig: layoutConfig().most(), | ||||||
|  |                 backgroundColor: Color.WHITE | ||||||
|  |             }).in(rootView) | ||||||
|  |         refreshView.backgroundColor = Color.YELLOW | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -47,6 +47,7 @@ @interface DoricListNode () <UITableViewDataSource, UITableViewDelegate> | |||||||
| @property(nonatomic, copy) NSString *renderItemFuncId; | @property(nonatomic, copy) NSString *renderItemFuncId; | ||||||
| @property(nonatomic, copy) NSString *loadMoreViewId; | @property(nonatomic, copy) NSString *loadMoreViewId; | ||||||
| @property(nonatomic, assign) BOOL loadMore; | @property(nonatomic, assign) BOOL loadMore; | ||||||
|  | @property(nonatomic, assign) NSUInteger loadAnchor; | ||||||
| @property(nonatomic, strong) NSMutableSet <DoricDidScrollBlock> *didScrollBlocks; | @property(nonatomic, strong) NSMutableSet <DoricDidScrollBlock> *didScrollBlocks; | ||||||
| @property(nonatomic, copy) NSString *onScrollFuncId; | @property(nonatomic, copy) NSString *onScrollFuncId; | ||||||
| @property(nonatomic, copy) NSString *onScrollEndFuncId; | @property(nonatomic, copy) NSString *onScrollEndFuncId; | ||||||
| @@ -124,6 +125,13 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger | |||||||
|     return self.itemCount + (self.loadMore ? 1 : 0); |     return self.itemCount + (self.loadMore ? 1 : 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | - (void)callLoadMore { | ||||||
|  |     if (self.itemCount != self.loadAnchor) { | ||||||
|  |         self.loadAnchor = self.itemCount; | ||||||
|  |         [self callJSResponse:self.onLoadMoreFuncId, nil]; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { | ||||||
|     NSUInteger position = (NSUInteger) indexPath.row; |     NSUInteger position = (NSUInteger) indexPath.row; | ||||||
|     NSDictionary *model = [self itemModelAt:position]; |     NSDictionary *model = [self itemModelAt:position]; | ||||||
| @@ -131,7 +139,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N | |||||||
|     NSString *reuseId = props[@"identifier"]; |     NSString *reuseId = props[@"identifier"]; | ||||||
|     if (position > 0 && position >= self.itemCount && self.onLoadMoreFuncId) { |     if (position > 0 && position >= self.itemCount && self.onLoadMoreFuncId) { | ||||||
|         reuseId = @"doricLoadMoreCell"; |         reuseId = @"doricLoadMoreCell"; | ||||||
|         [self callJSResponse:self.onLoadMoreFuncId, nil]; |         [self callLoadMore]; | ||||||
|     } |     } | ||||||
|     DoricTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId ?: @"doriccell"]; |     DoricTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId ?: @"doriccell"]; | ||||||
|     if (!cell) { |     if (!cell) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user