この記事はAndroidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Javaの開発経験、XML構文規則、Androidのアプリ開発経験がある方を対象としています。
Androidのアプリ開発でお役にたててれば、嬉しいです。
(これからAndroidのアプリ開発やJavaでの開発を始めたい方への案内は、記事の最後で紹介します)
スワイプ操作でアイテムが消えないRecyclerviewを実装する
旅行や観光のお供に最適です、360度カメラで思い出を記録しよう♪
ポイント
RecyclerViewでスワイプ操作を実装する場合、ItemTouchHelperを使った実装になります。
ItemTouchHelperのスワイプ(onSwiped)は、アイテムがリストから消滅した時点でイベント発生します。
このため、選択や削除前に確認メッセージを表示する場合に違和感があります。
今回は、スワイプ操作でアイテムがリストから消滅しない実装を紹介します。
ItemTouchHelperについて
スワイプヘルパー
スワイプ操作でアイテムがリストからを消滅しない、スワイプ操作をイベント処理できるスワイプヘルパーを作成します。
リストからを消滅しないように、スワイプのアニメーションで空白を描画することで再現します。
また、スワイプ後に元に戻すためのキューを用意します。
スワイプ操作のイベント処理は、空白を描画したことをフックするようにします。
public abstract class SwipeHelper extends CustomSimpleCallback {
public static final int SWIPE_HEIGHT = 40;
private final HorizontalListView
horizontalListView;
private List<SwipeArea> areas;
private final GestureDetector gestureDetector;
private int swipedPos = -1;
private float swipeThreshold = 0.5f;
private final Map<Integer, List<SwipeArea>>
areasBuffer;
private final Queue<Integer> recoverQueue;
private final int direction;
@SuppressLint("ClickableViewAccessibility")
public SwipeHelper(Context context, HorizontalListView horizontalListView, int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
this.horizontalListView = horizontalListView;
this.direction = swipeDirs;
this.areas = new ArrayList<>();
this.dragDirs = dragDirs;
this.swipeDirs = swipeDirs;
GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(@NonNull MotionEvent e) {
return true;
}
};
this.gestureDetector = new GestureDetector(context, gestureListener);
View.OnTouchListener onTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent e) {
if (swipedPos < 0) return false;
Point point = new Point((int) e.getRawX(), (int) e.getRawY());
RecyclerView.ViewHolder swipedViewHolder = horizontalListView.findViewHolderForAdapterPosition(swipedPos);
if (swipedViewHolder != null) {
View swipedItem = swipedViewHolder.itemView;
Rect rect = new Rect();
swipedItem.getGlobalVisibleRect(rect);
if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_MOVE) {
if (rect.left < point.x && rect.right > point.x) {
gestureDetector.onTouchEvent(e);
} else {
recoverQueue.add(swipedPos);
swipedPos = -1;
recoverSwipedItem();
}
}
}
return false;
}
};
this.horizontalListView.setOnTouchListener(onTouchListener);
areasBuffer = new HashMap<>();
recoverQueue = new LinkedList<Integer>() {
@Override
public boolean add(Integer o) {
if (contains(o))
return false;
else
return super.add(o);
}
};
attachSwipe();
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
viewHolder.itemView.setAlpha(1.0f);
int pos = viewHolder.getBindingAdapterPosition();
if (swipedPos != pos) {
recoverQueue.add(swipedPos);
}
swipedPos = pos;
if (areasBuffer.containsKey(swipedPos)) {
areas = areasBuffer.get(swipedPos);
if (areas != null) areas.get(0).onSwipe(pos, direction);
} else {
areas.clear();
}
areasBuffer.clear();
swipeThreshold = 0.5f * areas.size() * SWIPE_HEIGHT;
recoverSwipedItem();
}
@Override
public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
return swipeThreshold;
}
@Override
public float getSwipeEscapeVelocity(float defaultValue) {
return 0.1f * defaultValue;
}
@Override
public float getSwipeVelocityThreshold(float defaultValue) {
return 5.0f * defaultValue;
}
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
int pos = viewHolder.getBindingAdapterPosition();
float translationY = dY;
View itemView = viewHolder.itemView;
if (pos < 0){
swipedPos = pos;
return;
}
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
if ((dY < 0 && ((direction & ItemTouchHelper.UP) == ItemTouchHelper.UP) || ((direction & ItemTouchHelper.DOWN) == ItemTouchHelper.DOWN))) {
List<SwipeArea> buffer = new ArrayList<>();
if (!areasBuffer.containsKey(pos)){
instantiateUnderlaySwipe(viewHolder, buffer);
areasBuffer.put(pos, buffer);
} else {
buffer = areasBuffer.get(pos);
}
translationY = dY * Objects.requireNonNull(buffer).size() * SWIPE_HEIGHT / itemView.getHeight();
drawAreas(c, itemView, buffer, translationY);
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, translationY, actionState, isCurrentlyActive);
}
private void drawAreas(Canvas c, View itemView, List<SwipeArea> buffer, float dY) {
float top = dY < 0 ? itemView.getTop() : 0;
float down = dY > 0 ? itemView.getBottom() : 0;
float swipeHeight = (-1) * dY / buffer.size();
for (SwipeArea area : buffer) {
down = dY < 0 ? top - swipeHeight : down;
top = dY > 0 ? down - swipeHeight : top;
area.onDraw(c, new RectF(itemView.getLeft(), dY > 0 ? down : top, itemView.getRight(), dY > 0 ? top : down));
top = dY < 0 ? down : top;
down = dY > 0 ? top : down;
}
}
public void attachSwipe(){
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this);
itemTouchHelper.attachToRecyclerView(horizontalListView);
}
private synchronized void recoverSwipedItem(){
while (!recoverQueue.isEmpty()){
@SuppressWarnings("ConstantConditions")
int pos = recoverQueue.poll();
if (pos > -1) {
if (horizontalListView.getAdapter() != null) {
horizontalListView.getAdapter().notifyItemChanged(pos);
}
}
}
}
public abstract void instantiateUnderlaySwipe(RecyclerView.ViewHolder viewHolder, List<SwipeArea> swipeAreas);
public static class SwipeArea {
private final int color;
Context context;
private final SwipeHelperH.onDrawListener onDrawListener;
public SwipeArea(Context context, int color, SwipeHelperH.onDrawListener onDrawListener) {
this.color = color;
this.context = context;
this.onDrawListener = onDrawListener;
}
public void onDraw(Canvas c, RectF rect){
Paint p = new Paint();
p.setColor(color);
c.drawRect(rect, p);
p.setColor(Color.WHITE);
}
public void onSwipe(int pos, int direction) {
onDrawListener.onSwipe(pos, direction);
}
}
public interface onDrawListener {
void onSwipe(int pos, int direction);
}
}ItemTouchHelper.SimpleCallbackではなく、操作の無効化を追加したCustomSimpleCallbackを継承します。
SwipeHelperのコンストラクタでItemTouchHelperをRecyclerViewにアタッチします。
HorizontalListViewは横スクロールタイプのRecyclerViewを継承したカスタムクラスです。
CustomSimpleCallbackクラス
public class CustomSimpleCallback extends ItemTouchHelper.SimpleCallback {
private static final boolean DEBUG = false;
private static final String TAG = CustomSimpleCallback.class.getSimpleName();
private boolean drag = false;
private boolean invalid = false;
public int dragDirs;
public int swipeDirs;
public CustomSimpleCallback(int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
this.dragDirs = dragDirs;
this.swipeDirs = swipeDirs;
}
// DRAGGABLE スイッチ //
public void setDraggable(boolean value) {
drag = value;
}
// INVALID スイッチ //
public void setInvalid(boolean value) {
invalid = value;
}
public boolean getInvalid() {
return invalid;
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = drag ? dragDirs : 0;
return makeMovementFlags(invalid ? 0 : dragFlags, invalid ? 0 : swipeDirs);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
}スワイプ操作で確認メッセージを表示する
空白を描画したタイミングでインタフェースを使って、削除確認メッセージの表示を記述します。
ドラッグ&ドロップによるアイテムを入れ替えは、onMoveとonSelectedChangedとclearViewでアイテム描画、onMovedでアイテム入れ替えの更新を記述します。
private HorizontalListView todoView;
private HorizontalListView.Adapter
todoAdapter;
private SwipeHelperH swipeHelperH;
:
todoView = view.findViewById(R.id.todoView);
swipeHelperH = new SwipeHelperH(context, todoView, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, ItemTouchHelper.DOWN) {
@SuppressLint("NotifyDataSetChanged")
@Override
public void instantiateUnderlaySwipe(RecyclerView.ViewHolder viewHolder, List<SwipeArea> swipeAreas) {
swipeAreas.add(new SwipeArea(
context,
Color.TRANSPARENT,
(position, direction) -> {
// 削除確認メッセージの表示
:
todoAdapter.notifyDataSetChanged();
}
));
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, ItemTouchHelper.DOWN);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
todoAdapter.notifyItemMoved(viewHolder.getBindingAdapterPosition(), target.getBindingAdapterPosition());
return true;
}
@Override
public void onMoved(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, int fromPos, @NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) {
// アイテムの入れ替えの更新
:
todoAdapter.notifyDataSetChanged();
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
if (viewHolder != null) viewHolder.itemView.setAlpha(actionState == ACTION_STATE_DRAG ? 0.5f : 1.0f);
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
if (viewHolder.getBindingAdapterPosition() >= 0) {
viewHolder.itemView.setAlpha(1.0f);
setDraggable(true);
draggable = true;
}
super.clearView(recyclerView, viewHolder);
}
};
:
アイテムのスワイプ操作を無効にする
CustomSimpleCallbackのgetMovementFlagsでスワイプを無効にします。
今回は、ここまでです。
スワイプ操作でアイテムが消えないRecyclervieを実装しているAndroidアプリです。
誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にあるコメントか、
こちらから、お願いいたします♪
ポチッとして頂けると、
次のコンテンツを作成する励みになります♪
これからAndroidのアプリ開発やJavaでの開発を始めたい方へ
アプリケーション開発経験がない方や、アプリケーション開発経験がある方でも、JavaやC#などのオブジェクト指向言語が初めての方は、Androidのアプリ開発ができるようになるには、かなりの時間がかかります。
オンラインスクールでの習得を、強くおススメします。
未経験者からプログラマーを目指すのに最適です。まずは無料カウンセリングから♪

ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適です。まずは無料オンライン相談から♪

参考になったら、💛をポッチとしてね♪



コメント欄