如何使SwipeableCardViews
更像IOS 7邮件应用程序(滑动以显示按钮)
到目前为止,我已经创建了一个Android应用程序,允许用户向左或向右滑动Cardviews
。每张卡都有2个按钮,我将在以后分配功能。
(如下图所示):
我想要完成的是(但还不知道如何),而不是向左或向右滑动以完全移除卡片。
相反,我想要滑动手势,以显示隐藏在右或左卡下面的按钮。
IOS 7邮件应用程序已经执行此功能(下面的屏幕截图)我想在Android中使用Cardviews
(不是ListViews
)执行此操作。
我该怎样做呢?
到目前为止我的代码:
MainActivity.java
public class MainActivity extends ActionBarActivity {
private RecyclerView mRecyclerView;
private CardViewAdapter mAdapter;
private ArrayList<String> mItems;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mItems = new ArrayList<>(30);
for (int i = 0; i < 30; i++) {
mItems.add(String.format("Card number %2d", i));
}
OnItemTouchListener itemTouchListener = new OnItemTouchListener() {
@Override
public void onCardViewTap(View view, int position) {
Toast.makeText(MainActivity.this, "Tapped " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
@Override
public void onButton1Click(View view, int position) {
Toast.makeText(MainActivity.this, "Clicked Button1 in " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
@Override
public void onButton2Click(View view, int position) {
Toast.makeText(MainActivity.this, "Clicked Button2 in " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
};
mAdapter = new CardViewAdapter(mItems, itemTouchListener);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
SwipeableRecyclerViewTouchListener swipeTouchListener =
new SwipeableRecyclerViewTouchListener(mRecyclerView,
new SwipeableRecyclerViewTouchListener.SwipeListener() {
@Override
public boolean canSwipe(int position) {
return true;
}
@Override
public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
mItems.remove(position);
mAdapter.notifyItemRemoved(position);
}
mAdapter.notifyDataSetChanged();
}
@Override
public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
mItems.remove(position);
mAdapter.notifyItemRemoved(position);
}
mAdapter.notifyDataSetChanged();
}
});
mRecyclerView.addOnItemTouchListener(swipeTouchListener);
}
/**
* Interface for the touch events in each item
*/
public interface OnItemTouchListener {
/**
* Callback invoked when the user Taps one of the RecyclerView items
*
* @param view the CardView touched
* @param position the index of the item touched in the RecyclerView
*/
public void onCardViewTap(View view, int position);
/**
* Callback invoked when the Button1 of an item is touched
*
* @param view the Button touched
* @param position the index of the item touched in the RecyclerView
*/
public void onButton1Click(View view, int position);
/**
* Callback invoked when the Button2 of an item is touched
*
* @param view the Button touched
* @param position the index of the item touched in the RecyclerView
*/
public void onButton2Click(View view, int position);
}
/**
* A simple adapter that loads a CardView layout with one TextView and two Buttons, and
* listens to clicks on the Buttons or on the CardView
*/
public class CardViewAdapter extends RecyclerView.Adapter<CardViewAdapter.ViewHolder> {
private List<String> cards;
private OnItemTouchListener onItemTouchListener;
public CardViewAdapter(List<String> cards, OnItemTouchListener onItemTouchListener) {
this.cards = cards;
this.onItemTouchListener = onItemTouchListener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_view_layout, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.title.setText(cards.get(i));
}
@Override
public int getItemCount() {
return cards == null ? 0 : cards.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView title;
private Button button1;
private Button button2;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.card_view_title);
button1 = (Button) itemView.findViewById(R.id.card_view_button1);
button2 = (Button) itemView.findViewById(R.id.card_view_button2);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemTouchListener.onButton1Click(v, getPosition());
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemTouchListener.onButton2Click(v, getPosition());
}
});
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemTouchListener.onCardViewTap(v, getPosition());
}
});
}
}
}
}
SwipeableRecyclerViewTouchListener.java
public class SwipeableRecyclerViewTouchListener implements RecyclerView.OnItemTouchListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private RecyclerView mRecyclerView;
private SwipeListener mSwipeListener;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
// Transient properties
private List<PendingDismissData> mPendingDismisses = new ArrayList<>();
private int mDismissAnimationRefCount = 0;
private float mDownX;
private float mDownY;
private boolean mSwiping;
private int mSwipingSlop;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private View mDownView;
private boolean mPaused;
private float mFinalDelta;
/**
* Constructs a new swipe touch listener for the given {@link android.support.v7.widget.RecyclerView}
*
* @param recyclerView The recycler view whose items should be dismissable by swiping.
* @param listener The listener for the swipe events.
*/
public SwipeableRecyclerViewTouchListener(RecyclerView recyclerView, SwipeListener listener) {
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;
mSwipeListener = listener;
/**
* This will ensure that this SwipeableRecyclerViewTouchListener is paused during list view scrolling.
* If a scroll listener is already assigned, the caller should still pass scroll changes through
* to this listener.
*/
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
setEnabled(newState != RecyclerView.SCROLL_STATE_DRAGGING);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
});
}
/**
* 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;
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
return handleTouchEvent(motionEvent);
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
handleTouchEvent(motionEvent);
}
private boolean handleTouchEvent(MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mRecyclerView.getWidth();
}
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (mPaused) {
break;
}
// 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)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownY = motionEvent.getRawY();
mDownPosition = mRecyclerView.getChildPosition(mDownView);
if (mSwipeListener.canSwipe(mDownPosition)) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
} else {
mDownView = null;
}
}
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mVelocityTracker == null) {
break;
}
if (mDownView != null && mSwiping) {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
}
mFinalDelta = 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(mFinalDelta) > mViewWidth / 2 && mSwiping) {
dismiss = true;
dismissRight = mFinalDelta > 0;
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX && mSwiping) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (mFinalDelta < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
// dismiss
final View downView = mDownView; // mDownView gets null'd before animation ends
final int downPosition = mDownPosition;
++mDismissAnimationRefCount;
mDownView.animate()
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
performDismiss(downView, downPosition);
}
});
} else {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = 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 (!mSwiping && Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
mSwiping = true;
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
}
if (mSwiping) {
mDownView.setTranslationX(deltaX - mSwipingSlop);
mDownView.setAlpha(Math.max(0f, Math.min(1f,
1f - Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
}
private void performDismiss(final View dismissView, final 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.
final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
final int originalHeight = dismissView.getHeight();
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingDismisses);
int[] dismissPositions = new int[mPendingDismisses.size()];
for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
dismissPositions[i] = mPendingDismisses.get(i).position;
}
if (mFinalDelta > 0) {
mSwipeListener.onDismissedBySwipeRight(mRecyclerView, dismissPositions);
} else {
mSwipeListener.onDismissedBySwipeLeft(mRecyclerView, dismissPositions);
}
// Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
// animation with a stale position
mDownPosition = ListView.INVALID_POSITION;
ViewGroup.LayoutParams lp;
for (PendingDismissData pendingDismiss : mPendingDismisses) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalHeight;
pendingDismiss.view.setLayoutParams(lp);
}
// Send a cancel event
long time = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(time, time,
MotionEvent.ACTION_CANCEL, 0, 0, 0);
mRecyclerView.dispatchTouchEvent(cancelEvent);
mPendingDismisses.clear();
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
dismissView.setLayoutParams(lp);
}
});
mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
animator.start();
}
/**
* The callback interface used by {@link SwipeableRecyclerViewTouchListener} to inform its client
* about a swipe of one or more list item positions.
*/
public interface SwipeListener {
/**
* Called to determine whether the given position can be swiped.
*/
boolean canSwipe(int position);
/**
* Called when the item has been dismissed by swiping to the left.
*
* @param recyclerView The originating {@link android.support.v7.widget.RecyclerView}.
* @param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions);
/**
* Called when the item has been dismissed by swiping to the right.
*
* @param recyclerView The originating {@link android.support.v7.widget.RecyclerView}.
* @param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions);
}
class PendingDismissData implements Comparable<PendingDismissData> {
public int position;
public View view;
public PendingDismissData(int position, View view) {
this.position = position;
this.view = view;
}
@Override
public int compareTo(@NonNull PendingDismissData other) {
// Sort by descending position
return other.position - position;
}
}
}
activity_main.xml中
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
card_view_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical"
card_view:cardCornerRadius="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/card_view_title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:gravity="center"
android:textColor="@android:color/black"
android:textSize="24sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="@id/card_view_title"
android:layout_centerHorizontal="true"
android:gravity="center">
<Button
android:id="@+id/card_view_button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1" />
<Button
android:id="@+id/card_view_button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button2" />
</LinearLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>
答案 0 :(得分:5)
仅供参考,您可以使用这些swipe layout库。它包括您正在搜索的滑动操作。希望它有所帮助
答案 1 :(得分:1)
我刚刚做到了这一点。这就是它的样子:
我没有任何CardView
,但您可以拥有任何布局(因此您可以添加支持库CardView
或您喜欢的任何内容。)
这就是我做的。我用了this library。由于库有minSdkVersion
15而我们有14,我直接从GitHub复制粘贴以下类:OnItemClickListener
,RecyclerViewAdapter
,SwipeableItemClickListener
,SwipeToDismissTouchListener
和ViewAdapter
(如果您使用RecyclerView
,这是您唯一需要的。)
由于尚未更新lib,因此您需要将以下方法添加到SwipeableItemClickListener
中:
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
您的列表项布局应具有以下结构:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/list_item_article_height"
>
<!-- This is the layout you see always -->
<include
layout="@layout/list_item_article"
tools:visibility="gone"
/>
<!-- This is the layout shown after swiping. Must have visibility="gone" -->
<LinearLayout
android:visibility="gone"
tools:visibility="visible"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<TextView
android:id="@+id/swipe_cancel"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="2"
android:text="@string/swipe_cancel"
android:gravity="center"
/>
<TextView
android:id="@+id/swipe_delete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/swipe_delete"
android:gravity="center"
android:background="#f00"
android:textColor="#fff"
/>
</LinearLayout>
</FrameLayout>
然后在创建Activity/Fragment
并设置适配器后,只需将此代码添加到RecyclerView
:
final SwipeToDismissTouchListener<RecyclerViewAdapter> touchListener =
new SwipeToDismissTouchListener<>(
new RecyclerViewAdapter(mRecyclerView),
new SwipeToDismissTouchListener.DismissCallbacks<RecyclerViewAdapter>() {
@Override
public boolean canDismiss(int position) {
return true;
}
@Override
public void onDismiss(RecyclerViewAdapter recyclerView, int position) {
// remove item at position and notify adapter
}
}
);
mRecyclerView.setOnTouchListener(touchListener);
mRecyclerView.addOnScrollListener((RecyclerView.OnScrollListener) touchListener.makeScrollListener());
mRecyclerView.addOnItemTouchListener(new SwipeableItemClickListener(
getActivity(),
new OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
if (view.getId() == R.id.swipe_delete) {
touchListener.processPendingDismisses();
} else {
touchListener.undoPendingDismiss();
}
}
}
));
答案 2 :(得分:-1)
如果您只想添加和删除按钮,可以使用RecyclerView但使用RecyclerView.Adapter而不是CardViewAdapter。 这是一个例子:
public class MyActivity extends Activity {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter (see also next example)
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
}
...
}
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private String[] mDataset;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView mTextView;
public ViewHolder(TextView v) {
super(v);
mTextView = v;
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
// set the view's size, margins, paddings and layout parameters
...
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.mTextView.setText(mDataset[position]);
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.length;
}
}