防微信左滑删除的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 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); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | 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(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | <?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
1 2 3 4 5 6 7 8 | <?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
1 2 3 4 | <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> |
效果图:
最后,关注【码上加油站】微信公众号后,有疑惑有问题想加油的小伙伴可以码上加入社群,让我们一起码上加油吧!!!
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步