Android: implement horizontal list
This commit is contained in:
parent
9af7f389bc
commit
e69465e203
@ -68,6 +68,8 @@ import pub.doric.shader.ViewNode;
|
|||||||
import pub.doric.shader.flex.FlexNode;
|
import pub.doric.shader.flex.FlexNode;
|
||||||
import pub.doric.shader.flowlayout.FlowLayoutItemNode;
|
import pub.doric.shader.flowlayout.FlowLayoutItemNode;
|
||||||
import pub.doric.shader.flowlayout.FlowLayoutNode;
|
import pub.doric.shader.flowlayout.FlowLayoutNode;
|
||||||
|
import pub.doric.shader.horizontallist.HorizontalListItemNode;
|
||||||
|
import pub.doric.shader.horizontallist.HorizontalListNode;
|
||||||
import pub.doric.shader.list.ListItemNode;
|
import pub.doric.shader.list.ListItemNode;
|
||||||
import pub.doric.shader.list.ListNode;
|
import pub.doric.shader.list.ListNode;
|
||||||
import pub.doric.shader.slider.NestedSliderNode;
|
import pub.doric.shader.slider.NestedSliderNode;
|
||||||
@ -140,6 +142,8 @@ public class DoricRegistry {
|
|||||||
this.registerViewNode(GestureContainerNode.class);
|
this.registerViewNode(GestureContainerNode.class);
|
||||||
this.registerViewNode(BlurEffectViewNode.class);
|
this.registerViewNode(BlurEffectViewNode.class);
|
||||||
this.registerViewNode(AeroEffectViewNode.class);
|
this.registerViewNode(AeroEffectViewNode.class);
|
||||||
|
this.registerViewNode(HorizontalListNode.class);
|
||||||
|
this.registerViewNode(HorizontalListItemNode.class);
|
||||||
this.getResourceManager().registerLoader(new DoricAndroidLoader("drawable"));
|
this.getResourceManager().registerLoader(new DoricAndroidLoader("drawable"));
|
||||||
this.getResourceManager().registerLoader(new DoricAndroidLoader("raw"));
|
this.getResourceManager().registerLoader(new DoricAndroidLoader("raw"));
|
||||||
this.getResourceManager().registerLoader(new DoricAndroidAssetsLoader());
|
this.getResourceManager().registerLoader(new DoricAndroidAssetsLoader());
|
||||||
|
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2022] [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.horizontallist;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
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: pub.doric.shader.horizontallist
|
||||||
|
* @Author: jingpeng.wang
|
||||||
|
* @CreateDate: 2022-8-22
|
||||||
|
*/
|
||||||
|
class HorizontalListAdapter extends RecyclerView.Adapter<HorizontalListAdapter.DoricViewHolder> {
|
||||||
|
private static final int TYPE_LOAD_MORE = -1;
|
||||||
|
private final HorizontalListNode horizontalListNode;
|
||||||
|
|
||||||
|
HorizontalListAdapter(HorizontalListNode horizontalListNode) {
|
||||||
|
this.horizontalListNode = horizontalListNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
int itemCount = 0;
|
||||||
|
int loadAnchor = -1;
|
||||||
|
boolean loadMore = false;
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public DoricViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
HorizontalListItemNode node = (HorizontalListItemNode) ViewNode.create(horizontalListNode.getDoricContext(), "HorizontalListItem");
|
||||||
|
node.init(horizontalListNode);
|
||||||
|
return new DoricViewHolder(node, node.getNodeView());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull DoricViewHolder holder, int position) {
|
||||||
|
JSValue jsValue = getItemModel(position);
|
||||||
|
if (jsValue != null && jsValue.isObject()) {
|
||||||
|
JSObject jsObject = jsValue.asObject();
|
||||||
|
holder.horizontalListItemNode.setId(jsObject.getProperty("id").asString().value());
|
||||||
|
holder.horizontalListItemNode.reset();
|
||||||
|
holder.horizontalListItemNode.blend(jsObject.getProperty("props").asObject());
|
||||||
|
}
|
||||||
|
if (this.loadMore
|
||||||
|
&& position >= this.itemCount
|
||||||
|
&& !TextUtils.isEmpty(this.horizontalListNode.onLoadMoreFuncId)) {
|
||||||
|
callLoadMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return this.itemCount + (this.loadMore ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (position >= this.itemCount) {
|
||||||
|
return TYPE_LOAD_MORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue value = getItemModel(position);
|
||||||
|
if (value != null && value.isObject()) {
|
||||||
|
if (value.asObject().getProperty("props").asObject().getProperty("identifier").isString()) {
|
||||||
|
return value.asObject().getProperty("props").asObject().getProperty("identifier").asString().value().hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.getItemViewType(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSValue getItemModel(int position) {
|
||||||
|
if (position >= this.itemCount) {
|
||||||
|
return this.horizontalListNode.getSubModel(this.horizontalListNode.loadMoreViewId);
|
||||||
|
}
|
||||||
|
String id = horizontalListNode.itemValues.get(position);
|
||||||
|
if (TextUtils.isEmpty(id)) {
|
||||||
|
int batchCount = horizontalListNode.batchCount;
|
||||||
|
int start = position;
|
||||||
|
while (start > 0 && TextUtils.isEmpty(horizontalListNode.itemValues.get(start - 1))) {
|
||||||
|
start--;
|
||||||
|
batchCount++;
|
||||||
|
}
|
||||||
|
AsyncResult<JSDecoder> asyncResult = horizontalListNode.pureCallJSResponse(
|
||||||
|
"renderBunchedItems",
|
||||||
|
start,
|
||||||
|
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();
|
||||||
|
horizontalListNode.itemValues.put(i + start, itemId);
|
||||||
|
horizontalListNode.setSubModel(itemId, itemModel);
|
||||||
|
}
|
||||||
|
return horizontalListNode.getSubModel(horizontalListNode.itemValues.get(position));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return new JSNull();
|
||||||
|
} else {
|
||||||
|
JSObject childModel = horizontalListNode.getSubModel(id);
|
||||||
|
if (childModel == null) {
|
||||||
|
return new JSNull();
|
||||||
|
} else {
|
||||||
|
return childModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void blendSubNode(JSObject subProperties) {
|
||||||
|
for (int i = 0; i < horizontalListNode.itemValues.size(); i++) {
|
||||||
|
if (subProperties.getProperty("id").asString().value().equals(horizontalListNode.itemValues.valueAt(i))) {
|
||||||
|
notifyItemChanged(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callLoadMore() {
|
||||||
|
if (loadAnchor != itemCount) {
|
||||||
|
loadAnchor = itemCount;
|
||||||
|
this.horizontalListNode.callJSResponse(this.horizontalListNode.onLoadMoreFuncId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onItemLongClick(int position, View childView) {
|
||||||
|
JSValue jsValue = getItemModel(position);
|
||||||
|
if (jsValue != null && jsValue.isObject()) {
|
||||||
|
JSObject jsObject = jsValue.asObject();
|
||||||
|
String id = jsObject.getProperty("id").asString().value();
|
||||||
|
final ViewNode<?> itemNode = this.horizontalListNode.getSubNodeById(id);
|
||||||
|
JSObject props = jsObject.getProperty("props").asObject();
|
||||||
|
JSValue prop = props.getProperty("actions");
|
||||||
|
if (itemNode != null && prop.isArray()) {
|
||||||
|
JSArray actions = prop.asArray();
|
||||||
|
if (actions != null && actions.size() > 0) {
|
||||||
|
String[] items = new String[actions.size()];
|
||||||
|
final String[] callbacks = new String[actions.size()];
|
||||||
|
for (int i = 0; i < actions.size(); i++) {
|
||||||
|
JSObject action = actions.get(i).asObject();
|
||||||
|
String title = action.getProperty("title").asString().value();
|
||||||
|
String callback = action.getProperty("callback").asString().value();
|
||||||
|
items[i] = title;
|
||||||
|
callbacks[i] = callback;
|
||||||
|
}
|
||||||
|
new AlertDialog.Builder(childView.getContext())
|
||||||
|
.setItems(items, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
itemNode.callJSResponse(callbacks[which]);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DoricViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
HorizontalListItemNode horizontalListItemNode;
|
||||||
|
|
||||||
|
DoricViewHolder(HorizontalListItemNode node, @NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
horizontalListItemNode = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2022] [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.horizontallist;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import pub.doric.shader.SuperNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: pub.doric.shader.horizontallist
|
||||||
|
* @Author: jingpeng.wang
|
||||||
|
* @CreateDate: 2022-8-22
|
||||||
|
*/
|
||||||
|
@DoricPlugin(name = "HorizontalListItem")
|
||||||
|
public class HorizontalListItemNode extends StackNode {
|
||||||
|
public String identifier = "";
|
||||||
|
|
||||||
|
public HorizontalListItemNode(DoricContext doricContext) {
|
||||||
|
super(doricContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(SuperNode<?> superNode) {
|
||||||
|
super.init(superNode);
|
||||||
|
this.mReusable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void blend(FrameLayout view, String name, JSValue prop) {
|
||||||
|
if ("actions".equals(name)) {
|
||||||
|
} else 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,617 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2022] [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.horizontallist;
|
||||||
|
|
||||||
|
import static androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.github.pengfeizhou.jscore.JSDecoder;
|
||||||
|
import com.github.pengfeizhou.jscore.JSNumber;
|
||||||
|
import com.github.pengfeizhou.jscore.JSONBuilder;
|
||||||
|
import com.github.pengfeizhou.jscore.JSObject;
|
||||||
|
import com.github.pengfeizhou.jscore.JSValue;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import pub.doric.DoricContext;
|
||||||
|
import pub.doric.DoricScrollChangeListener;
|
||||||
|
import pub.doric.IDoricScrollable;
|
||||||
|
import pub.doric.async.AsyncResult;
|
||||||
|
import pub.doric.extension.bridge.DoricMethod;
|
||||||
|
import pub.doric.extension.bridge.DoricPlugin;
|
||||||
|
import pub.doric.shader.SuperNode;
|
||||||
|
import pub.doric.shader.ViewNode;
|
||||||
|
import pub.doric.utils.DoricJSDispatcher;
|
||||||
|
import pub.doric.utils.DoricUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: pub.doric.shader.horizontallist
|
||||||
|
* @Author: jingpeng.wang
|
||||||
|
* @CreateDate: 2022-8-22
|
||||||
|
*/
|
||||||
|
@DoricPlugin(name = "HorizontalList")
|
||||||
|
public class HorizontalListNode extends SuperNode<RecyclerView> implements IDoricScrollable {
|
||||||
|
private final HorizontalListAdapter horizontalListAdapter;
|
||||||
|
private String renderItemFuncId;
|
||||||
|
String onLoadMoreFuncId;
|
||||||
|
int itemCount = 0;
|
||||||
|
int batchCount = 15;
|
||||||
|
SparseArray<String> itemValues = new SparseArray<>();
|
||||||
|
private boolean loadMore = false;
|
||||||
|
String loadMoreViewId;
|
||||||
|
private final Set<DoricScrollChangeListener> listeners = new HashSet<>();
|
||||||
|
private String onScrollFuncId;
|
||||||
|
private String onScrollEndFuncId;
|
||||||
|
private final DoricJSDispatcher jsDispatcher = new DoricJSDispatcher();
|
||||||
|
|
||||||
|
public HorizontalListNode(DoricContext doricContext) {
|
||||||
|
super(doricContext);
|
||||||
|
this.horizontalListAdapter = new HorizontalListAdapter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean scrollable = true;
|
||||||
|
|
||||||
|
private final DoricItemTouchHelperCallback doricItemTouchHelperCallback = new DoricItemTouchHelperCallback(
|
||||||
|
new OnItemTouchCallbackListener() {
|
||||||
|
@Override
|
||||||
|
public void onSwiped(int adapterPosition) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeMove(int fromPos) {
|
||||||
|
if (!TextUtils.isEmpty(beforeDraggingFuncId)) {
|
||||||
|
callJSResponse(beforeDraggingFuncId, fromPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(int srcPosition, int targetPosition) {
|
||||||
|
String srcValue = itemValues.valueAt(srcPosition);
|
||||||
|
String targetValue = itemValues.valueAt(targetPosition);
|
||||||
|
itemValues.setValueAt(srcPosition, targetValue);
|
||||||
|
itemValues.setValueAt(targetPosition, srcValue);
|
||||||
|
horizontalListAdapter.notifyItemMoved(srcPosition, targetPosition);
|
||||||
|
if (!TextUtils.isEmpty(onDraggingFuncId)) {
|
||||||
|
callJSResponse(onDraggingFuncId, srcPosition, targetPosition);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMoved(int fromPos, int toPos) {
|
||||||
|
if (!TextUtils.isEmpty(onDraggedFuncId)) {
|
||||||
|
callJSResponse(onDraggedFuncId, fromPos, toPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private String beforeDraggingFuncId;
|
||||||
|
private String onDraggingFuncId;
|
||||||
|
private String onDraggedFuncId;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
horizontalListAdapter.blendSubNode(subProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView build() {
|
||||||
|
final RecyclerView recyclerView = new RecyclerView(getContext());
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false) {
|
||||||
|
@Override
|
||||||
|
public boolean canScrollHorizontally() {
|
||||||
|
if (!scrollable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.canScrollHorizontally();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||||
|
if (scrollable) {
|
||||||
|
super.smoothScrollToPosition(recyclerView, state, position);
|
||||||
|
} else {
|
||||||
|
DoricLinearSmoothScroller linearSmoothScroller = new DoricLinearSmoothScroller(recyclerView.getContext());
|
||||||
|
linearSmoothScroller.setTargetPosition(position);
|
||||||
|
startSmoothScroll(linearSmoothScroller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
recyclerView.setAdapter(this.horizontalListAdapter);
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy);
|
||||||
|
final int offsetX = recyclerView.computeHorizontalScrollOffset();
|
||||||
|
final int offsetY = recyclerView.computeVerticalScrollOffset();
|
||||||
|
for (DoricScrollChangeListener listener : listeners) {
|
||||||
|
listener.onScrollChange(recyclerView, offsetX, offsetY, offsetX - dx, offsetY - dy);
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(onScrollFuncId)) {
|
||||||
|
jsDispatcher.dispatch(new Callable<AsyncResult<JSDecoder>>() {
|
||||||
|
@Override
|
||||||
|
public AsyncResult<JSDecoder> call() {
|
||||||
|
return callJSResponse(onScrollFuncId, new JSONBuilder()
|
||||||
|
.put("x", DoricUtils.px2dp(offsetX))
|
||||||
|
.put("y", DoricUtils.px2dp(offsetY))
|
||||||
|
.toJSONObject());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
|
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||||
|
if (!TextUtils.isEmpty(onScrollEndFuncId)) {
|
||||||
|
int offsetX = recyclerView.computeHorizontalScrollOffset();
|
||||||
|
int offsetY = recyclerView.computeVerticalScrollOffset();
|
||||||
|
callJSResponse(
|
||||||
|
onScrollEndFuncId,
|
||||||
|
new JSONBuilder()
|
||||||
|
.put("x", DoricUtils.px2dp(offsetX))
|
||||||
|
.put("y", DoricUtils.px2dp(offsetY))
|
||||||
|
.toJSONObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final GestureDetector gestureDetector = new GestureDetector(
|
||||||
|
getContext(),
|
||||||
|
new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
@Override
|
||||||
|
public void onLongPress(MotionEvent e) {
|
||||||
|
super.onLongPress(e);
|
||||||
|
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
|
||||||
|
if (childView != null) {
|
||||||
|
int position = recyclerView.getChildLayoutPosition(childView);
|
||||||
|
horizontalListAdapter.onItemLongClick(position, childView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||||
|
return gestureDetector.onTouchEvent(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DoricItemTouchHelper doricItemTouchHelper = new DoricItemTouchHelper(doricItemTouchHelperCallback);
|
||||||
|
|
||||||
|
doricItemTouchHelper.attachToRecyclerView(recyclerView);
|
||||||
|
|
||||||
|
return recyclerView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void blend(JSObject jsObject) {
|
||||||
|
super.blend(jsObject);
|
||||||
|
int limit = jsObject.propertySet().contains("loadMoreView") ? 2 : 1;
|
||||||
|
if (jsObject.propertySet().size() > limit || !jsObject.propertySet().contains("subviews")) {
|
||||||
|
if (mView != null) {
|
||||||
|
mView.post(new Runnable() {
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
horizontalListAdapter.loadMore = loadMore;
|
||||||
|
horizontalListAdapter.itemCount = itemCount;
|
||||||
|
horizontalListAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void blend(RecyclerView view, String name, final JSValue prop) {
|
||||||
|
switch (name) {
|
||||||
|
case "scrollable":
|
||||||
|
if (!prop.isBoolean()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.scrollable = prop.asBoolean().value();
|
||||||
|
break;
|
||||||
|
case "itemCount":
|
||||||
|
if (!prop.isNumber()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.itemCount = prop.asNumber().toInt();
|
||||||
|
break;
|
||||||
|
case "renderItem":
|
||||||
|
if (!prop.isString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String funcId = prop.asString().value();
|
||||||
|
if (!funcId.equals(this.renderItemFuncId)) {
|
||||||
|
this.horizontalListAdapter.loadAnchor = -1;
|
||||||
|
this.renderItemFuncId = funcId;
|
||||||
|
// If reset renderItem,should reset native cache.
|
||||||
|
for (int index = 0; index < this.itemValues.size(); index++) {
|
||||||
|
removeSubModel(this.itemValues.valueAt(index));
|
||||||
|
}
|
||||||
|
this.itemValues.clear();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "onLoadMore":
|
||||||
|
this.onLoadMoreFuncId = prop.asString().value();
|
||||||
|
break;
|
||||||
|
case "loadMoreView":
|
||||||
|
this.loadMoreViewId = prop.asString().value();
|
||||||
|
break;
|
||||||
|
case "batchCount":
|
||||||
|
this.batchCount = prop.asNumber().toInt();
|
||||||
|
break;
|
||||||
|
case "loadMore":
|
||||||
|
this.loadMore = prop.asBoolean().value();
|
||||||
|
break;
|
||||||
|
case "onScroll":
|
||||||
|
if (!prop.isString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onScrollFuncId = prop.asString().value();
|
||||||
|
break;
|
||||||
|
case "onScrollEnd":
|
||||||
|
if (!prop.isString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onScrollEndFuncId = prop.asString().value();
|
||||||
|
break;
|
||||||
|
case "scrolledPosition":
|
||||||
|
if (!prop.isNumber()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
view.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
moveToPosition(prop.asNumber().toInt(), false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "canDrag":
|
||||||
|
if (!prop.isBoolean()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean canDrag = prop.asBoolean().value();
|
||||||
|
doricItemTouchHelperCallback.setDragEnable(canDrag);
|
||||||
|
break;
|
||||||
|
case "beforeDragging":
|
||||||
|
if (!prop.isString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.beforeDraggingFuncId = prop.asString().value();
|
||||||
|
break;
|
||||||
|
case "onDragging":
|
||||||
|
if (!prop.isString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onDraggingFuncId = prop.asString().value();
|
||||||
|
break;
|
||||||
|
case "onDragged":
|
||||||
|
if (!prop.isString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onDraggedFuncId = prop.asString().value();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
super.blend(view, name, prop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void blendSubLayoutConfig(ViewNode<?> viewNode, JSObject jsObject) {
|
||||||
|
super.blendSubLayoutConfig(viewNode, jsObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
HorizontalListAdapter.DoricViewHolder viewHolder = (HorizontalListAdapter.DoricViewHolder) mView.getChildViewHolder(view);
|
||||||
|
if (id.equals(viewHolder.horizontalListItemNode.getId())) {
|
||||||
|
return viewHolder.horizontalListItemNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addScrollChangeListener(DoricScrollChangeListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeScrollChangeListener(DoricScrollChangeListener listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DoricMethod
|
||||||
|
public void scrollToItem(JSObject params) {
|
||||||
|
boolean animated = false;
|
||||||
|
if (params.getProperty("animated").isBoolean()) {
|
||||||
|
animated = params.getProperty("animated").asBoolean().value();
|
||||||
|
}
|
||||||
|
JSNumber pos = params.getProperty("index").asNumber();
|
||||||
|
moveToPosition(pos.toInt(), animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DoricMethod
|
||||||
|
public JSONArray findVisibleItems() {
|
||||||
|
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) this.mView.getLayoutManager();
|
||||||
|
assert linearLayoutManager != null;
|
||||||
|
int startPos = linearLayoutManager.findFirstVisibleItemPosition();
|
||||||
|
int endPos = linearLayoutManager.findLastVisibleItemPosition();
|
||||||
|
JSONArray jsonArray = new JSONArray();
|
||||||
|
for (int i = startPos; i <= endPos; i++) {
|
||||||
|
jsonArray.put(i);
|
||||||
|
}
|
||||||
|
return jsonArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DoricMethod
|
||||||
|
public JSONArray findCompletelyVisibleItems() {
|
||||||
|
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) this.mView.getLayoutManager();
|
||||||
|
assert linearLayoutManager != null;
|
||||||
|
int startPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
|
||||||
|
int endPos = linearLayoutManager.findLastCompletelyVisibleItemPosition();
|
||||||
|
JSONArray jsonArray = new JSONArray();
|
||||||
|
for (int i = startPos; i <= endPos; i++) {
|
||||||
|
jsonArray.put(i);
|
||||||
|
}
|
||||||
|
return jsonArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DoricMethod
|
||||||
|
public void reload() {
|
||||||
|
this.horizontalListAdapter.loadAnchor = -1;
|
||||||
|
// If reload,should reset native cache.
|
||||||
|
for (int index = 0; index < this.itemValues.size(); index++) {
|
||||||
|
removeSubModel(this.itemValues.valueAt(index));
|
||||||
|
}
|
||||||
|
this.itemValues.clear();
|
||||||
|
mView.post(new Runnable() {
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
horizontalListAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveToPosition(int pos, boolean smooth) {
|
||||||
|
if (mView.getLayoutManager() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(getView().getLayoutManager() instanceof LinearLayoutManager)) {
|
||||||
|
defaultScrollTo(pos, smooth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LinearLayoutManager layoutManager = (LinearLayoutManager) mView.getLayoutManager();
|
||||||
|
int firstItem = layoutManager.findFirstVisibleItemPosition();
|
||||||
|
int lastItem = layoutManager.findLastVisibleItemPosition();
|
||||||
|
if (pos <= firstItem) {
|
||||||
|
defaultScrollTo(pos, smooth);
|
||||||
|
} else if (pos > lastItem) {
|
||||||
|
defaultScrollTo(pos, smooth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void defaultScrollTo(int pos, boolean b) {
|
||||||
|
if (b) {
|
||||||
|
mView.smoothScrollToPosition(pos);
|
||||||
|
} else {
|
||||||
|
mView.scrollToPosition(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
scrollable = true;
|
||||||
|
loadMore = false;
|
||||||
|
loadMoreViewId = null;
|
||||||
|
onLoadMoreFuncId = null;
|
||||||
|
onScrollFuncId = null;
|
||||||
|
onScrollEndFuncId = null;
|
||||||
|
renderItemFuncId = null;
|
||||||
|
beforeDraggingFuncId = null;
|
||||||
|
onDraggingFuncId = null;
|
||||||
|
onDraggedFuncId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DoricItemTouchHelper extends ItemTouchHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ItemTouchHelper that will work with the given Callback.
|
||||||
|
* <p>
|
||||||
|
* You can attach ItemTouchHelper to a RecyclerView via
|
||||||
|
* {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
|
||||||
|
* an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
|
||||||
|
*
|
||||||
|
* @param callback The Callback which controls the behavior of this touch helper.
|
||||||
|
*/
|
||||||
|
public DoricItemTouchHelper(@NonNull Callback callback) {
|
||||||
|
super(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DoricItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||||
|
|
||||||
|
private OnItemTouchCallbackListener onItemTouchCallbackListener;
|
||||||
|
|
||||||
|
private boolean isCanDrag = false;
|
||||||
|
private boolean isCanSwipe = false;
|
||||||
|
|
||||||
|
private int fromPos;
|
||||||
|
private int toPos;
|
||||||
|
|
||||||
|
public DoricItemTouchHelperCallback(OnItemTouchCallbackListener onItemTouchCallbackListener) {
|
||||||
|
this.onItemTouchCallbackListener = onItemTouchCallbackListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnItemTouchCallbackListener(OnItemTouchCallbackListener onItemTouchCallbackListener) {
|
||||||
|
this.onItemTouchCallbackListener = onItemTouchCallbackListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDragEnable(boolean canDrag) {
|
||||||
|
isCanDrag = canDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSwipeEnable(boolean canSwipe) {
|
||||||
|
isCanSwipe = canSwipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLongPressDragEnabled() {
|
||||||
|
return isCanDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
|
||||||
|
public boolean isItemViewSwipeEnabled() {
|
||||||
|
return isCanSwipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||||
|
if (layoutManager instanceof LinearLayoutManager) {
|
||||||
|
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
||||||
|
int orientation = linearLayoutManager.getOrientation();
|
||||||
|
int dragFlag = 0;
|
||||||
|
int swipeFlag = 0;
|
||||||
|
if (orientation == LinearLayoutManager.HORIZONTAL) {
|
||||||
|
swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
|
||||||
|
dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
|
||||||
|
}
|
||||||
|
return makeMovementFlags(dragFlag, swipeFlag);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||||
|
if (onItemTouchCallbackListener != null) {
|
||||||
|
return onItemTouchCallbackListener.onMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
|
||||||
|
super.onSelectedChanged(viewHolder, actionState);
|
||||||
|
if (viewHolder != null) {
|
||||||
|
fromPos = viewHolder.getAdapterPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionState == ACTION_STATE_DRAG) {
|
||||||
|
if (onItemTouchCallbackListener != null) {
|
||||||
|
onItemTouchCallbackListener.beforeMove(fromPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder);
|
||||||
|
toPos = viewHolder.getAdapterPosition();
|
||||||
|
|
||||||
|
if (onItemTouchCallbackListener != null) {
|
||||||
|
onItemTouchCallbackListener.onMoved(fromPos, toPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||||
|
if (onItemTouchCallbackListener != null) {
|
||||||
|
onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface OnItemTouchCallbackListener {
|
||||||
|
void onSwiped(int adapterPosition);
|
||||||
|
|
||||||
|
void beforeMove(int fromPos);
|
||||||
|
|
||||||
|
boolean onMove(int srcPosition, int targetPosition);
|
||||||
|
|
||||||
|
void onMoved(int fromPos, int toPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DoricLinearSmoothScroller extends LinearSmoothScroller {
|
||||||
|
|
||||||
|
public DoricLinearSmoothScroller(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int calculateDxToMakeVisible(View view, int snapPreference) {
|
||||||
|
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
|
||||||
|
if (layoutManager == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||||
|
view.getLayoutParams();
|
||||||
|
final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
|
||||||
|
final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
|
||||||
|
final int start = layoutManager.getPaddingLeft();
|
||||||
|
final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
|
||||||
|
return calculateDtToFit(left, right, start, end, snapPreference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user