feat:Fix List call onLoadMore multi times
This commit is contained in:
parent
438d7233ff
commit
112e45af06
@ -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: () => {
|
|
||||||
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,
|
itemCount: 0,
|
||||||
renderItem: () => new ListItem,
|
|
||||||
layoutConfig: {
|
layoutConfig: {
|
||||||
widthSpec: LayoutSpec.MOST,
|
widthSpec: LayoutSpec.MOST,
|
||||||
heightSpec: LayoutSpec.MOST,
|
heightSpec: LayoutSpec.JUST,
|
||||||
|
weight: 1
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
}),
|
|
||||||
|
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
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