发布时间:2025-12-11 00:40:29 浏览次数:8
第一步效果图
1.0自定义控件 SwipeLayout 继承FrameLayout重写里面三个构造方法,分别调用initView().
2.0在布局中使用自定义控件
3.0在initView()方法中,创建拖拽辅辅助工具 ViewDragHelper()
该方法需要传入回调 MyCallBack()
4.0,创建MyCallBack()回调,继承ViewDragHelper.Callback
在回调中 覆盖tryCaptureView方法,返回true 允许child被拖拽,被 覆盖clampViewPositionHorizontal 返回left系统提供拖拽位置
5.0 onInterceptTouchEvent 返回:让ViewDragHelper判断是否需要拦截事件
6.0 onTouchEvent 返回true 并且让ViewDragHelper分析事件
具体代码:
布局:
<cn.itheima.swipelayout.SwipeLayoutxmlns: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="wrap_content"><!--正文部分--><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="#fff"android:orientation="horizontal"><TextViewandroid:id="@+id/item_tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="10dp"android:text="张三"android:textSize="20sp"/></RelativeLayout><!--按钮部分--><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#888888"android:padding="10dp"android:text="呼叫"android:textSize="20sp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#f00"android:padding="10dp"android:text="删除"android:textSize="20sp"/></LinearLayout></cn.itheima.swipelayout.SwipeLayout>
SwipeLayout 代码:
publicclassSwipeLayoutextendsFrameLayout{privateViewDragHelpermDragHelper;publicSwipeLayout(Contextcontext){super(context);initView();}publicSwipeLayout(Contextcontext,AttributeSetattrs){super(context,attrs);initView();}publicSwipeLayout(Contextcontext,AttributeSetattrs,intdefStyleAttr){super(context,attrs,defStyleAttr);initView();}privatevoidinitView(){mDragHelper=ViewDragHelper.create(this,newMyCallBack());}//让ViewDragHelper就是拖拽辅助工具返回true则表示要拦截触摸事件@OverridepublicbooleanonInterceptTouchEvent(MotionEventev){//让拖拽辅助工具判断是否需要拦截事件returnmDragHelper.shouldInterceptTouchEvent(ev);}@OverridepublicbooleanonTouchEvent(MotionEventevent){//让拖拽辅助工具分析事件分析用户手势mDragHelper.processTouchEvent(event);returntrue;}privateclassMyCallBackextendsViewDragHelper.Callback{/***如果返回true则表示child允许被拖拽*/@OverridepublicbooleantryCaptureView(Viewchild,intpointerId){returntrue;}/***固定被拖拽控件的水平位置,*参数里的left是系统推荐移动到的位置,可以进行修正,*方法返回的值就是child将要移动到的位置*/@OverridepublicintclampViewPositionHorizontal(Viewchild,intleft,intdx){returnleft;}}}第二步:
1.0创建onFinishInflate方法获取子控件,并且判断健壮性
/*控件初始化时执行,可以用于获取子控件*/@OverrideprotectedvoidonFinishInflate(){//健壮性检查if(getChildCount()!=2){thrownewRuntimeException("SwipeLayout必须存放两个子控件");}if(!(getChildAt(0)instanceofViewGroup)||!(getChildAt(1)instanceofViewGroup)){thrownewRuntimeException("SwipeLayout的子控件必须是ViewGroup");}mContent=(ViewGroup)getChildAt(0);mDeletePanel=(ViewGroup)getChildAt(1);}2.0创建onSizeChanged方法,在控件大小改变的时候调用,获取控件的宽高,和删除的面板的最大移动范围
/***当控件大小改变的时候调用这个方法*/@OverrideprotectedvoidonSizeChanged(intw,inth,intoldw,intoldh){super.onSizeChanged(w,h,oldw,oldh);intmWith=w;intmHeigth=h;//界面创建过程中,不能使用getWidth方法intmRang=mDeletePanel.getMeasuredWidth();}3.0在onLayout中指定侧拉面板的位置
//指定侧拉面板的位置@OverrideprotectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){super.onLayout(changed,left,top,right,bottom);mDeletePanel.layout(mWith,0,mWith+mRang,mHeigth);}4.0在onViewPositionChanged方法中实现联动效果
/***当被拖拽的控件已经移动过后,会调用这个方法,可以用于处理控件间的联动效果*@left被拖拽控件的真实移动位置*@dx被拖拽控件的真实偏移大小*/@OverridepublicvoidonViewPositionChanged(ViewchangedView,intleft,inttop,intdx,intdy){if(changedView==mContent){//移动正文的同时也要移动侧栏mDeletePanel.offsetLeftAndRight(dx);}else{mContent.offsetLeftAndRight(dx);}}5.0在 clampViewPositionHorizontal方法中 固定被拖拽控件的水平位置,
/***固定被拖拽控件的水平位置,*参数里的left是系统推荐移动到的位置,可以进行修正,*方法返回的值就是child将要移动到的位置*/@OverridepublicintclampViewPositionHorizontal(Viewchild,intleft,intdx){if(child==mContent){if(left>0){left=0;}elseif(left<-mRang){left=-mRang;}}else{if(left>mWith){//mWith是屏幕的宽度left=mWith;}elseif(left<mWith-mRang){left=mWith-mRang;}}returnleft;}第三步:
效果图
1.0onViewReleased中根据来开局里面,判断是否打开还是关闭
2.0 在 moveContent中第一次滑动
3.0computeScroll中,继续滑动,直到滑动到指定的位置
4.0注意在onViewPositionChanged中手动刷新界面,调用invalidate方法
如果不手动刷新界面,效果展示不出来
/***当用户松手时执行*@xvel松手时在X方向的移动速度,如果为正数则说明是向右移动,如果是负数则说明是向左移动,如果为零,说明是静止状态*/@OverridepublicvoidonViewReleased(ViewreleasedChild,floatxvel,floatyvel){if(xvel>0){//向右移动close();}elseif(xvel<0){//向左移动opend();}elseif(xvel>-mRang/2){//静止状态close();//展开不到一半,关闭面板}else{opend();}}}/***打开面板*/privatevoidopend(){intleft=-mRang;moveContent(left);}/***关闭面板*/privatevoidclose(){intleft=0;moveContent(left);}privatevoidmoveContent(intleft){//开启平滑滚动,如果返回true则说明要继续刷新界面,保持滚动if(mDragHelper.smoothSlideViewTo(mContent,left,0)){invalidate();}}@OverridepublicvoidcomputeScroll(){//继续平滑滚动,如果返回true则说明要继续刷新界面,保持滚动if(mDragHelper.continueSettling(true)){invalidate();}}第四步:
1.0现给ListView赋值 在这就省略
2.0在SwipeLayout中使用枚举记录面板的状态
privateenumStatus{CLOSED,OPENED,DRAGING;}privateStatusstatus=Status.CLOSED;publicStatusgetStatus(){returnstatus;}3.0// 记录上一个打开的面板。注意:一定要是 静态变量
privatestaticSwipeLayoutpreSwipeLayout;
4.0在onViewPositionChanged中创建一个方法操作关闭面板
//关闭上一个打开的面板closePre();
5.0closePre()在这个方法中,判断当前面板的状态,并且根据状态,关闭上一个打开的面板
//判断当前面板是否正在打开,如果正在打开则将上一个打开的面板关闭privatevoidclosePre(){//记录旧状态StatuspreStatus=status;if(mContent.getLeft()==-mRang){//记录当前面板已经打开status=status.OPENED;}elseif(mContent.getLeft()==0){//当前面板已经关闭status=status.CLOSED;}else{status=status.DRAGING;}//如果当前面板旧状态为关闭,并且新状态为拖拽,那么此时可以关闭之前打开的面板if(preStatus==status.CLOSED&&status==status.DRAGING){if(preSwipeLayout!=null&&preSwipeLayout!=this){//关闭上一个面板preSwipeLayout.close();}//将当前面板标记为打开的面板preSwipeLayout=this;}}