package com.loaderman.swiperecycleviewdemo; import android.content.Context; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; public class SwipeRecycleView extends RecyclerView { private SwipeMenuLayout mLastMenuLayout; private int mLastTouchPosition; protected int mScaleTouchSlop; public SwipeRecycleView(Context context) { this(context, null); } public SwipeRecycleView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public SwipeRecycleView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mScaleTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } private int mLastX, mLastY; private int mDownX, mDownY; @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean isIntercepted = super.onInterceptTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = (int) event.getX(); mLastY = (int) event.getY(); mDownX = (int) event.getX(); mDownY = (int) event.getY(); isIntercepted = false; //根据MotionEvent的X Y值得到子View View view = findChildViewUnder(mLastX, mLastY); if (view == null) return false; //点击的子View所在的位置 final int touchPos = getChildAdapterPosition(view); if (touchPos != mLastTouchPosition && mLastMenuLayout != null && mLastMenuLayout.currentState != SwipeMenuLayout.STATE_CLOSED) { if (mLastMenuLayout.isMenuOpen()) { //如果之前的菜单栏处于打开状态,则关闭它 mLastMenuLayout.smoothToCloseMenu(); } isIntercepted = true; } else { //根据点击位置获得相应的子View ViewHolder holder = findViewHolderForAdapterPosition(touchPos); if (holder != null) { View childView = holder.itemView; if (childView != null && childView instanceof SwipeMenuLayout) { mLastMenuLayout = (SwipeMenuLayout) childView; mLastTouchPosition = touchPos; } } } break; case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: int dx = (int) (mDownX - event.getX()); int dy = (int) (mDownY - event.getY()); if (Math.abs(dx) > mScaleTouchSlop && Math.abs(dx) > Math.abs(dy) || (mLastMenuLayout != null && mLastMenuLayout.currentState != SwipeMenuLayout.STATE_CLOSED)) { //如果X轴偏移量大于Y轴偏移量 或者上一个打开的菜单还没有关闭 则禁止RecycleView滑动 RecycleView不去拦截事件 return false; } break; } return isIntercepted; } @Override public boolean onTouchEvent(MotionEvent e) { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: //若某个Item的菜单还没有关闭,则RecycleView不能滑动 if (!mLastMenuLayout.isMenuClosed()) { return false; } break; case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_UP: if (mLastMenuLayout != null && mLastMenuLayout.isMenuOpen()) { mLastMenuLayout.smoothToCloseMenu(); } break; } return super.onTouchEvent(e); } }
package com.loaderman.swiperecycleviewdemo; import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.LinearLayout; import android.widget.OverScroller; public class SwipeMenuLayout extends LinearLayout { public static final int STATE_CLOSED = 0;//关闭状态 public static final int STATE_OPEN = 1;//打开状态 public static final int STATE_MOVING_LEFT = 2;//左滑将要打开状态 public static final int STATE_MOVING_RIGHT = 3;//右滑将要关闭状态 public int currentState = 0; private int menuWidth;//菜单总长度 private OverScroller mScroller; private int mScaledTouchSlop; private int mRightId;//右边隐藏菜单id private View rightMenuView;//右边的菜单按钮 private int mLastX, mLastY; private int mDownX, mDownY; public SwipeMenuLayout(Context context) { this(context, null); } public SwipeMenuLayout(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public SwipeMenuLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScroller = new OverScroller(context); ViewConfiguration configuration = ViewConfiguration.get(getContext()); mScaledTouchSlop = configuration.getScaledTouchSlop(); configuration.getScaledMaximumFlingVelocity(); configuration.getScaledMinimumFlingVelocity(); //获取右边菜单id TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwipeMenuLayout); mRightId = typedArray.getResourceId(R.styleable.SwipeMenuLayout_right_id, 0); typedArray.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildren(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override protected void onFinishInflate() { super.onFinishInflate(); if (mRightId != 0) { rightMenuView = findViewById(mRightId); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { menuWidth = rightMenuView.getMeasuredWidth(); super.onLayout(changed, l, t, r, b); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = (int) event.getX(); mDownY = (int) event.getY(); mLastX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: int dx = (int) (mDownX - event.getX()); int dy = (int) (mDownY - event.getY()); //如果Y轴偏移量大于X轴偏移量 不再滑动 if (Math.abs(dy) > Math.abs(dx)) return false; int deltaX = (int) (mLastX - event.getX()); if (deltaX > 0) { //向左滑动 currentState = STATE_MOVING_LEFT; if (deltaX >= menuWidth || getScrollX() + deltaX >= menuWidth) { //右边缘检测 scrollTo(menuWidth, 0); currentState = STATE_OPEN; break; } } else if (deltaX < 0) { //向右滑动 currentState = STATE_MOVING_RIGHT; if (deltaX + getScrollX() <= 0) { //左边缘检测 scrollTo(0, 0); currentState = STATE_CLOSED; break; } } scrollBy(deltaX, 0); mLastX = (int) event.getX(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (currentState == STATE_MOVING_LEFT) { //左滑打开 mScroller.startScroll(getScrollX(), 0, menuWidth - getScrollX(), 0, 300); invalidate(); } else if (currentState == STATE_MOVING_RIGHT || currentState == STATE_OPEN) { //右滑关闭 smoothToCloseMenu(); } //如果小于滑动距离并且菜单是关闭状态 此时Item可以有点击事件 int deltx = (int) (mDownX - event.getX()); return !(Math.abs(deltx) < mScaledTouchSlop && isMenuClosed()) || super.onTouchEvent(event); } return super.onTouchEvent(event); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { // Get current x and y positions int currX = mScroller.getCurrX(); int currY = mScroller.getCurrY(); scrollTo(currX, currY); postInvalidate(); } if (isMenuOpen()) { currentState = STATE_OPEN; } else if (isMenuClosed()) { currentState = STATE_CLOSED; } } /** * 判断menu此时的状态 * * @return true 打开状态 false 处于关闭状态 */ public boolean isMenuOpen() { return getScrollX() >= menuWidth; } /** * 判断menu此时的状态 * * @return true 关闭状态 false 未关闭状态 */ public boolean isMenuClosed() { return getScrollX() <= 0; } /** * 关闭菜单 */ public void smoothToCloseMenu() { mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 300); invalidate(); } }
package com.loaderman.swiperecycleviewdemo; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; public class SwipeAdapter extends RecyclerView.Adapter<SwipeAdapter.SwipeHolder> { private Context mContext; public SwipeAdapter(Context mContext) { this.mContext = mContext; } @Override public SwipeHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.swipe_menu_item, parent, false); return new SwipeHolder(view); } @Override public void onBindViewHolder(final SwipeHolder holder, final int position) { holder.tv_content.setText("这是第" + (position + 1) + "条数据"); holder.tv_to_unread.setVisibility(position % 2 == 0 ? View.VISIBLE : View.GONE); holder.tv_to_top.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (holder.swipe_menu.isMenuOpen()) { holder.swipe_menu.smoothToCloseMenu(); } Toast.makeText(mContext, "置顶", Toast.LENGTH_SHORT).show(); } }); holder.tv_to_unread.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (holder.swipe_menu.isMenuOpen()) { holder.swipe_menu.smoothToCloseMenu(); } Toast.makeText(mContext, "标为未读", Toast.LENGTH_SHORT).show(); } }); holder.tv_to_delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (holder.swipe_menu.isMenuOpen()) { holder.swipe_menu.smoothToCloseMenu(); } Toast.makeText(mContext, "删除", Toast.LENGTH_SHORT).show(); } }); holder.swipe_menu.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mContext, "这是第" + (position + 1) + "条数据", Toast.LENGTH_SHORT).show(); } }); } @Override public int getItemCount() { return 30; } public class SwipeHolder extends RecyclerView.ViewHolder { private TextView tv_to_top, tv_to_unread, tv_to_delete, tv_content; private SwipeMenuLayout swipe_menu; public SwipeHolder(View itemView) { super(itemView); swipe_menu = (SwipeMenuLayout) itemView.findViewById(R.id.swipe_menu); tv_to_top = (TextView) itemView.findViewById(R.id.tv_to_top); tv_to_unread = (TextView) itemView.findViewById(R.id.tv_to_unread); tv_to_delete = (TextView) itemView.findViewById(R.id.tv_to_delete); tv_content = (TextView) itemView.findViewById(R.id.tv_content); } } }
package com.loaderman.swiperecycleviewdemo; import android.graphics.Rect; import android.support.v7.widget.RecyclerView; import android.view.View; public class MyDividerDecoration extends RecyclerView.ItemDecoration { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { // super.getItemOffsets(outRect, view, parent, state); outRect.set(0, 0, 0, 2); } }
package com.loaderman.swiperecycleviewdemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SwipeRecycleView swipe_recycleview = (SwipeRecycleView) findViewById(R.id.swipe_recycleview); swipe_recycleview.setLayoutManager(new LinearLayoutManager(this)); swipe_recycleview.addItemDecoration(new MyDividerDecoration()); SwipeAdapter swipeAdapter = new SwipeAdapter(this); swipe_recycleview.setAdapter(swipeAdapter); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.loaderman.swiperecycleviewdemo.MainActivity"> <com.loaderman.swiperecycleviewdemo.SwipeRecycleView android:id="@+id/swipe_recycleview" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
swipe_menu_item.xml
<?xml version="1.0" encoding="utf-8"?> <com.loaderman.swiperecycleviewdemo.SwipeMenuLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/swipe_menu" android:layout_width="match_parent" android:layout_height="70dp" android:layout_centerInParent="true" android:background="@color/white" android:orientation="horizontal" app:content_id="@+id/ll_layout" app:right_id="@+id/ll_right_menu"> <LinearLayout android:id="@+id/ll_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <TextView android:id="@+id/tv_content" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginLeft="20dp" android:gravity="center_vertical" android:text="HelloWorld" android:textSize="16sp" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="right" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:gravity="center_vertical|end" android:text="左滑←←←" android:textSize="16sp" /> </LinearLayout> <LinearLayout android:id="@+id/ll_right_menu" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal"> <TextView android:id="@+id/tv_to_top" android:layout_width="90dp" android:layout_height="match_parent" android:background="@color/gray_holo_light" android:gravity="center" android:text="置顶" android:textColor="@color/white" android:textSize="16sp" /> <TextView android:id="@+id/tv_to_unread" android:layout_width="90dp" android:layout_height="match_parent" android:background="@color/yellow" android:gravity="center" android:text="标为未读" android:textColor="@color/white" android:textSize="16sp" /> <TextView android:id="@+id/tv_to_delete" android:layout_width="90dp" android:layout_height="match_parent" android:background="@color/red_f" android:gravity="center" android:text="删除" android:textColor="@color/white" android:textSize="16sp" /> </LinearLayout> </com.loaderman.swiperecycleviewdemo.SwipeMenuLayout>
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="SwipeMenuLayout"> <!-- format="reference"意为参考某一资源ID --> <attr name="content_id" format="reference" /> <attr name="right_id" format="reference" /> </declare-styleable> </resources>
color.xml
<color name="white">#fff</color> <color name="red_f">#ff3c00</color> <item name="gray_holo_light" type="color">#ffd0d0d0</item> <color name="yellow">#c0ffbd21</color>
效果图: