侧滑删除菜单 SwipeMenuLayout
来源:互联网 发布:蔡司编程视频教程 编辑:程序博客网 时间:2024/05/22 00:20
侧滑菜单在列表布局中越来越常见,其良好的交互为 App 增色了不好,在 Android 中,其实现方式也有很多种,本文是基于自定义 ViewGroup 方式实现,使用时在列表 item 布局中引入该 Layout 即可。
实现效果图:
所用知识点:
- 自定义 ViewGroup
- ScrollTo() 和 ScrollTo() 区别及用法
- getScrollX(),getScrollY() 表示的意义及用法
- Scroller用法
- Android 事件分发机制
1. ScrollTo() 和 ScrollBy() 区别及用法
scrollTo() : 滑动到指定坐标位置点,是绝对滑动。
scrollBy() : 相对于当前位置滑动一段距离,是相对滑动,其内部实现是基于 scrollTo() 实现的,在当前位置坐标点加上滑动距离。
注意:滑动的是 View 的内容,并不是滑动 View 本身。
2. getScrollX() 和 getScrollY() 表示意义和用法
图上面,褐色的框,其实就是我们眼睛看到的手机界面,就是一个窗口。
而绿色的长方体呢,就是一块可以左右拉动的幕布啦,其实也就是我们要显示在窗口上面的内容,它其实是可以很大的,大到无限大,只是没在窗口中间的,所以我们就看不到。
而getScrollX 其实获取的值,就是这块 幕布在窗口左边界时候的值了,而幕布上面哪个点是原点(0,0)呢?就是初始化时内容显示的位置。
所以当我们将幕布往右推动的时候,幕布在窗口左边界的值就会在0的左边(-100),而向左推动,则其值会是在0的右边(100)。
举例:
效果:
3.Scroller 用法
(1).原理介绍:
scrollTo() 和 scrollBy() 实现的是一个结果,即是说,当调用scrollTo(100,0) 时,再重新绘制时,内容已经出现在(100,0)位置上,缺少一个移动的过程,而 Scroller 就是帮助我们实现这个滚动的过程的。
动画的原理其实不停的重绘位置变化的内容,在视觉效果上,就产生了动画的效果。
(2) 使用步骤:
- 创建 Scroller 对象,一般是在 构造方法中创建。
private Scroller mScroller;public SwipeMenuLayout(Context context) { this(context, null);}public SwipeMenuLayout(Context context, AttributeSet attrs) { this(context, attrs, 0);}public SwipeMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScroller = new Scroller(context);}
- 重写 自定义 View 的 computeScroll() 方法。下面代码基本不会变化。
@Overridepublic void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { // 动画没有结束 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); }}
- 调用 Scroller 的 startScroll()方法,并 invalidate() 重绘 View.
mScroller.startScroll(int startX,int startY,int dx,int dy); // startX起始坐标,dx 偏移量invalidate();
4.自定义 ViewGroup - SwipeMenuLayout
- 自定义属性 attrs.xml
<resources> <declare-styleable name="SwipeMenuLayout"> <attr name="leftMenuId" format="reference" /> <attr name="rightMenuId" format="reference" /> <attr name="contentId" format="reference" /> </declare-styleable></resources>
SwipeMenuLayout.java
public class SwipeMenuLayout extends ViewGroup { private static final String TAG = "SwipeMenuLayout"; private Scroller mScroller; private int mScaledTouchSlop; private int leftMenuId; private int rightMenuId; private View leftMenuView; private View rightMenuView; private View contentView; private int contentId; private boolean isSwipeing; // 静态类写入内存共享。用来判断当前界面是否有menu打开 private static SwipeMenuLayout swipeMenuLayout; private static State curState; private static boolean isTouching = false; public SwipeMenuLayout(Context context) { this(context, null); } public SwipeMenuLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwipeMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); readAttrs(context, attrs); // 1.创建 Scroller 对象 mScroller = new Scroller(context); ViewConfiguration viewConfiguration = ViewConfiguration.get(context); mScaledTouchSlop = viewConfiguration.getScaledTouchSlop(); } private void readAttrs(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwipeMenuLayout); try { leftMenuId = typedArray.getResourceId(R.styleable.SwipeMenuLayout_leftMenuId, 0); rightMenuId = typedArray.getResourceId(R.styleable.SwipeMenuLayout_rightMenuId, 0); contentId = typedArray.getResourceId(R.styleable.SwipeMenuLayout_contentId, 0); } finally { typedArray.recycle(); } } /** * 测量方法可能会被调用多次 * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setClickable(true); int viewHeight = 0; int viewWidth = 0; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); childView.setClickable(true); if (childView.getVisibility() == View.GONE) { continue; } measureChild(childView, widthMeasureSpec, heightMeasureSpec); viewHeight = Math.max(viewHeight, childView.getMeasuredHeight()); Log.d(TAG, "onMeasure: getMeasureWidth() = " + i + "," + +childView.getMeasuredWidth()); viewWidth += childView.getMeasuredWidth(); } setMeasuredDimension(viewWidth, viewHeight); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { Log.d(TAG, "onLayout: l = " + l + ",t = " + t + ",r = " + r + ",b = " + b); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); if (leftMenuView == null && childView.getId() == leftMenuId) { leftMenuView = childView; continue; } if (rightMenuView == null && childView.getId() == rightMenuId) { rightMenuView = childView; continue; } if (contentView == null && childView.getId() == contentId) { contentView = childView; } } Log.d(TAG, "onLayout: leftMenuView.getMeasureWidth() = " + leftMenuView.getMeasuredWidth()); Log.d(TAG, "onLayout: contentView.getMeasureWidth() = " + contentView.getMeasuredWidth()); Log.d(TAG, "onLayout: rightMenuView.getMeasureWidth() = " + rightMenuView.getMeasuredWidth()); // 布局 leftMenu if (leftMenuView != null) { leftMenuView.layout(-leftMenuView.getMeasuredWidth(), t, 0, b); } // 布局 contentView if (contentView != null) { contentView.layout(0, t, contentView.getMeasuredWidth(), b); } // 布局 rightMenu if (rightMenuView != null) { rightMenuView.layout(contentView.getMeasuredWidth(), t, contentView.getMeasuredWidth() + rightMenuView.getMeasuredWidth(), b); } } // 2. 重写 computeScroll() @Override public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { // 动画没有结束 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //通知View重绘-invalidate()->onDraw()->computeScroll() postInvalidate(); } } private PointF lastPoint; // 记录第一次触摸点的坐标,方便计算手指抬起时,总的滑动距离 private PointF firstPoint; // 手指按下到抬起,总的滑动距离 float finalDistance; @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if (isTouching) { return false; } isTouching = true; isSwipeing = false; if (firstPoint == null) { firstPoint = new PointF(); } if (lastPoint == null) { lastPoint = new PointF(); } // 当前触摸的不是已经打开的那个 SwipeMenuLayout,则需要将打开的那个关闭掉。 if (swipeMenuLayout != null) { if (swipeMenuLayout != this) { // 调用已经打开的那个 SwipeMenuLayout 关闭方法 swipeMenuLayout.handleSwipeMenu(State.CLOSE);// getParent().requestDisallowInterceptTouchEvent(true); } } firstPoint.set(ev.getX(), ev.getY()); lastPoint.set(ev.getX(), ev.getY()); break; case MotionEvent.ACTION_MOVE: // 偏移量 = 当前坐标值 - 上次坐标值 int dx = (int) (ev.getX() - lastPoint.x); int dy = (int) (ev.getY() - lastPoint.y); // scrollBy 移动,正值内容向左移动,负值内容向右移动 if (Math.abs(dx) > Math.abs(dy)) { scrollBy(-dx, 0); } // 边界限定 if (getScrollX() > 0) { // 向左滑动,滑出 rightMenuView if (rightMenuView != null) { // 存在 rightMenuView,滑动距离不能超过 rightMenuView 宽度 if (getScrollX() > rightMenuView.getMeasuredWidth()) { scrollTo(rightMenuView.getMeasuredWidth(), 0); } } else { // 不存在 rightMenuView,禁止向左滑动 scrollTo(0, 0); } } else if (getScrollX() < 0) { // 向右滑动,滑出 leftMenuView if (leftMenuView != null) { // 存在 leftMenuView,滑动距离不能大于 leftMenuView 宽度 if (getScrollX() < -leftMenuView.getMeasuredWidth()) { // getScrollX()是负值, scrollTo(-leftMenuView.getMeasuredWidth(), 0); } } else { scrollTo(0, 0); } } // 当水平滑动时,请求父控件不要拦截事件 if (Math.abs(dx) > mScaledTouchSlop) { getParent().requestDisallowInterceptTouchEvent(true); } lastPoint.set(ev.getX(), ev.getY()); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: isTouching = false; finalDistance = ev.getX() - firstPoint.x; if (Math.abs(finalDistance) > mScaledTouchSlop) { isSwipeing = true; } State state = isShouldOpenMenu(getScrollX()); handleSwipeMenu(state); break; case MotionEvent.ACTION_POINTER_UP: break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: //滑动时拦截点击时间 if (Math.abs(finalDistance) > mScaledTouchSlop) { // 当手指拖动值大于mScaledTouchSlop值时,认为应该进行滚动,拦截子控件的事件 return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //滑动后不触发contentView的点击事件 if (isSwipeing) { isSwipeing = false; finalDistance = 0; return true; }//// if (getX() < getScreenWidth() - rightMenuView.getMeasuredWidth()) {// return true;// } break; } return super.onInterceptTouchEvent(ev); } private void handleSwipeMenu(State state) { if (state == State.RIGHT_MENU_OPEN) { swipeMenuLayout = this; mScroller.startScroll(getScrollX(), 0, rightMenuView.getMeasuredWidth() - getScrollX(), 0); curState = state; } else if (state == State.LEFT_MENU_OPEN) { swipeMenuLayout = this; // getScrollX() 为负值 mScroller.startScroll(getScrollX(), 0, -getScrollX() - leftMenuView.getMeasuredWidth(), 0); curState = state; } else if (state == State.CLOSE) { mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0); swipeMenuLayout = null; curState = null; } //通知View重绘-invalidate()->onDraw()->computeScroll() invalidate(); } private State isShouldOpenMenu(int scrollX) { // (1) scrollX > 0 : 表明现在处于 rightMenuView 打开状态,根据临界值决定是关闭,还是继续打开 if (scrollX > 0) { if (finalDistance < 0) { // 左滑 if (rightMenuView != null && scrollX > mScaledTouchSlop) { return State.RIGHT_MENU_OPEN; } } else if (finalDistance > 0) { // 右滑 if (rightMenuView != null && scrollX < rightMenuView.getMeasuredWidth() - mScaledTouchSlop) { return State.CLOSE; } } } else if (scrollX < 0) { // (2)scrollX < 0:表明现在处于 leftMenuView 打开状态,根据临界值是否打开,还是关闭 if (finalDistance < 0) { // 左滑 if (leftMenuView != null && Math.abs(scrollX) > mScaledTouchSlop) { return State.CLOSE; } } else if (finalDistance > 0) { // 右滑 if (leftMenuView != null && Math.abs(scrollX) > mScaledTouchSlop) { return State.LEFT_MENU_OPEN; } } } return State.CLOSE; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (this == swipeMenuLayout) { swipeMenuLayout.handleSwipeMenu(curState); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (this == swipeMenuLayout) { swipeMenuLayout.handleSwipeMenu(State.CLOSE); } } public int getScreenWidth() { return getResources().getDisplayMetrics().widthPixels; } enum State { LEFT_MENU_OPEN, RIGHT_MENU_OPEN, CLOSE }}
ListView item 布局 item_list_view.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="70dp" android:orientation="vertical"> <com.xing.swipemenulayoutlibrary.SwipeMenuLayout android:id="@+id/swipeMenuLayout" android:layout_width="match_parent" android:layout_height="70dp" app:contentId="@+id/content_view" app:leftMenuId="@+id/left_menu" app:rightMenuId="@+id/right_menu"> <LinearLayout android:id="@+id/left_menu" android:layout_width="80dp" android:layout_height="match_parent" android:orientation="horizontal"> <TextView android:layout_width="80dp" android:layout_height="match_parent" android:background="@android:color/holo_blue_dark" android:gravity="center" android:text="LeftMenu" android:textColor="@android:color/white" /> </LinearLayout> <TextView android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" android:paddingLeft="16dp" android:text="Android 8.0 奥利奥来了" android:textColor="@android:color/black" /> <LinearLayout android:id="@+id/right_menu" android:layout_width="240dp" android:layout_height="match_parent" android:orientation="horizontal"> <TextView android:layout_width="80dp" android:layout_height="match_parent" android:background="#D9DEE4" android:gravity="center" android:text="Top" android:textColor="@android:color/white" /> <TextView android:id="@+id/tv_add" android:layout_width="80dp" android:layout_height="match_parent" android:background="#ECD50A" android:gravity="center" android:text="Add" android:textColor="@android:color/white" android:textSize="16sp" /> <TextView android:id="@+id/tv_delete" android:layout_width="80dp" android:layout_height="match_parent" android:background="#FF4A57" android:gravity="center" android:text="Delete" android:textColor="@android:color/white" android:textSize="16sp" /> </LinearLayout> </com.xing.swipemenulayoutlibrary.SwipeMenuLayout></LinearLayout>
- 侧滑删除菜单 SwipeMenuLayout
- ListView 侧滑菜单的实现 -- 大道至简的SwipeMenuLayout
- Android 侧滑删除 菜单.
- Android 侧滑删除菜单
- 一步集成侧滑(删除)菜单
- 自定义ViewGroup实现侧滑删除菜单
- RecycleView拖拽排序+侧滑删除+侧滑菜单
- RecyclerView侧滑菜单,RecyclerView滑动删除,RecyclerView长按拖拽
- 一步集成侧滑(删除)菜单,高仿QQ、IOS
- RecyclerView 侧滑删除菜单 最简版 没有之一
- Android侧滑删除菜单,高仿QQ、IOS侧滑删除
- 一步集成侧滑(删除)菜单,高仿QQ、IOS条目侧滑删除
- 史上最简单侧滑菜单,0耦合,支持任意ViewGroup。一步集成侧滑(删除)菜单,高仿QQ、IOS。~
- 动态添加菜单\删除菜单\插入菜单
- 自定义ListView,实现Item侧滑删除及侧滑出菜单效果
- 自定义ListView,实现Item侧滑删除及侧滑出菜单效果
- RecyclerView实现支持拖拽、删除、侧滑菜单的列表
- RecyclerView侧滑菜单,滑动删除,长按拖拽,下拉刷新上拉加载
- 使用YASM编程
- python读取配置文件configparser
- web之路
- 2018网易校招题
- 迷宫问题,最短路径
- 侧滑删除菜单 SwipeMenuLayout
- MFC中Radio Button使用方法
- C语言中全局变量、局部变量、静态全局变量、静态局部变量的区别
- Mean time to recovery/HBase
- linux网络编程资料整理
- dawm_uprising
- 第二周项目三.2
- 机器学习中正则化项L1和L2的直观理解
- yaffs2移植到linux-4.3.2