【发布时间】:2023-03-08 13:40:02
【问题描述】:
这是一个非常简短的问题: 最近谷歌更新了它的材料设计指南,表明多选项目应该像在谷歌照片应用程序(here)上一样:
我注意到即使你已经处于多选模式,你仍然可以在任何你想要的地方使用这个手势。
到目前为止,我所做的是处理单击项目以进行多选,但我该如何做 Google 显示的操作?
【问题讨论】:
标签: android android-recyclerview material-design multi-select
这是一个非常简短的问题: 最近谷歌更新了它的材料设计指南,表明多选项目应该像在谷歌照片应用程序(here)上一样:
我注意到即使你已经处于多选模式,你仍然可以在任何你想要的地方使用这个手势。
到目前为止,我所做的是处理单击项目以进行多选,但我该如何做 Google 显示的操作?
【问题讨论】:
标签: android android-recyclerview material-design multi-select
虽然它很复杂,并且有一些库可供使用,但如果您可以根据自己的目的开发一个库会更好(假设我们希望为 Boogle、Bookworm 或 Bejeweled 等一种类型的游戏使用此功能......)。
所以,如果您想自己制作,这里有一些不错的提示:
1) 完全理解触摸事件(当action down, move, up, ...将被发送;如何区分多点触摸和单点触摸)。
2) 很好的区分onTouch和onIntercepTouch。
3) 对 RecyclerView.OnItemTouchListener 做一些研究,因为我们要使用它。
4) 研究的时候放一些日志知道流事件,学习触摸事件的时候不要调试。
接下来,你开始的小例子:
abstract public class OnItemTouchMultiDragListener implements RecyclerView.OnItemTouchListener {
private static final int MAX_CLICK_DURATION = 200;
private boolean isIn; // Is touching still at the same item.
private String tagTouchableView; // View catches touch event in each item of recycler view.
private int countIn; // How many times touch event in item.
private long timeDown; // Time touch down on touchable view.
protected int startPos; // Position of item to start touching.
protected int lastPos; // Position of item last touching in.
protected int endPos; // Position of item to end touching (touch up).
private boolean isTouchDownAtTouchableView; // If touch down event is in a touchable view.
public OnItemTouchMultiDragListener(String tagTouchableView){
this.tagTouchableView = tagTouchableView;
}
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
if(motionEvent.getPointerCount() > 1){
// Touch with many fingers, don't handle.
return false;
}
int action = motionEvent.getAction();
if(action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL){
View v = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
RecyclerView.ViewHolder holder = null;
endPos = -1;
if(v != null) {
View vTouchable = v.findViewWithTag(tagTouchableView);
// If up in that item too, since we only in one item.
if (vTouchable != null && isInView(motionEvent, vTouchable)) {
holder = recyclerView.getChildViewHolder(v);
endPos = holder.getAdapterPosition();
}
}
// If touch down/ move only in one item.
if(countIn == 1 && isTouchDownAtTouchableView){
if(holder != null && endPos != -1) {
if (isPossiblyClick()) {
onClickUp(endPos, holder);
} else {
onLongClickUp(endPos, holder);
}
}
}else if (countIn > 1){
onDragMultiUp(lastPos, holder);
}else {
onUp();
}
// Reset touch status.
isIn = false;
isTouchDownAtTouchableView = false;
countIn = 0;
return false;
}
View v = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
if(v != null) {
View vTouchable = v.findViewWithTag(tagTouchableView);
if(vTouchable != null && isInView(motionEvent, vTouchable)) {
RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(v);
int pos = holder.getAdapterPosition();
if(isIn && pos == lastPos || pos == -1){
// Still in the same pos or can not determine what the pos is.
return false;
}
timeDown = Calendar.getInstance().getTimeInMillis();
isIn = true;
RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(v);
int pos = holder.getAdapterPosition();
if(action == MotionEvent.ACTION_DOWN){
onDownTouchableView(pos);
isTouchDownAtTouchableView = true;
}else if(isTouchDownAtTouchableView && action == MotionEvent.ACTION_MOVE){
onMoveTouchableView(pos);
}
onInItemAndInTouchableView(motionEvent, pos);
if(countIn == 0){
startPos = pos;
}
lastPos = pos;
countIn ++;
}else {
isIn = false;
onInItemButNotInTouchable();
}
}else {
isIn = false;
onOutItem();
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) { }
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) { }
private boolean isPossiblyClick() {
long clickDuration = Calendar.getInstance().getTimeInMillis() - timeDown;
return clickDuration < MAX_CLICK_DURATION;
}
private boolean isInView(MotionEvent ev, View... views) {
Rect rect = new Rect();
for (View v : views) {
v.getGlobalVisibleRect(rect);
Log.d("","");
if (rect.contains((int) ev.getRawX(), (int) ev.getRawY()))
return true;
}
return false;
}
public void onInItemAndInTouchableView(MotionEvent motionEvent, int pos){}
abstract public void onDownTouchableView(int pos);
abstract public void onMoveTouchableView(int pos);
public void onUp(){}
public void onClickUp(int pos, RecyclerView.ViewHolder holder){}
public void onLongClickUp(int pos, RecyclerView.ViewHolder holder){}
public void onDragMultiUp(int endPos, RecyclerView.ViewHolder holder){}
public void onInItemButNotInTouchable(){}
public void onOutItem(){}
public int getStartPos() {
return startPos;
}
public int getEndPos() {
return endPos;
}
}
要使用它,请执行以下操作:
private OnItemTouchMultiDragListener onItemTouchMultiDragListener = new OnItemTouchMultiDragListener("touchable") {
@Override
public void onDownTouchableView(int pos) {
// Write log here.
// This is an abstract method, you must implement.
}
@Override
public void onMoveTouchableView(int pos) {
// Write log here.
// This is an abstract method, you must implement.
}
};
您可以覆盖的其他方法是:onInItemAndInTouchableView、onUp、onClickUp、onLongClickUp、onDragMultiUp、onInItemButNotInTouchable、onOutItem。顺便说一句,在测试时将这些方法写入日志以了解何时调用。
当然,在实例化OnItemTouchListener 的实例之后,我们必须将其添加到回收器视图中,例如:
recyclerView.addOnItemTouchListener(onItemTouchMultiDragListener);
您还应该注意的另一件事是回收站视图中项目的布局,这是我的,它是网格布局中的方形项目,周围有填充以将项目彼此分开(因此只有项目视图的一部分接受触摸事件,而不是填充区域):
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="90dp"
android:layout_height="90dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Please note on the tag "touchable" which we must pass as param in constructor to let our listener know which part of the item will be listening to touch event -->
<View
android:tag="touchable"
android:id="@+id/v_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bkg_corner_gray"
app:layout_constraintWidth_percent="0.78846153846"
app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="35sp"
android:textColor="@color/black"
app:layout_constraintStart_toStartOf="@id/v_background"
app:layout_constraintEnd_toEndOf="@id/v_background"
app:layout_constraintTop_toTopOf="@id/v_background"
app:layout_constraintBottom_toBottomOf="@id/v_background"/>
</android.support.constraint.ConstraintLayout>
更新(2019 年 11 月 25 日)
这是一个简单的例子:https://github.com/mttdat/example-multi-drag-recycleview
【讨论】: