Android: implement horizontal list

This commit is contained in:
王劲鹏 2022-08-23 11:56:25 +08:00 committed by osborn
parent 9af7f389bc
commit e69465e203
4 changed files with 880 additions and 0 deletions

View File

@ -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());

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}