Как справиться с отменой после удаления, чтобы удалить в android ListView?

Мне нужно реализовать прокрутку для удаления в ListView с функцией Undo, как в приложении Gmail

Я знаю, что есть вопросы, которые уже задают вопрос о удалении

Удалить список элементов с помощью слайд – как Gmail

а также

Android scipe удалить строку списка

Но ни один из них не объясняет, как управлять Undo после удаления и Анимировать назад удаленное представление об отмене!

Я нашел другого без ответа.

Как реализовать gmail как действие «Достижение» или «Отменить» в элементе списка

Вот мой вопрос.

Как создать список в андроиде с удаленным удалением, затем показать UNDO в удаленном пространстве и Анимировать назад тот же вид, если отменено нажатие, также удалить параметр отмены в прокрутке или другой щелчок или прокрутку элемента?

У вас умные люди есть идея?

 | ----------- ITEM 1-----------| | ----------- ITEM 2-----------| | --Deleted-------<[UNDO]>| | ----------- ITEM 4-----------| | ----------- ITEM 5-----------| 

Примечание: извините, что я не могу добавить изображение из-за моей низкой репутации!

Solutions Collecting From Web of "Как справиться с отменой после удаления, чтобы удалить в android ListView?"

Вы можете найти именно то, что вы просили в https://github.com/nhaarman/ListViewAnimations

Ваш вопрос звучит так, будто вы смущены тем, как управлять фактической частью отмены. Если это так, то я бы предложил вам заглянуть в шаблон дизайна, называемый «Шаблон проектирования команд». Вы можете поставить в очередь «Команды», например, удалить, а затем только зафиксировать их, как только таймер отмены, или же вы его выполнили.

Библиотеку, чтобы сделать элементы в ListView или RecyclerView с возможностью ее отменить, используя свой собственный вид для обеспечения такой функциональности, как приложение Gmail для Android.

https://github.com/hudomju/android-swipe-to-dismiss-undo

Вот суть, на всякий случай, если страница изменится или ссылка недействительна:

ListViewActivity:

 public class ListViewActivity extends Activity { private static final int TIME_TO_AUTOMATICALLY_DISMISS_ITEM = 3000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.list_view_activity); init((ListView) findViewById(R.id.list_view)); } private void init(ListView listView) { final MyBaseAdapter adapter = new MyBaseAdapter(); listView.setAdapter(adapter); final SwipeToDismissTouchListener<ListViewAdapter> touchListener = new SwipeToDismissTouchListener<>( new ListViewAdapter(listView), new SwipeToDismissTouchListener.DismissCallbacks<ListViewAdapter>() { @Override public boolean canDismiss(int position) { return true; } @Override public void onPendingDismiss(ListViewAdapter recyclerView, int position) { } @Override public void onDismiss(ListViewAdapter view, int position) { adapter.remove(position); } }); touchListener.setDismissDelay(TIME_TO_AUTOMATICALLY_DISMISS_ITEM); listView.setOnTouchListener(touchListener); // Setting this scroll listener is required to ensure that during ListView scrolling, // we don't look for swipes. listView.setOnScrollListener((AbsListView.OnScrollListener) touchListener.makeScrollListener()); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (touchListener.existPendingDismisses()) { touchListener.undoPendingDismiss(); } else { Toast.makeText(ListViewActivity.this, "Position " + position, LENGTH_SHORT).show(); } } }); } static class MyBaseAdapter extends BaseAdapter { private static final int SIZE = 100; private final List<String> mDataSet = new ArrayList<>(); MyBaseAdapter() { for (int i = 0; i < SIZE; i++) mDataSet.add(i, "This is row number " + i); } @Override public int getCount() { return mDataSet.size(); } @Override public String getItem(int position) { return mDataSet.get(position); } @Override public long getItemId(int position) { return position; } public void remove(int position) { mDataSet.remove(position); notifyDataSetChanged(); } static class ViewHolder { TextView dataTextView; ViewHolder(View view) { dataTextView = (TextView) view.findViewById(R.id.txt_data); view.setTag(this); } } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = convertView == null ? new ViewHolder(convertView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item, parent, false)) : (ViewHolder) convertView.getTag(); viewHolder.dataTextView.setText(mDataSet.get(position)); return convertView; } } 

}

SwipeToDismissTouchListener:

 public class SwipeToDismissTouchListener<SomeCollectionView extends ViewAdapter> implements View.OnTouchListener { // Cached ViewConfiguration and system-wide constant values private final int mSlop; private final int mMinFlingVelocity; private final int mMaxFlingVelocity; private final long mAnimationTime; // Fixed properties private final SomeCollectionView mRecyclerView; private final DismissCallbacks<SomeCollectionView> mCallbacks; private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero // Transient properties private PendingDismissData mPendingDismiss; private float mDownX; private float mDownY; private boolean mSwiping; private int mSwipingSlop; private VelocityTracker mVelocityTracker; private int mDownPosition; private RowContainer mRowContainer; private boolean mPaused; // Handler to dismiss pending items after a delay private final Handler mHandler; private final Runnable mDismissRunnable = new Runnable() { @Override public void run() { processPendingDismisses(); } }; private long mDismissDelayMillis = -1; // negative to disable automatic dismissing public class RowContainer { final View container; final View dataContainer; final View undoContainer; boolean dataContainerHasBeenDismissed; public RowContainer(ViewGroup container) { this.container = container; dataContainer = container.getChildAt(0); undoContainer = container.getChildAt(1); dataContainerHasBeenDismissed = false; } View getCurrentSwipingView() { return dataContainerHasBeenDismissed ? undoContainer: dataContainer; } } /** * The callback interface used by {@link SwipeToDismissTouchListener} to inform its client * about a successful dismissal of one or more list item positions. */ public interface DismissCallbacks<SomeCollectionView extends ViewAdapter> { /** * Called to determine whether the given position can be dismissed. */ boolean canDismiss(int position); /** * Called when an item is swiped away by the user and the undo layout is completely visible. * Do NOT remove the list item yet, that should be done in {@link #onDismiss(com.hudomju.swipe.adapter.ViewAdapter, int)} * This may also be called immediately before and item is completely dismissed. * * @param recyclerView The originating {@link android.support.v7.widget.RecyclerView}. * @param position The position of the dismissed item. */ void onPendingDismiss(SomeCollectionView recyclerView, int position); /** * Called when the item is completely dismissed and removed from the list, after the undo layout is hidden. * * @param recyclerView The originating {@link android.support.v7.widget.RecyclerView}. * @param position The position of the dismissed item. */ void onDismiss(SomeCollectionView recyclerView, int position); } /** * Constructs a new swipe-to-dismiss touch listener for the given list view. * * @param recyclerView The list view whose items should be dismissable. * @param callbacks The callback to trigger when the user has indicated that she would like to * dismiss one or more list items. */ public SwipeToDismissTouchListener(SomeCollectionView recyclerView, DismissCallbacks<SomeCollectionView> callbacks) { ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext()); mSlop = vc.getScaledTouchSlop(); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16; mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); mAnimationTime = recyclerView.getContext().getResources().getInteger( android.R.integer.config_shortAnimTime); mRecyclerView = recyclerView; mCallbacks = callbacks; mHandler = new Handler(); } /** * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures. * * @param enabled Whether or not to watch for gestures. */ public void setEnabled(boolean enabled) { mPaused = !enabled; } /** * Set the delay after which the pending items will be dismissed when there was no user action. * Set to a negative value to disable automatic dismissing items. * @param dismissDelayMillis The delay between onPendingDismiss and onDismiss calls, in milliseconds. */ public void setDismissDelay(long dismissDelayMillis) { this.mDismissDelayMillis = dismissDelayMillis; } /** * Returns an {@link android.widget.AbsListView.OnScrollListener} to be added to the {@link * android.widget.ListView} using {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}. * If a scroll listener is already assigned, the caller should still pass scroll changes through * to this listener. This will ensure that this {@link SwipeToDismissTouchListener} is * paused during list view scrolling.</p> * * @see SwipeToDismissTouchListener */ public Object makeScrollListener() { return mRecyclerView.makeScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int scrollState) { processPendingDismisses(); setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } @Override public void onScroll(AbsListView absListView, int i, int i1, int i2) { } }); } @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (mViewWidth < 2) { mViewWidth = mRecyclerView.getWidth(); } switch (motionEvent.getActionMasked()) { case MotionEvent.ACTION_DOWN: { if (mPaused) { return false; } // TODO: ensure this is a finger, and set a flag // Find the child view that was touched (perform a hit test) Rect rect = new Rect(); int childCount = mRecyclerView.getChildCount(); int[] listViewCoords = new int[2]; mRecyclerView.getLocationOnScreen(listViewCoords); int x = (int) motionEvent.getRawX() - listViewCoords[0]; int y = (int) motionEvent.getRawY() - listViewCoords[1]; View child; for (int i = 0; i < childCount; i++) { child = mRecyclerView.getChildAt(i); child.getHitRect(rect); if (rect.contains(x, y)) { assert child instanceof ViewGroup && ((ViewGroup) child).getChildCount() == 2 : "Each child needs to extend from ViewGroup and have two children"; boolean dataContainerHasBeenDismissed = mPendingDismiss != null && mPendingDismiss.position == mRecyclerView.getChildPosition(child) && mPendingDismiss.rowContainer.dataContainerHasBeenDismissed; mRowContainer = new RowContainer((ViewGroup) child); mRowContainer.dataContainerHasBeenDismissed = dataContainerHasBeenDismissed; break; } } if (mRowContainer != null) { mDownX = motionEvent.getRawX(); mDownY = motionEvent.getRawY(); mDownPosition = mRecyclerView.getChildPosition(mRowContainer.container); if (mCallbacks.canDismiss(mDownPosition)) { mVelocityTracker = VelocityTracker.obtain(); mVelocityTracker.addMovement(motionEvent); } else { mRowContainer = null; } } return false; } case MotionEvent.ACTION_CANCEL: { if (mVelocityTracker == null) { break; } if (mRowContainer != null && mSwiping) { // cancel mRowContainer.getCurrentSwipingView() .animate() .translationX(0) .alpha(1) .setDuration(mAnimationTime) .setListener(null); } mVelocityTracker.recycle(); mVelocityTracker = null; mDownX = 0; mDownY = 0; mRowContainer = null; mDownPosition = ListView.INVALID_POSITION; mSwiping = false; break; } case MotionEvent.ACTION_UP: { if (mVelocityTracker == null) { break; } float deltaX = motionEvent.getRawX() - mDownX; mVelocityTracker.addMovement(motionEvent); mVelocityTracker.computeCurrentVelocity(1000); float velocityX = mVelocityTracker.getXVelocity(); float absVelocityX = Math.abs(velocityX); float absVelocityY = Math.abs(mVelocityTracker.getYVelocity()); boolean dismiss = false; boolean dismissRight = false; if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) { dismiss = true; dismissRight = deltaX > 0; } else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity && absVelocityY < absVelocityX && mSwiping) { // dismiss only if flinging in the same direction as dragging dismiss = (velocityX < 0) == (deltaX < 0); dismissRight = mVelocityTracker.getXVelocity() > 0; } if (dismiss && mDownPosition != ListView.INVALID_POSITION) { // dismiss final RowContainer downView = mRowContainer; // mDownView gets null'd before animation ends final int downPosition = mDownPosition; mRowContainer.getCurrentSwipingView() .animate() .translationX(dismissRight ? mViewWidth : -mViewWidth) .alpha(0) .setDuration(mAnimationTime) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { performDismiss(downView, downPosition); } }); } else { // cancel mRowContainer.getCurrentSwipingView() .animate() .translationX(0) .alpha(1) .setDuration(mAnimationTime) .setListener(null); } mVelocityTracker.recycle(); mVelocityTracker = null; mDownX = 0; mDownY = 0; mRowContainer = null; mDownPosition = ListView.INVALID_POSITION; mSwiping = false; break; } case MotionEvent.ACTION_MOVE: { if (mVelocityTracker == null || mPaused) { break; } mVelocityTracker.addMovement(motionEvent); float deltaX = motionEvent.getRawX() - mDownX; float deltaY = motionEvent.getRawY() - mDownY; if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) { mSwiping = true; mSwipingSlop = deltaX > 0 ? mSlop : -mSlop; mRecyclerView.requestDisallowInterceptTouchEvent(true); // Cancel ListView's touch (un-highlighting the item) MotionEvent cancelEvent = MotionEvent.obtain(motionEvent); cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); mRecyclerView.onTouchEvent(cancelEvent); cancelEvent.recycle(); } if (mSwiping) { mRowContainer.getCurrentSwipingView().setTranslationX(deltaX - mSwipingSlop); mRowContainer.getCurrentSwipingView().setAlpha(Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth))); return true; } break; } } return false; } class PendingDismissData implements Comparable<PendingDismissData> { public int position; public RowContainer rowContainer; public PendingDismissData(int position, RowContainer rowContainer) { this.position = position; this.rowContainer= rowContainer; } @Override public int compareTo(@NonNull PendingDismissData other) { // Sort by descending position return other.position - position; } } private void performDismiss(RowContainer dismissView, int dismissPosition) { // Animate the dismissed list item to zero-height and fire the dismiss callback when // all dismissed list item animations have completed. This triggers layout on each animation // frame; in the future we may want to do something smarter and more performant. if (mPendingDismiss != null) { boolean dismissingDifferentRow = mPendingDismiss.position != dismissPosition; int newPosition = mPendingDismiss.position < dismissPosition ? dismissPosition-1 : dismissPosition; processPendingDismisses(); if (dismissingDifferentRow) { addPendingDismiss(dismissView, newPosition); } } else { addPendingDismiss(dismissView, dismissPosition); } } private void addPendingDismiss(RowContainer dismissView, int dismissPosition) { dismissView.dataContainerHasBeenDismissed = true; dismissView.undoContainer.setVisibility(View.VISIBLE); mPendingDismiss = new PendingDismissData(dismissPosition, dismissView); // Notify the callbacks mCallbacks.onPendingDismiss(mRecyclerView, dismissPosition); // Automatically dismiss the item after a certain delay if(mDismissDelayMillis >= 0) mHandler.removeCallbacks(mDismissRunnable); mHandler.postDelayed(mDismissRunnable, mDismissDelayMillis); } /** * If a view was dismissed and the undo container is showing it will proceed with the final * dismiss of the item. * @return whether there were any pending rows to be dismissed. */ public boolean processPendingDismisses() { boolean existPendingDismisses = existPendingDismisses(); if (existPendingDismisses) processPendingDismisses(mPendingDismiss); return existPendingDismisses; } /** * Whether a row has been dismissed and is waiting for confirmation * @return whether there are any pending rows to be dismissed. */ public boolean existPendingDismisses() { return mPendingDismiss != null && mPendingDismiss.rowContainer.dataContainerHasBeenDismissed; } /** * If a view was dismissed and the undo container is showing it will undo and make the data * container reappear. * @return whether there were any pending rows to be dismissed. */ public boolean undoPendingDismiss() { boolean existPendingDismisses = existPendingDismisses(); if (existPendingDismisses) { mPendingDismiss.rowContainer.undoContainer.setVisibility(View.GONE); mPendingDismiss.rowContainer.dataContainer .animate() .translationX(0) .alpha(1) .setDuration(mAnimationTime) .setListener(null); mPendingDismiss = null; } return existPendingDismisses; } private void processPendingDismisses(final PendingDismissData pendingDismissData) { mPendingDismiss = null; final ViewGroup.LayoutParams lp = pendingDismissData.rowContainer.container.getLayoutParams(); final int originalHeight = pendingDismissData.rowContainer.container.getHeight(); ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mCallbacks.canDismiss(pendingDismissData.position)) mCallbacks.onDismiss(mRecyclerView, pendingDismissData.position); pendingDismissData.rowContainer.dataContainer.post(new Runnable() { @Override public void run() { pendingDismissData.rowContainer.dataContainer.setTranslationX(0); pendingDismissData.rowContainer.dataContainer.setAlpha(1); pendingDismissData.rowContainer.undoContainer.setVisibility(View.GONE); pendingDismissData.rowContainer.undoContainer.setTranslationX(0); pendingDismissData.rowContainer.undoContainer.setAlpha(1); lp.height = originalHeight; pendingDismissData.rowContainer.container.setLayoutParams(lp); } }); } }); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { lp.height = (Integer) valueAnimator.getAnimatedValue(); pendingDismissData.rowContainer.container.setLayoutParams(lp); } }); animator.start(); } 

}