Improve EasyRecyclerView item click
This commit is contained in:
1 parent
f4aae2a027
commit
471919682e
5 files changed
+450
-345
No files matched your search
@@ -35,6 +35,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Html;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Gravity;
|
||||
@@ -57,6 +58,8 @@ import com.hippo.ehviewer.AppContext;
|
||||
import com.hippo.ehviewer.ImageLoader;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.app.MaterialAlertDialog;
|
||||
import com.hippo.ehviewer.cache.AnyCache;
|
||||
import com.hippo.ehviewer.cache.ImageCache;
|
||||
import com.hippo.ehviewer.data.ApiGalleryDetail;
|
||||
import com.hippo.ehviewer.data.Comment;
|
||||
import com.hippo.ehviewer.data.Data;
|
||||
@@ -75,8 +78,6 @@ import com.hippo.ehviewer.drawable.OvalDrawable;
|
||||
import com.hippo.ehviewer.effect.ripple.RippleSalon;
|
||||
import com.hippo.ehviewer.ehclient.DetailUrlParser;
|
||||
import com.hippo.ehviewer.ehclient.EhClient;
|
||||
import com.hippo.ehviewer.cache.AnyCache;
|
||||
import com.hippo.ehviewer.cache.ImageCache;
|
||||
import com.hippo.ehviewer.service.DownloadService;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Constants;
|
||||
@@ -1262,7 +1263,7 @@ public class GalleryDetailActivity extends AbsTranslucentActivity
|
||||
|
||||
}
|
||||
|
||||
private class CommentAdapter extends EasyRecyclerView.FooterAdapter<CommentHolder> {
|
||||
private class CommentAdapter extends RecyclerView.Adapter<CommentHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
@@ -1271,14 +1272,7 @@ public class GalleryDetailActivity extends AbsTranslucentActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentHolder onCreateAndBindFooterViewHolder(ViewGroup parent,
|
||||
View footerView) {
|
||||
// No footer
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentHolder onCreateViewHolderActual(ViewGroup parent,
|
||||
public CommentHolder onCreateViewHolder(ViewGroup parent,
|
||||
int viewType) {
|
||||
View view = mInflater.inflate(R.layout.comments_item, parent, false);
|
||||
RippleSalon.addRipple(view, ColorStateList.valueOf(0x40000000)); // TODO
|
||||
@@ -1286,7 +1280,7 @@ public class GalleryDetailActivity extends AbsTranslucentActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolderActual(CommentHolder holder, int position) {
|
||||
public void onBindViewHolder(CommentHolder holder, int position) {
|
||||
final Comment comment = ((GalleryDetail) mGalleryInfo).comments.get(position);
|
||||
|
||||
holder.userView.setText(comment.user);
|
||||
@@ -1295,7 +1289,7 @@ public class GalleryDetailActivity extends AbsTranslucentActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCountActual() {
|
||||
public int getItemCount() {
|
||||
if (mGalleryInfo instanceof GalleryDetail) {
|
||||
GalleryDetail galleryDetail = (GalleryDetail)mGalleryInfo;
|
||||
if (galleryDetail.comments == null)
|
||||
|
||||
@@ -85,6 +85,14 @@ public final class MathUtils {
|
||||
return (float) Math.sqrt(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
public static boolean near(float x1, float y1, float x2, float y2, float slop) {
|
||||
return dist(x1, y1, x2, y2) < slop;
|
||||
}
|
||||
|
||||
public static boolean near(float x1, float y1, float z1, float x2, float y2, float z2, float slop) {
|
||||
return dist(x1, y1, z1, x2, y2, z2) < slop;
|
||||
}
|
||||
|
||||
public static float mag(float a, float b) {
|
||||
return (float) Math.hypot(a, b);
|
||||
}
|
||||
|
||||
@@ -202,4 +202,9 @@ public final class ViewUtils {
|
||||
y >= view.getTop() &&
|
||||
y < view.getBottom();
|
||||
}
|
||||
|
||||
public static void transformPointToViewLocal(float[] point, View parent, View child) {
|
||||
point[0] += parent.getScrollX() - child.getLeft();
|
||||
point[1] += parent.getScrollY() - child.getTop();
|
||||
}
|
||||
}
|
||||
@@ -34,15 +34,14 @@ import android.widget.TextView;
|
||||
import com.hippo.ehviewer.AppHandler;
|
||||
import com.hippo.ehviewer.ImageLoader;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.cache.ImageCache;
|
||||
import com.hippo.ehviewer.cardview.CardViewSalon;
|
||||
import com.hippo.ehviewer.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.ehclient.ListParser;
|
||||
import com.hippo.ehviewer.cache.ImageCache;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Ui;
|
||||
import com.hippo.ehviewer.util.ViewUtils;
|
||||
import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView.FooterAdapter;
|
||||
import com.hippo.ehviewer.widget.recyclerview.MarginItemDecoration;
|
||||
import com.hippo.ehviewer.widget.refreshlayout.RefreshLayout;
|
||||
|
||||
@@ -704,7 +703,7 @@ public class GalleryListView extends FrameLayout implements RefreshLayout.OnHead
|
||||
}
|
||||
}
|
||||
|
||||
public class GalleryAdapter extends EasyRecyclerView.FooterAdapter<GalleryViewHolder> {
|
||||
public class GalleryAdapter extends RecyclerView.Adapter<GalleryViewHolder> {
|
||||
|
||||
private final Context mContext;
|
||||
private final List<GalleryInfo> mGiList;
|
||||
@@ -718,13 +717,7 @@ public class GalleryListView extends FrameLayout implements RefreshLayout.OnHead
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryViewHolder onCreateAndBindFooterViewHolder(
|
||||
ViewGroup parent, View footerView) {
|
||||
return new GalleryViewHolder(footerView, FooterAdapter.TYPE_FOOTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryViewHolder onCreateViewHolderActual(ViewGroup parent,
|
||||
public GalleryViewHolder onCreateViewHolder(ViewGroup parent,
|
||||
int viewType) {
|
||||
int resId;
|
||||
if (viewType == LIST_MODE_DETAIL)
|
||||
@@ -741,8 +734,10 @@ public class GalleryListView extends FrameLayout implements RefreshLayout.OnHead
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolderActual(GalleryViewHolder holder,
|
||||
public void onBindViewHolder(GalleryViewHolder holder,
|
||||
int position) {
|
||||
mEasyRecyclerView.checkItemCheckedState(holder.itemView, position);
|
||||
|
||||
GalleryInfo gi = mGiList.get(position);
|
||||
final LoadImageView thumb = holder.thumb;
|
||||
final String key = String.valueOf(gi.gid);
|
||||
@@ -801,12 +796,12 @@ public class GalleryListView extends FrameLayout implements RefreshLayout.OnHead
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewTypeActual(int position) {
|
||||
public int getItemViewType(int position) {
|
||||
return mListMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCountActual() {
|
||||
public int getItemCount() {
|
||||
return mGiList.size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hippo Seven
|
||||
* Copyright (C) 2014-2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -8,7 +8,6 @@
|
||||
* 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.
|
||||
@@ -16,37 +15,34 @@
|
||||
|
||||
package com.hippo.ehviewer.widget.recyclerview;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.StaggeredGridLayoutManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ActionMode;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.InputDevice;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SoundEffectConstants;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.ListAdapter;
|
||||
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
import com.hippo.ehviewer.util.ViewUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* Get some code from twoway-view and AbsListView
|
||||
*
|
||||
* @author Hippo
|
||||
*
|
||||
* Add setOnItemClickListener, setOnItemLongClickListener and setChoiceMode for
|
||||
* RecyclerView
|
||||
*/
|
||||
public class EasyRecyclerView extends RecyclerView implements View.OnClickListener,
|
||||
View.OnLongClickListener {
|
||||
|
||||
private static final String TAG = EasyRecyclerView.class.getSimpleName();
|
||||
// Get some code from twoway-view and AbsListView.
|
||||
public class EasyRecyclerView extends RecyclerView {
|
||||
|
||||
private static final String STATE_KEY_CHOICE_MODE = "choiceMode";
|
||||
private static final String STATE_KEY_CHECKED_STATES = "checkedStates";
|
||||
@@ -112,15 +108,27 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
*/
|
||||
private LongSparseArray<Integer> mCheckedIdStates;
|
||||
|
||||
private OnItemClickListener mOnItemClickListener;
|
||||
private OnItemLongClickListener mOnItemLongClickListener;
|
||||
private boolean mNeedOnClickListener = false;
|
||||
private boolean mNeedOnLongClickListener = false;
|
||||
|
||||
private FooterAdapter mAdapter;
|
||||
private Adapter mAdapter;
|
||||
|
||||
private float mVerticalScrollFactor = 0;
|
||||
|
||||
// For click and long click
|
||||
private boolean mPrePressed = false;
|
||||
private boolean mHasPerformedLongPress = false;
|
||||
private View mMotionView;
|
||||
private int mMotionPosition;
|
||||
private CheckForTap mPendingCheckForTap;
|
||||
private CheckForLongPress mPendingCheckForLongPress;
|
||||
private UnsetPressedState mUnsetPressedState;
|
||||
private PerformClick mPerformClick;
|
||||
private final float[] mTmpPoint = new float[2];
|
||||
private int mTouchSlop = -1;
|
||||
private float mStartX;
|
||||
private float mStartY;
|
||||
private OnItemClickListener mOnItemClickListener;
|
||||
private OnItemLongClickListener mOnItemLongClickListener;
|
||||
|
||||
|
||||
public EasyRecyclerView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -133,131 +141,19 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public View getFootView() {
|
||||
if (mAdapter != null)
|
||||
return mAdapter.getFooterView();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isNeedOnClickListener() {
|
||||
if (mOnItemClickListener == null && mChoiceMode == CHOICE_MODE_NONE)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isNeedOnLongClickListener() {
|
||||
if (mOnItemLongClickListener != null || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setAllOnClickListener() {
|
||||
final int count = getChildCount();
|
||||
final View footView = getFootView();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child != footView) {
|
||||
child.setOnClickListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetAllOnClickListener() {
|
||||
final int count = getChildCount();
|
||||
final View footView = getFootView();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child != footView) {
|
||||
child.setOnClickListener(null);
|
||||
child.setClickable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setAllOnLongClickListener() {
|
||||
final int count = getChildCount();
|
||||
final View footView = getFootView();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child != footView) {
|
||||
child.setOnLongClickListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetAllOnLongClickListener() {
|
||||
final int count = getChildCount();
|
||||
final View footView = getFootView();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child != footView) {
|
||||
child.setOnLongClickListener(null);
|
||||
child.setLongClickable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateOnClickState() {
|
||||
boolean newNeedOnClickListener = isNeedOnClickListener();
|
||||
if (mNeedOnClickListener != newNeedOnClickListener) {
|
||||
mNeedOnClickListener = newNeedOnClickListener;
|
||||
if (mNeedOnClickListener)
|
||||
setAllOnClickListener();
|
||||
else
|
||||
resetAllOnClickListener();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateOnLongClickState() {
|
||||
boolean newNeedOnLongClickListener = isNeedOnLongClickListener();
|
||||
if (mNeedOnLongClickListener != newNeedOnLongClickListener) {
|
||||
mNeedOnLongClickListener = newNeedOnLongClickListener;
|
||||
if (mNeedOnLongClickListener)
|
||||
setAllOnLongClickListener();
|
||||
else
|
||||
resetAllOnLongClickListener();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when an item in the
|
||||
* RecyclerView has been clicked.
|
||||
*
|
||||
* @param listener The callback that will be invoked.
|
||||
*/
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
mOnItemClickListener = listener;
|
||||
updateOnClickState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when an item in the
|
||||
* RecyclerView has been clicked and held.
|
||||
*
|
||||
* @param listener The callback that will be invoked.
|
||||
*/
|
||||
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
|
||||
mOnItemLongClickListener = listener;
|
||||
updateOnLongClickState();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* adapter must be {@link com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView.FooterAdapter}
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setAdapter(RecyclerView.Adapter adapter) {
|
||||
|
||||
super.setAdapter(adapter);
|
||||
|
||||
mAdapter = (FooterAdapter) adapter;
|
||||
mAdapter.mHolder = this;
|
||||
mAdapter = adapter;
|
||||
if (adapter != null) {
|
||||
if (mChoiceMode != CHOICE_MODE_NONE && adapter.hasStableIds() &&
|
||||
mCheckedIdStates == null) {
|
||||
mCheckedIdStates = new LongSparseArray<Integer>();
|
||||
mCheckedIdStates = new LongSparseArray<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +195,7 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
* @see #setChoiceMode(int)
|
||||
*/
|
||||
public boolean isItemChecked(int position) {
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
|
||||
return mCheckStates.get(position);
|
||||
}
|
||||
@@ -342,7 +239,7 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
/**
|
||||
* Returns the set of checked items ids. The result is only valid if the
|
||||
* choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
|
||||
* has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
|
||||
* has stable IDs. ({@link android.widget.ListAdapter#hasStableIds()} == {@code true})
|
||||
*
|
||||
* @return A new array which contains the id of each checked item in the
|
||||
* list.
|
||||
@@ -394,7 +291,7 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
|
||||
if (mMultiChoiceModeCallback == null ||
|
||||
!mMultiChoiceModeCallback.hasWrappedCallback()) {
|
||||
throw new IllegalStateException("AbsListView: attempted to start selection mode " +
|
||||
throw new IllegalStateException("EasyRecyclerView: attempted to start selection mode " +
|
||||
"for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
|
||||
"supplied. Call setMultiChoiceModeListener to set a callback.");
|
||||
}
|
||||
@@ -478,16 +375,14 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
mCheckStates = new SparseBooleanArray(0);
|
||||
}
|
||||
if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
|
||||
mCheckedIdStates = new LongSparseArray<Integer>(0);
|
||||
mCheckedIdStates = new LongSparseArray<>(0);
|
||||
}
|
||||
// Modal multi-choice mode only has choices when the mode is active. Clear them.
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
|
||||
clearChoices();
|
||||
setLongClickable(true);
|
||||
}
|
||||
}
|
||||
|
||||
updateOnClickState();
|
||||
updateOnLongClickState();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,17 +410,15 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
final int position = getChildPosition(child);
|
||||
final int position = getChildAdapterPosition(child);
|
||||
setViewChecked(child, mCheckStates.get(position));
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static void setViewChecked(View view, boolean checked) {
|
||||
final boolean useActivated = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
|
||||
if (view instanceof Checkable) {
|
||||
((Checkable) view).setChecked(checked);
|
||||
} else if (useActivated) {
|
||||
} else {
|
||||
view.setActivated(checked);
|
||||
}
|
||||
}
|
||||
@@ -550,7 +443,7 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
|
||||
/**
|
||||
* Gets a scale factor that determines the distance the view should scroll
|
||||
* vertically in response to {@link MotionEvent#ACTION_SCROLL}.
|
||||
* vertically in response to {@link android.view.MotionEvent#ACTION_SCROLL}.
|
||||
* @return The vertical scroll scale factor.
|
||||
*/
|
||||
protected float getVerticalScrollFactor() {
|
||||
@@ -567,81 +460,179 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
return mVerticalScrollFactor;
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
final Parcelable state = new Bundle(super.onSaveInstanceState());
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (!isEnabled()) {
|
||||
// A disabled view that is clickable still consumes the touch
|
||||
// events, it just doesn't respond to them.
|
||||
return isClickable() || isLongClickable();
|
||||
}
|
||||
|
||||
state.putInt(STATE_KEY_CHOICE_MODE, mChoiceMode);
|
||||
state.putParcelable(STATE_KEY_CHECKED_STATES, mCheckStates);
|
||||
state.putParcelable(STATE_KEY_CHECKED_ID_STATES, mCheckedIdStates);
|
||||
state.putInt(STATE_KEY_CHECKED_COUNT, mCheckedItemCount);
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
onTouchDown(ev);
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
onTouchMove(ev);
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP: {
|
||||
onTouchUp(ev);
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
onTouchCancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onTouchEvent(ev);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onRestoreInstanceState(Bundle state) {
|
||||
mChoiceMode = ChoiceMode.values()[state.getInt(STATE_KEY_CHOICE_MODE)];
|
||||
mCheckedStates = state.getParcelable(STATE_KEY_CHECKED_STATES);
|
||||
mCheckedIdStates = state.getParcelable(STATE_KEY_CHECKED_ID_STATES);
|
||||
mCheckedCount = state.getInt(STATE_KEY_CHECKED_COUNT);
|
||||
private void onTouchDown(MotionEvent ev) {
|
||||
final float x = ev.getX();
|
||||
final float y = ev.getY();
|
||||
mStartX = x;
|
||||
mStartY = y;
|
||||
|
||||
// TODO confirm ids here
|
||||
}*/
|
||||
mMotionView = findChildViewUnder(x, y);
|
||||
mMotionPosition = getChildAdapterPosition(mMotionView);
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when an item in the
|
||||
* EasyRecyclerView has been clicked.
|
||||
*/
|
||||
public interface OnItemClickListener {
|
||||
/**
|
||||
* Callback method to be invoked when an item in the EasyRecyclerView
|
||||
* has been clicked.
|
||||
*
|
||||
* @param parent The EasyRecyclerView where the click happened.
|
||||
* @param view The view within the EasyRecyclerView that was clicked
|
||||
* @param position The position of the view in the adapter.
|
||||
* @param id The row id of the item that was clicked.
|
||||
*
|
||||
* @return true if the callback consumed the click, false otherwise
|
||||
*/
|
||||
boolean onItemClick(EasyRecyclerView parent, View view, int position, long id);
|
||||
if (mMotionView != null && mMotionPosition >= 0) {
|
||||
mHasPerformedLongPress = false;
|
||||
|
||||
mPrePressed = true;
|
||||
if (mPendingCheckForTap == null) {
|
||||
mPendingCheckForTap = new CheckForTap();
|
||||
}
|
||||
mPendingCheckForTap.x = x;
|
||||
mPendingCheckForTap.y = y;
|
||||
mPendingCheckForTap.v = mMotionView;
|
||||
mPendingCheckForTap.p = mMotionPosition;
|
||||
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
|
||||
|
||||
} else {
|
||||
mMotionView = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when an item in the
|
||||
* EasyRecyclerView has been clicked and held.
|
||||
*/
|
||||
public interface OnItemLongClickListener {
|
||||
/**
|
||||
* Callback method to be invoked when an item in the EasyRecyclerView
|
||||
* has been clicked and held.
|
||||
*
|
||||
* @param parent The EasyRecyclerView where the click happened
|
||||
* @param view The view within the EasyRecyclerView that was clicked
|
||||
* @param position The position of the view in the list
|
||||
* @param id The row id of the item that was clicked
|
||||
*
|
||||
* @return true if the callback consumed the long click, false otherwise
|
||||
*/
|
||||
boolean onItemLongClick(EasyRecyclerView parent, View view, int position, long id);
|
||||
private void onTouchMove(MotionEvent ev) {
|
||||
if (mTouchSlop == -1)
|
||||
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
|
||||
|
||||
if (mMotionView != null && mMotionPosition >= 0 &&
|
||||
!MathUtils.near(mStartX, mStartY, ev.getX(), ev.getY(), mTouchSlop)) {
|
||||
removeTapCallback();
|
||||
if (mMotionView.isPressed()) {
|
||||
// Remove any future long press/tap checks
|
||||
removeLongPressCallback();
|
||||
mMotionView.setPressed(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTouchUp(MotionEvent ev) {
|
||||
View motionView = mMotionView;
|
||||
int motionPosition = mMotionPosition;
|
||||
|
||||
/*
|
||||
* EasyRecyclerView.OnItemTouchListener will not work fine
|
||||
* when item view's child need click action, because they make
|
||||
* touch event deliver different
|
||||
*/
|
||||
if (motionView != null && motionPosition >= 0) {
|
||||
final float x = ev.getX();
|
||||
final float y = ev.getY();
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final int position = getChildPosition(view);
|
||||
final long id = mAdapter.getItemId(position);
|
||||
if (motionView.isPressed() || mPrePressed) {
|
||||
// The button is being released before we actually
|
||||
// showed it as pressed. Make it show the pressed
|
||||
// state now (before scheduling the click) to ensure
|
||||
// the user sees it.
|
||||
final float[] point = mTmpPoint;
|
||||
point[0] = x;
|
||||
point[1] = y;
|
||||
ViewUtils.transformPointToViewLocal(point, EasyRecyclerView.this, motionView);
|
||||
setPressed(motionView, true, point[0], point[1]);
|
||||
|
||||
if (!mHasPerformedLongPress) {
|
||||
// This is a tap, so remove the longpress check
|
||||
removeLongPressCallback();
|
||||
|
||||
// Use a Runnable and post this rather than calling
|
||||
// performClick directly. This lets other visual state
|
||||
// of the view update before click actions start.
|
||||
if (mPerformClick == null) {
|
||||
mPerformClick = new PerformClick();
|
||||
}
|
||||
mPerformClick.v = motionView;
|
||||
mPerformClick.p = motionPosition;
|
||||
mPerformClick.rememberWindowAttachCount();
|
||||
if (!post(mPerformClick)) {
|
||||
mPerformClick.run();
|
||||
}
|
||||
}
|
||||
|
||||
if (mUnsetPressedState == null) {
|
||||
mUnsetPressedState = new UnsetPressedState();
|
||||
}
|
||||
|
||||
mUnsetPressedState.v = motionView;
|
||||
if (mPrePressed) {
|
||||
postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
|
||||
} else if (!post(mUnsetPressedState)) {
|
||||
// If the post failed, unpress right now
|
||||
mUnsetPressedState.run();
|
||||
}
|
||||
|
||||
removeTapCallback();
|
||||
}
|
||||
|
||||
// Release
|
||||
mMotionView = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onTouchCancel() {
|
||||
if (mMotionView != null && mMotionPosition >= 0) {
|
||||
mMotionView.setPressed(false);
|
||||
removeTapCallback();
|
||||
removeLongPressCallback();
|
||||
|
||||
// Release motion view
|
||||
mMotionView = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTapCallback() {
|
||||
if (mPendingCheckForTap != null) {
|
||||
mPrePressed = false;
|
||||
removeCallbacks(mPendingCheckForTap);
|
||||
mPendingCheckForTap.v = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeLongPressCallback() {
|
||||
if (mPendingCheckForLongPress != null) {
|
||||
removeCallbacks(mPendingCheckForLongPress);
|
||||
mPendingCheckForLongPress.v = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPressed(View v, boolean pressed, float x, float y){
|
||||
v.setPressed(pressed);
|
||||
if (pressed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
v.drawableHotspotChanged(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean performItemClick(View view, int position, long id) {
|
||||
boolean handled = false;
|
||||
boolean dispatchItemClick = true;
|
||||
|
||||
if (mChoiceMode != CHOICE_MODE_NONE) {
|
||||
handled = true;
|
||||
boolean checkedStateChanged = false;
|
||||
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
|
||||
@@ -687,36 +678,244 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
}
|
||||
}
|
||||
|
||||
if (dispatchItemClick && mOnItemClickListener != null) {
|
||||
mOnItemClickListener.onItemClick(this, view, position, id);
|
||||
if (dispatchItemClick) {
|
||||
if (mOnItemClickListener != null) {
|
||||
playSoundEffect(SoundEffectConstants.CLICK);
|
||||
mOnItemClickListener.onItemClick(this, view, position, id);
|
||||
if (view != null) {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
final int position = getChildPosition(view);
|
||||
final long id = mAdapter.getItemId(position);
|
||||
|
||||
private boolean performItemLongPress(final View child,
|
||||
final int longPressPosition, final long longPressId) {
|
||||
// CHOICE_MODE_MULTIPLE_MODAL takes over long press.
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
|
||||
if (mChoiceActionMode == null &&
|
||||
(mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
|
||||
setItemChecked(position, true);
|
||||
setItemChecked(longPressPosition, true);
|
||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean handled = false;
|
||||
if (mOnItemLongClickListener != null) {
|
||||
handled = mOnItemLongClickListener.onItemLongClick(this, view,
|
||||
position, id);
|
||||
handled = mOnItemLongClickListener.onItemLongClick(this, child,
|
||||
longPressPosition, longPressId);
|
||||
}
|
||||
if (handled) {
|
||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
/**
|
||||
* A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
|
||||
* Register a callback to be invoked when an item in this AdapterView has
|
||||
* been clicked.
|
||||
*
|
||||
* @param listener The callback that will be invoked.
|
||||
*/
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
mOnItemClickListener = listener;
|
||||
}
|
||||
|
||||
|
||||
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
|
||||
if (!isLongClickable()) {
|
||||
setLongClickable(true);
|
||||
}
|
||||
mOnItemLongClickListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* If choice mode is set, call it in {@link Adapter#onBindViewHolder}
|
||||
*/
|
||||
public void checkItemCheckedState(View view, int position) {
|
||||
setViewChecked(view, isItemChecked(position));
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
final Parcelable state = new Bundle(super.onSaveInstanceState());
|
||||
|
||||
state.putInt(STATE_KEY_CHOICE_MODE, mChoiceMode);
|
||||
state.putParcelable(STATE_KEY_CHECKED_STATES, mCheckStates);
|
||||
state.putParcelable(STATE_KEY_CHECKED_ID_STATES, mCheckedIdStates);
|
||||
state.putInt(STATE_KEY_CHECKED_COUNT, mCheckedItemCount);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public void onRestoreInstanceState(Bundle state) {
|
||||
mChoiceMode = ChoiceMode.values()[state.getInt(STATE_KEY_CHOICE_MODE)];
|
||||
mCheckedStates = state.getParcelable(STATE_KEY_CHECKED_STATES);
|
||||
mCheckedIdStates = state.getParcelable(STATE_KEY_CHECKED_ID_STATES);
|
||||
mCheckedCount = state.getInt(STATE_KEY_CHECKED_COUNT);
|
||||
|
||||
// TODO confirm ids here
|
||||
}*/
|
||||
|
||||
private final class CheckForTap implements Runnable {
|
||||
float x;
|
||||
float y;
|
||||
View v;
|
||||
int p;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
View view = v;
|
||||
int position = p;
|
||||
if (view != null && position >= 0) {
|
||||
|
||||
mPrePressed = false;
|
||||
final float[] point = mTmpPoint;
|
||||
point[0] = x;
|
||||
point[1] = y;
|
||||
ViewUtils.transformPointToViewLocal(point, EasyRecyclerView.this, view);
|
||||
setPressed(v, true, point[0], point[1]);
|
||||
|
||||
if (isLongClickable()) {
|
||||
mHasPerformedLongPress = false;
|
||||
|
||||
if (mPendingCheckForLongPress == null) {
|
||||
mPendingCheckForLongPress = new CheckForLongPress();
|
||||
}
|
||||
mPendingCheckForLongPress.rememberWindowAttachCount();
|
||||
mPendingCheckForLongPress.v = view;
|
||||
mPendingCheckForLongPress.p = position;
|
||||
postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() -
|
||||
ViewConfiguration.getTapTimeout());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Release
|
||||
v = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class CheckForLongPress extends WindowRunnnable implements Runnable {
|
||||
View v;
|
||||
int p;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
View view = v;
|
||||
int position = p;
|
||||
if (view != null && position >= 0 && view.isPressed() && view.getParent() != null) {
|
||||
boolean handled = false;
|
||||
|
||||
if (sameWindow()) {
|
||||
handled = performItemLongPress(view, position, getAdapter().getItemId(position));
|
||||
}
|
||||
if (handled) {
|
||||
mHasPerformedLongPress = true;
|
||||
view.setPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Release
|
||||
v = null;
|
||||
}
|
||||
}
|
||||
|
||||
private final class UnsetPressedState implements Runnable {
|
||||
View v;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (v != null) {
|
||||
v.setPressed(false);
|
||||
|
||||
// Release
|
||||
v = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PerformClick extends WindowRunnnable implements Runnable {
|
||||
View v;
|
||||
int p;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
View view = v;
|
||||
int position = p;
|
||||
if (view != null && position >= 0 && sameWindow()) {
|
||||
performItemClick(view, position, mAdapter.getItemId(position));
|
||||
}
|
||||
|
||||
// Release
|
||||
v = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for Runnables that will check that their view is still attached to
|
||||
* the original window as when the Runnable was created.
|
||||
*
|
||||
*/
|
||||
private class WindowRunnnable {
|
||||
private int mOriginalAttachCount;
|
||||
|
||||
public void rememberWindowAttachCount() {
|
||||
mOriginalAttachCount = getWindowAttachCount();
|
||||
}
|
||||
|
||||
public boolean sameWindow() {
|
||||
return getWindowAttachCount() == mOriginalAttachCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when an item in the
|
||||
* EasyRecyclerView has been clicked.
|
||||
*/
|
||||
public interface OnItemClickListener {
|
||||
/**
|
||||
* Callback method to be invoked when an item in the EasyRecyclerView
|
||||
* has been clicked.
|
||||
*
|
||||
* @param parent The EasyRecyclerView where the click happened.
|
||||
* @param view The view within the EasyRecyclerView that was clicked
|
||||
* @param position The position of the view in the adapter.
|
||||
* @param id The row id of the item that was clicked.
|
||||
*
|
||||
* @return true if the callback consumed the click, false otherwise
|
||||
*/
|
||||
boolean onItemClick(EasyRecyclerView parent, View view, int position, long id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when an item in the
|
||||
* EasyRecyclerView has been clicked and held.
|
||||
*/
|
||||
public interface OnItemLongClickListener {
|
||||
/**
|
||||
* Callback method to be invoked when an item in the EasyRecyclerView
|
||||
* has been clicked and held.
|
||||
*
|
||||
* @param parent The EasyRecyclerView where the click happened
|
||||
* @param view The view within the EasyRecyclerView that was clicked
|
||||
* @param position The position of the view in the list
|
||||
* @param id The row id of the item that was clicked
|
||||
*
|
||||
* @return true if the callback consumed the long click, false otherwise
|
||||
*/
|
||||
boolean onItemLongClick(EasyRecyclerView parent, View view, int position, long id);
|
||||
}
|
||||
|
||||
/**
|
||||
* A MultiChoiceModeListener receives events for {@link android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
|
||||
* It acts as the {@link ActionMode.Callback} for the selection mode and also receives
|
||||
* {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
|
||||
* selects and deselects list items.
|
||||
@@ -731,7 +930,7 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
* @param checked <code>true</code> if the item is now checked, <code>false</code>
|
||||
* if the item is now unchecked.
|
||||
*/
|
||||
public void onItemCheckedStateChanged(ActionMode mode,
|
||||
void onItemCheckedStateChanged(ActionMode mode,
|
||||
int position, long id, boolean checked);
|
||||
}
|
||||
|
||||
@@ -750,6 +949,7 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
if (mWrapped.onCreateActionMode(mode, menu)) {
|
||||
// Initialize checked graphic state?
|
||||
setLongClickable(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -774,6 +974,8 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
clearChoices();
|
||||
|
||||
requestLayout();
|
||||
|
||||
setLongClickable(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -787,103 +989,4 @@ public class EasyRecyclerView extends RecyclerView implements View.OnClickListen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class FooterAdapter<VH extends ViewHolder> extends RecyclerView.Adapter<VH> {
|
||||
|
||||
public static final int TYPE_FOOTER = Integer.MAX_VALUE;
|
||||
|
||||
private EasyRecyclerView mHolder;
|
||||
private View mFooterView;
|
||||
|
||||
public boolean hasFootView() {
|
||||
return mFooterView != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* footerView == null to remove foot view
|
||||
* @param footerView
|
||||
*/
|
||||
public void setFooterView(View footerView) {
|
||||
if (mFooterView == footerView)
|
||||
return;
|
||||
|
||||
int itemCountActual = getItemCountActual();
|
||||
View originView = mFooterView;
|
||||
mFooterView = footerView;
|
||||
if (originView != null) {
|
||||
if (mFooterView != null)
|
||||
notifyItemChanged(itemCountActual);
|
||||
else
|
||||
notifyItemRemoved(itemCountActual);
|
||||
} else {
|
||||
notifyItemInserted(itemCountActual);
|
||||
}
|
||||
}
|
||||
|
||||
public View getFooterView() {
|
||||
return mFooterView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final VH onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (mFooterView != null && viewType == TYPE_FOOTER) {
|
||||
StaggeredGridLayoutManager.LayoutParams lp = new StaggeredGridLayoutManager.LayoutParams(
|
||||
StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT,
|
||||
StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT);
|
||||
lp.setFullSpan(true);
|
||||
mFooterView.setLayoutParams(lp);
|
||||
return onCreateAndBindFooterViewHolder(parent, mFooterView);
|
||||
} else {
|
||||
return onCreateViewHolderActual(parent, viewType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onBindViewHolder(VH holder, int position) {
|
||||
if (mFooterView == null || position != getItemCountActual()) {
|
||||
onBindViewHolderActual(holder, position);
|
||||
if (mHolder.mNeedOnClickListener)
|
||||
holder.itemView.setOnClickListener(mHolder);
|
||||
if (mHolder.mNeedOnLongClickListener)
|
||||
holder.itemView.setOnLongClickListener(mHolder);
|
||||
EasyRecyclerView.setViewChecked(holder.itemView,
|
||||
mHolder.isItemChecked(position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getItemViewType(int position) {
|
||||
if (mFooterView != null && position == getItemCountActual())
|
||||
return TYPE_FOOTER;
|
||||
else
|
||||
return getItemViewTypeActual(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getItemCount() {
|
||||
int itemCount = getItemCountActual();
|
||||
if (mFooterView != null)
|
||||
itemCount++;
|
||||
return itemCount;
|
||||
}
|
||||
|
||||
public abstract VH onCreateAndBindFooterViewHolder(ViewGroup parent, View footerView);
|
||||
|
||||
public abstract VH onCreateViewHolderActual(ViewGroup parent, int viewType);
|
||||
|
||||
public abstract void onBindViewHolderActual(VH holder, int position);
|
||||
|
||||
/**
|
||||
* Foot view type is Integer.MAX_VALUE, do not be the same
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public int getItemViewTypeActual(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public abstract int getItemCountActual();
|
||||
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user