Java tutorial
/* * MonMa: Eine freie Android-Application fuer die Verwaltung privater Finanzen * * Copyright [2015] [Alexander Winkler, 2373 Dahme/Germany] * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; if * not, see <http://www.gnu.org/licenses/>. */ package de.aw.awlib.adapters; import android.support.annotation.CallSuper; import android.support.annotation.IdRes; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.v4.view.MotionEventCompat; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import de.aw.awlib.R; import de.aw.awlib.recyclerview.AWLibViewHolder; import de.aw.awlib.recyclerview.AWSimpleItemTouchHelperCallback; import static android.support.v7.widget.RecyclerView.NO_POSITION; import static android.support.v7.widget.RecyclerView.OnScrollListener; import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING; /** * Basis-Adapter fuer RecyclerView. Unterstuetzt Swipe und Drag. */ public abstract class AWBaseAdapter extends RecyclerView.Adapter<AWLibViewHolder> implements AWLibViewHolder.OnHolderClickListener, AWLibViewHolder.OnHolderLongClickListener { static final int UNDODELETEVIEW = -1; private static final int CHANGEDVIEW = UNDODELETEVIEW - 1; private final AWBaseAdapterBinder mBinder; private final int[] mViewHolderResIDs; private RecyclerView mRecyclerView; private int mTextResID = R.string.tvGeloescht; private AWOnScrollListener mOnScrollListener; private AWSimpleItemTouchHelperCallback callbackTouchHelper; private ItemTouchHelper mTouchHelper; private int onTouchStartDragResID = -1; private OnDragListener mOnDragListener; private OnSwipeListener mOnSwipeListener; private int mPendingChangedItemPosition = NO_POSITION; private int mPendingDeleteItemPosition = NO_POSITION; private int mPendingChangeLayout; /** * Initialisiert Adapter. * * @param binder * Binder fuer onBindView */ public AWBaseAdapter(AWBaseAdapterBinder binder) { mBinder = binder; mViewHolderResIDs = null; } public AWBaseAdapter(@NonNull AWBaseAdapterBinder binder, @NonNull @LayoutRes int... viewResIds) { mBinder = binder; mViewHolderResIDs = viewResIds; } /** * Cancels PendingSwipe */ @CallSuper public void cancelPendingChange() { int position = mPendingChangedItemPosition; if (position != NO_POSITION) { mPendingChangedItemPosition = NO_POSITION; notifyItemChanged(position); } } /** * Cancels PendingDelete */ @CallSuper public void cancelPendingDelete() { int position = mPendingDeleteItemPosition; if (position != NO_POSITION) { mPendingDeleteItemPosition = NO_POSITION; notifyItemChanged(position); } } private void configure() { if (callbackTouchHelper == null) { callbackTouchHelper = new AWSimpleItemTouchHelperCallback(this); } callbackTouchHelper.setIsSwipeable(mOnSwipeListener != null); callbackTouchHelper.setIsDragable(mOnDragListener != null); mTouchHelper = new ItemTouchHelper(callbackTouchHelper); } /** * Wird gerufen, wenn in einem Konstruktor ein {@link AWBaseAdapterBinder} gesetzt wurde. * * @param holderView * ViewHolder * * @return default: null */ protected AWLibViewHolder createViewHolder(View holderView) { return null; } /** * Entfernt ein Item an der Position. Funktioniert nur, wenn {@link * AWBaseAdapter#setOnSwipeListener(OnSwipeListener)} mit einem SwipeListener gerufen wurde. * Adapter wird mittels notify informiert. * * @param position * Position des items im Adapter, das entfernt werden soll. */ private void dismissItem(int position) { if (position != NO_POSITION) { mPendingDeleteItemPosition = NO_POSITION; onItemDismissed(position); } } /** * @return Liste der IDs der Items, die nach remove bzw. drag noch vorhanden ist. */ public List<Long> getItemIDs() { int size = getItemCount(); List<Long> mItemIDList = new ArrayList<>(size); for (int i = 0; i < size; i++) { mItemIDList.add(getItemId(i)); } return mItemIDList; } @Override public final int getItemViewType(int position) { if (mPendingDeleteItemPosition == position) { return UNDODELETEVIEW; } if (mPendingChangedItemPosition == position) { return CHANGEDVIEW; } return getViewType(position); } /** * @return Die Position eines PendingDeleteItems oder NO_POSITION */ protected final int getPendingDeleteItemPosition() { return mPendingDeleteItemPosition; } /** * Liefert den Typ der View zu eine Position im Adapter. Ist in einem Konstruktor ein {@link * AWBaseAdapterBinder} gesetzt worden, wird dieser fuer die Ermittliung des ViewTypes * aufgerufen. Sollte ueberschrieben werden, wenn in einem Konstruktor mehrer ResIds fuer Views * gesetzt wurden. * * @param position * Position im Adapter * * @return Typ der View. Siehe {@link RecyclerView.Adapter#getItemViewType} */ public int getViewType(int position) { if (mBinder != null) { return mBinder.getItemViewType(position); } return super.getItemViewType(position); } /** * @return Die Position eines PendingSwipeItems oder NO_POSITION */ protected int getPendingSwipeItemPosition() { return mPendingChangedItemPosition; } public final RecyclerView getRecyclerView() { return mRecyclerView; } /** * Wird aus {@link AWBaseAdapter#onBindViewHolder(AWLibViewHolder, int)} gerufen. Erbende * Klassen muesen pruefen, ob der ItemViewType < 0 ist, in diesem Fall wird eine View gezeigt, * die hier bearbeitet wurde. */ @CallSuper @Override public void onBindViewHolder(final AWLibViewHolder holder, final int position) { switch (holder.getItemViewType()) { case UNDODELETEVIEW: View view = holder.itemView.findViewById(R.id.llUndo); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int mPosition = mPendingDeleteItemPosition; mPendingDeleteItemPosition = NO_POSITION; notifyItemChanged(mPosition); } }); TextView tv = holder.itemView.findViewById(R.id.tvGeloescht); tv.setText(mTextResID); view = holder.itemView.findViewById(R.id.llGeloescht); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (holder.getAdapterPosition() != NO_POSITION) { mPendingDeleteItemPosition = NO_POSITION; } onItemDismissed(mPendingDeleteItemPosition); } }); break; default: if (onTouchStartDragResID != -1) { holder.itemView.setHapticFeedbackEnabled(true); View handleView = holder.itemView.findViewById(onTouchStartDragResID); handleView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { AWBaseAdapter.this.onStartDrag(holder); } if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) { AWBaseAdapter.this.onStopDrag(holder); } v.performClick(); return true; } }); } } } @CallSuper @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); mRecyclerView = recyclerView; if (mTouchHelper != null) { mTouchHelper.attachToRecyclerView(mRecyclerView); } mOnScrollListener = new AWOnScrollListener(); mRecyclerView.addOnScrollListener(mOnScrollListener); // Erzwingen, dass Holder vom Typ CHANGEDVIEW und UNDOLETEVIEW immer // wieder neu erstellt werden mRecyclerView.getRecycledViewPool().setMaxRecycledViews(CHANGEDVIEW, 0); mRecyclerView.getRecycledViewPool().setMaxRecycledViews(UNDODELETEVIEW, 0); } public final void onDragged(RecyclerView recyclerView, RecyclerView.ViewHolder from, RecyclerView.ViewHolder to) { mOnDragListener.onDragged(recyclerView, from, to); onItemMoved(from.getAdapterPosition(), to.getAdapterPosition()); } @Override public AWLibViewHolder onCreateViewHolder(ViewGroup viewGroup, int itemType) { LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); View rowView; switch (itemType) { case UNDODELETEVIEW: rowView = inflater.inflate(R.layout.can_undo_view, viewGroup, false); break; case CHANGEDVIEW: rowView = inflater.inflate(mPendingChangeLayout, viewGroup, false); break; default: if (mViewHolderResIDs != null) { rowView = inflater.inflate(mViewHolderResIDs[itemType], viewGroup, false); } else rowView = mBinder.onCreateViewHolder(inflater, viewGroup, itemType); } AWLibViewHolder holder = createViewHolder(rowView); if (holder == null) { holder = new AWLibViewHolder(rowView); } holder.setOnClickListener(this); holder.setOnLongClickListener(this); return holder; } @CallSuper @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { super.onDetachedFromRecyclerView(recyclerView); mRecyclerView.removeOnScrollListener(mOnScrollListener); mRecyclerView = null; } /** * Wird gerufen, wenn ein Item entfernt wurde * * @param position * Position des Items */ protected void onItemDismissed(int position) { } /** * Wird gerufen, wenn ein Item in der Position veraendert wurde * * @param fromPosition * alte Position * @param toPosition * neue Position */ protected void onItemMoved(int fromPosition, int toPosition) { } /** * Wird gerufen, wenn ein ViewHolder gecklicked wurde. * * @param holder * ViewHolder */ protected void onViewHolderClicked(AWLibViewHolder holder) { } private void onStartDrag(RecyclerView.ViewHolder holder) { holder.itemView.setPressed(true); mTouchHelper.startDrag(holder); } private void onStopDrag(AWLibViewHolder holder) { holder.itemView.setPressed(false); } public final void onSwiped(AWLibViewHolder viewHolder, int direction, int position, long id) { mOnSwipeListener.onSwiped(viewHolder, direction, position, id); } @Override public final void onViewHolderClick(AWLibViewHolder holder) { switch (holder.getItemViewType()) { case UNDODELETEVIEW: break; default: onViewHolderClicked(holder); } } /** * Wird gerufen, wenn ein ViewHolder long-gecklicked wurde. * * @param holder * ViewHolder * * @return default: false. */ protected boolean onViewHolderLongClicked(AWLibViewHolder holder) { return false; } @Override public final boolean onViewHolderLongClick(AWLibViewHolder holder) { return onViewHolderLongClicked(holder); } /** * Setzt den OnDragListener. In diesem Fall wird die RecyclerView Dragable * * * @param listener * OnDragListener */ public final void setOnDragListener(OnDragListener listener) { mOnDragListener = listener; configure(); } /** * Setzt den OnSwipeListener. In diesem Fall wird die RecyclerView Swipeable * * @param listener * OnSwipeListener */ public final void setOnSwipeListener(OnSwipeListener listener) { mOnSwipeListener = listener; configure(); } /** * Durch setzen der resID der DetailView wird diese View als OneToch-Draghandler benutzt, d.h. * dass bei einmaligen beruehren dieses Items der Drag/Drop-Vorgang startet. Die resID muss in * onCreate() gesetzt werden. * * @param resID * resID der View, bei deren Beruehrung der Drag/Drop Vorgand starten soll */ public final void setOnTouchStartDragResID(@IdRes int resID) { this.onTouchStartDragResID = resID; } /** * Hier kann ein Item gesetzt werden, dass eine separate View anzeigt. Diese View ist entweder * vom Binder entsprechend zu setzen (in getItemViewType, OnCreateViewHolder) oder durch die im * Konstruktor uebergebeben viewResIds zu bestimmen. Wenn dann die RecyclerView bewegt wird oder * ein anderes Item zu gesetzt wird, wird die View wieder zureuckgesetzt * * @param position * Position des Items */ @CallSuper public void setPendingChangedItemPosition(int position, @LayoutRes int layout) { if (mPendingChangedItemPosition != NO_POSITION) { cancelPendingChange(); } mPendingChangeLayout = layout; this.mPendingChangedItemPosition = position; notifyItemChanged(position); } /** * Hier kann eine Item durch Angabe der Position zum loeschen vorgemerkt werden. In diesem Fall * wird eine View mit 'Geloescht' bzw. 'Rueckgaengig' angezeigt. Wenn dann die RecyclerView * bewegt wird oder ein anderes Item zu Loeschung vorgemerkt wird, wird das Item tatsaechlich * aus dem Adapter entfernt. Der Binder wird durch * {@link AWBaseAdapterBinder#onItemDismissed(int)} * informiert. * * @param position * Position des Items */ @CallSuper public void setPendingDeleteItemPosition(int position) { int mPending = mPendingDeleteItemPosition; if (mPendingDeleteItemPosition != NO_POSITION) { dismissItem(mPending); } if (mPending != position) { mPendingDeleteItemPosition = position; notifyItemChanged(position); } } /** * Setzt die ResID des Textes, der in einer UndoleteView angezeigt wird. Wird die nicht gesetzt, * wird 'Geloescht' angezeigt. * * @param textresID * textResID */ public final void setTextResID(@StringRes int textresID) { mTextResID = textresID; } /** * Swap den Adapter nochmals in die RecyclerView. Sinnvoll, wenn sich Inhalte geaendert haben, * aber keine neuen Daten generiert werden sollen. */ public final void swap() { mRecyclerView.swapAdapter(this, false); } public interface AWBaseAdapterBinder { int getItemViewType(int position); View onCreateViewHolder(LayoutInflater inflater, ViewGroup viewGroup, int itemType); } public interface OnDissmissListener { void onDismiss(int position); } public interface OnDragListener { void onDragged(RecyclerView recyclerView, RecyclerView.ViewHolder from, RecyclerView.ViewHolder to); } public interface OnSwipeListener { void onSwiped(AWLibViewHolder viewHolder, int direction, final int position, final long id); } private class AWOnScrollListener extends OnScrollListener { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { switch (newState) { case SCROLL_STATE_DRAGGING: if (mPendingDeleteItemPosition != NO_POSITION) { dismissItem(mPendingDeleteItemPosition); cancelPendingDelete(); } if (mPendingChangedItemPosition != NO_POSITION) { cancelPendingChange(); } break; } super.onScrollStateChanged(recyclerView, newState); } } }