Android ViewDragHelper使用介绍

Android ViewDragHelper使用介绍,第1张

概述ViewDragHelper是support.v4下提供的用于处理拖拽滑动的辅助类,查看Android的DrawerLayout源码,可以发现,它内部就是使用了该辅助类来处理滑动事件的.

VIEwDragHelper是support.v4下提供的用于处理拖拽滑动的辅助类,查看AndroID的DrawerLayout源码,可以发现,它内部就是使用了该辅助类来处理滑动事件的.

public DrawerLayout(Context context,AttributeSet attrs,int defStyle) {   super(context,attrs,defStyle);   setDescendantFocusability(VIEwGroup.FOCUS_AFTER_DESCENDANTS);   final float density = getResources().getdisplayMetrics().density;   mMinDrawermargin = (int) (MIN_DRAWER_margin * density + 0.5f);   final float minVel = MIN_FliNG_VELociTY * density;   mleftCallback = new VIEwDragCallback(Gravity.left);   mRightCallback = new VIEwDragCallback(Gravity.RIGHT);   mleftDragger = VIEwDragHelper.create(this,touch_SLOP_SENSITIVITY,mleftCallback);   mleftDragger.setEdgeTrackingEnabled(VIEwDragHelper.EDGE_left);   mleftDragger.setMinVeLocity(minVel);   mleftCallback.setDragger(mleftDragger);    mRightDragger = VIEwDragHelper.create(this,mRightCallback);   mRightDragger.setEdgeTrackingEnabled(VIEwDragHelper.EDGE_RIGHT);   mRightDragger.setMinVeLocity(minVel);   mRightCallback.setDragger(mRightDragger);    //省略  } 

有了VIEwDragHelper,我们在写与拖拽滑动相关的自定义控件的时候就变得非常简单了,例如我们可以用来实现自定义侧滑菜单,再也不需要在ontouchEvent方法里计算滑动距离来改变布局边框的位置了.

使用VIEwDragHelper类的大体步骤分为3步:

步骤1.在自定义的VIEwGroup子类下通过VIEwDragHelper的静态方法获取到VIEwDragHelper的实例引用,注意它是一个单例的.

查看源码:

/**  * Factory method to create a new VIEwDragHelper.  *  * @param forParent Parent vIEw to monitor  * @param cb Callback to provIDe information and receive events  * @return a new VIEwDragHelper instance  */  public static VIEwDragHelper create(VIEwGroup forParent,Callback cb) {   return new VIEwDragHelper(forParent.getContext(),forParent,cb);  } 

可以发现它需要接收2个参数,参数1就是当前要使用VIEwDragHelper的自定义控件的引用,Callback是一个回调抽象类,该回调接口是用于建立当前自定义控件与VIEwDragHelper沟通的桥梁,Callback内定义了多个回调函数,这些回调函数涵盖了与当前自定义控件相关的是否允许拖拽,当前拖拽的VIEw是哪一个vIEw,拖拽的vIEw的位置如何变化,释放的时候那个vIEw被释放了,释放时的速度是怎么样的等等.稍后会详细介绍.

步骤2.有了VIEwDragHelper的引用后,我们就需要传递相关的触摸事件给VIEwDragHelper来帮我们处理,那么怎么传递呢?

可以通过重写onIntercepttouchEvent和ontouchEvent这2个函数来传递,前者是用于决定是否要拦截中断事件的,后者是用于消费触摸事件的,如果前者return true则表示事件需要被拦截,那么事件就会直接回调给ontouchEvent去处理,如果ontouchEvent返回true,则事件被消费,返回false则向上返回它的父类调用处,如果事件在向上层层返回的过程中没有被处理的话,那么事件最终将会消失;当然,如果onIntercepttouchEvent返回false的话,那么事件就会继续向下传递个它的直接子VIEw去分发处理,关于事件分发的更多理论知识,大家可以看这篇文章事件分发机制的原理总结.

实例代码:

@OverrIDe public boolean onIntercepttouchEvent(MotionEvent ev) {  //由VIEwDragHelper类来决定是否要拦截事件  return dragHelper.shouldIntercepttouchEvent(ev); } @OverrIDe public boolean ontouchEvent(MotionEvent event) {  try {   //由VIEwDragHelper类来决定是否要处理触摸事件,这里可能有异常   dragHelper.processtouchEvent(event);  } catch (Exception e) {   e.printstacktrace();  }  //返回true,可以持续接收到后续事件  return true; } 

步骤3:重写VIEwDragHelper.Callback()的相关回调方法,处理事件,大体有如下方法:

下面将通过demo来分别介绍几个常用的方法,先来看看demo的整体代码,这是一个自定义的VIEwGroup的子类,里面有2个子控件,分别是侧边栏和主体布局

/**  * Created by mChenys on 2015/12/16.  */ public class DragLayout extends FrameLayout {  private String TAG = "DragLayout";  private VIEwDragHelper dragHelper;  private linearLayout mleftContent; //左侧面板  private linearLayout mMainContent;//主体面板   public DragLayout(Context context) {   this(context,null);  }   public DragLayout(Context context,AttributeSet attrs) {   this(context,0);  }   public DragLayout(Context context,int defStyleAttr) {   super(context,defStyleAttr);   init();  }  //重写此方法,可以获取该容器下的所有的直接子VIEw  @OverrIDe  protected voID onFinishInflate() {   super.onFinishInflate();   mleftContent = (linearLayout) getChildAt(0);   mMainContent = (linearLayout) getChildAt(1);  }    private voID init() {   //step1 通过VIEwDragHelper的单例方法获取VIEwDragHelper的实例   dragHelper = VIEwDragHelper.create(this,mCallback);  }  //step2 传递触摸事件,需要重写onIntercepttouchEvent和ontouchEvent  @OverrIDe  public boolean onIntercepttouchEvent(MotionEvent ev) {   //由VIEwDragHelper类来决定是否要拦截事件   return dragHelper.shouldIntercepttouchEvent(ev);  }  @OverrIDe  public boolean ontouchEvent(MotionEvent event) {   try {    //由VIEwDragHelper类来决定是否要处理触摸事件    dragHelper.processtouchEvent(event);   } catch (Exception e) {    e.printstacktrace();   }   //返回true,可以持续接收到后续事件   return true;  }  //step3 重写VIEwDragHelper.Callback()的相关回调方法,处理事件  private VIEwDragHelper.Callback mCallback = new VIEwDragHelper.Callback() {   /**    * 1.改方法是abstract的方法,必须要实现,其返回结果决定当前child是否可以拖拽    * @param child 当前被拖拽的vIEw    * @param pointerID pointerID 区分多点触摸的ID    * @return true表示允许拖拽,false则不允许拖拽,默认返回false    */   @OverrIDe   public boolean tryCaptureVIEw(VIEw child,int pointerID) {    Log.d(TAG,"tryCaptureVIEw:当前被拖拽的vIEw:" + child);    return false;   }  }; } 

布局文件:

<?xml version="1.0" enCoding="utf-8"?> <mchenys.net.csdn.blog.mytencentqq.vIEw.DragLayout xmlns:androID="http://schemas.androID.com/apk/res/androID"  androID:ID="@+ID/dragLayout"  androID:layout_wIDth="match_parent"  androID:layout_height="match_parent">  <!--左侧-->  <linearLayout   androID:ID="@+ID/layout_left"   androID:layout_wIDth="match_parent"   androID:layout_height="match_parent"   androID:background="@androID:color/holo_red_light"   androID:orIEntation="vertical">   <TextVIEw    androID:layout_wIDth="match_parent"    androID:layout_height="match_parent"    androID:gravity="center"    androID:text="左侧"    androID:textSize="30sp" />  </linearLayout>  <!--主体布局-->  <linearLayout   androID:ID="@+ID/layout_main"   androID:layout_wIDth="match_parent"   androID:layout_height="match_parent"   androID:background="@androID:color/holo_blue_light"   androID:orIEntation="vertical">   <TextVIEw    androID:layout_wIDth="match_parent"    androID:layout_height="match_parent"    androID:gravity="center"    androID:text="主体"    androID:textSize="30sp" />  </linearLayout> </mchenys.net.csdn.blog.mytencentqq.vIEw.DragLayout> 

运行效果:

打印的log

D/DragLayout: tryCaptureVIEw:当前被拖拽的vIEw:androID.Widget.linearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:ID/layout_main} D/DragLayout: tryCaptureVIEw:当前被拖拽的vIEw:androID.Widget.linearLayout{32f4d44b V.E..... ........ 0,1134 #7f0c0052 app:ID/layout_main} 

由上面的log可以知道tryCaptureVIEw方法被执行了,但是mMainContent却没有被拖动出来,只是为什么呢,因为tryCaptureVIEw返回了false.默认是返回false的,下面修改为mMainContent可以拖动,mleftContent不可以拖动:

@OverrIDe public boolean tryCaptureVIEw(VIEw child,int pointerID) {  Log.d(TAG,"tryCaptureVIEw:当前被拖拽的vIEw:" + child);  if (child == mMainContent) {   return true; //只有主题布局可以被拖动  }  return false; } 

运行效果还是移动不了,这是为什么呢?

这是以因为我们还没有重写clampVIEwpositionHorizontal方法,下面将介绍该方法的使用

/**  * 根据建议值修正将要移动到的横向位置,此时没有发生真正的移动  * @param child 当前被拖拽的VIEw  * @param left 新的建议值  * @param dx 水平位置的变化量  * @return  */ @OverrIDe public int clampVIEwpositionHorizontal(VIEw child,int left,int dx) {  Log.d(TAG,"clampVIEwpositionHorizontal:" + "旧的left坐标oldleft:" + child.getleft()    + " 水平位置的变化量dx:" + dx + " 新的建议值left:" + left);    return super.clampVIEwpositionHorizontal(child,left,dx); //父类默认返回0 } 

该方法返回的是水平方向的移动建议值,该建议值等于当前的X坐标+水平方向的变化量,向右移动,偏移量为正值,向左移动则为负数.默认返回的是调用父类的重写的方法,查看源码可以发现默认返回的是0,如果建议值等于0的话,就表示水平方向不会移动.如果想要移动,我们需要返回它提供的建议值left,我们来看看运行的log:

tryCaptureVIEw:当前被拖拽的vIEw:androID.Widget.linearLayout{23a3c537 V.E..... ........ 0,1134 #7f0c0052 app:ID/layout_main} clampVIEwpositionHorizontal:旧的left坐标oldleft:0 水平位置的变化量dx:1 新的建议值left:1 clampVIEwpositionHorizontal:旧的left坐标oldleft:0 水平位置的变化量dx:12 新的建议值left:12 clampVIEwpositionHorizontal:旧的left坐标oldleft:0 水平位置的变化量dx:63 新的建议值left:63 clampVIEwpositionHorizontal:旧的left坐标oldleft:0 水平位置的变化量dx:53 新的建议值left:53 tryCaptureVIEw:当前被拖拽的vIEw:androID.Widget.linearLayout{23a3c537 V.E..... ........ 0,1134 #7f0c0052 app:ID/layout_main} clampVIEwpositionHorizontal:旧的left坐标oldleft:0 水平位置的变化量dx:-5 新的建议值left:-5 clampVIEwpositionHorizontal:旧的left坐标oldleft:0 水平位置的变化量dx:-2 新的建议值left:-2 clampVIEwpositionHorizontal:旧的left坐标oldleft:0 水平位置的变化量dx:-6 新的建议值left:-6 clampVIEwpositionHorizontal:旧的left坐标oldleft:0 水平位置的变化量dx:-11 新的建议值left:-11 

由上面的log可以看出,分别是向右拖拽和向左拖拽的结果,如果我们返回了它的建议值,就可以实现水平方向的拖动了.

将clampVIEwpositionHorizontal的返回值修改成return left;看看运行效果:

跟我们预想的结果一样,只有主体布局可以滑动,左侧的布局不能滑动,如果想要左侧布局也可以滑动,那么只需要在tryCaptureVIEw直接返回true即可.效果如下:

同样的,如果要实现垂直方向的拖拽滚动,就需要重新下面这个方法了.

/**  * 根据建议值修正将要移动到的纵向位置,此时没有发生真正的移动  * @param child 当前被拖拽的VIEw  * @param top 新的建议值  * @param dy 垂直位置的变化量  * @return  */ @OverrIDe public int clampVIEwpositionVertical(VIEw child,int top,int dy) {  Log.d(TAG,"clampVIEwpositionHorizontal:" + "旧的top坐标oldtop:" + child.gettop()    + " 垂直位置的变化量dy:" + dy + " 新的建议值top:" + top);  return top; } 

 效果如下,可以随意的滑动,实现起来是不是很简单啊

继续介绍剩下的回调方法

/**  * 当capturedChild被捕获时调用  * @param capturedChild 当前被拖拽的vIEw  * @param activePointerID  */ @OverrIDe public voID onVIEwCaptured(VIEw capturedChild,int activePointerID) {  Log.d(TAG,"onVIEwCaptured:当前被拖拽的vIEw:" + capturedChild);  super.onVIEwCaptured(capturedChild,activePointerID); } 

该回调方法和tryCaptureVIEw一样都可以获取到当前被拖拽的VIEw,不同点在于tryCaptureVIEw是可以决定哪个VIEw是可以被拖拽滑动的,而onVIEwCaptured只是用来获取当前到底哪个VIEw被正在拖拽而已.

/**  * 返回拖拽的范围,不对拖拽进行真正的限制,仅仅决定了动画执行的速度  * @param child  * @return 返回一个固定值  */ @OverrIDe public int getVIEwHorizontalDragRange(VIEw child) {  int range = super.getVIEwHorizontalDragRange(child);  Log.d(TAG,"被拖拽的范围getVIEwHorizontalDragRange:" + range);  return range; } 

该回调方法是用于决定水平方向的动画执行速度,相对的垂直方向肯定也会有相应的方法,没错,就是下面这个:

@OverrIDe public int getVIEwVerticalDragRange(VIEw child) {  return super.getVIEwVerticalDragRange(child); }

那么话又说回来,我们有什么办法可以限制子VIEw的滑动范围呢,如果范围不能很好的控制的话,那滑动也没有什么意义了.还记得上面介绍的clampVIEwpositionHorizontal和clampVIEwpositionVertical吗,分别用于设置水平方向和垂直方向的移动建议值,假设我们要限制该自定义控件的子VIEw在水平方向移动的范围为0-屏幕宽度的0.6,那么如何控制呢.要实现这个限制,我们现在获取屏幕的宽度,由于当前的自定义控件是全屏显示的,所以直接就可以那控件的宽度来作为屏幕的宽度,那么如何获取呢?有2种方式,分别是在onMeasure和onSizeChange方法里面调用getMeasureDWIDth()方法获取.前者是测量完之后获取,后者是当控件的宽高发生变化后获取,例如:

@OverrIDe protected voID onSizeChanged(int w,int h,int olDW,int oldh) {  super.onSizeChanged(w,h,olDW,oldh);  // 当尺寸有变化的时候调用  mHeight = getMeasuredHeight();  mWIDth = getMeasureDWIDth();  // 移动的范围  mRange = (int) (mWIDth * 0.6f); } 

接下来,在clampVIEwpositionHorizontal方法内部做判断,如果当前的建议值left超过了mRange,那么返回mRange,如果小于了0,则返回0,这样一来子VIEw的滑动范围就限定在0-mRange之间了,修改代码如下:

@OverrIDe public int clampVIEwpositionHorizontal(VIEw child,"clampVIEwpositionHorizontal 建议值left:" + left + " mRange:" + mRange);  if (left > mRange) {   left = mRange;  } else if (left < 0) {   left = 0;  }  return left; } 

如果垂直方向也想限定的话,那就修改clampVIEwpositionVertical返回的建议值

@OverrIDe public int clampVIEwpositionVertical(VIEw child,"clampVIEwpositionVertical 建议值top:" + top + " mRange:" + mRange);  if (top > mRange) {   top = mRange;  } else if (top < 0) {   top = 0;  }  return top; } 

来看看运行的效果:

如此一来,我们就可以随意的控制子VIEw的拖拽滑动的范围了.那么新的问题又来的,如果现在的需求是只能mMainContent被拖拽,mleftContent不能被拖拽,也许你会说,这还不简单吗,直接在tryCaptureVIEw判断当前拖拽的子VIEw是mleftContent的话就返回false不就得了,如果需求只是这样的话确实可以满足了,但是如果在加上一个条件,那就是拖拽mleftContent的时候的效果相当于把mMainContent拖拽了,什么意思呢,也就说现在mMainContent已经是打开的状态了,我想通过滑动mleftContent就能把mMainContent滑动了.而mleftContent还是原来的位置不动.这个要怎么实现呢?

首先可以肯定的是,tryCaptureVIEw方法必须返回true,表示mMainContent和mleftContent都可以被滑动,接下来要处理的就是如何在mleftContent滑动的时候是滑动mMainContent的.那么现在就要介绍另一个回调方法了,如下所示:

/**  * 当VIEw的位置发生变化的时候,处理要做的事情(更新状态,伴随动画,重绘界面)  * 此时,VIEw已经发生了位置的改变  *  * @param changedVIEw 改变位置的VIEw  * @param left  新的左边值  * @param top   新的上边值  * @param dx   水平方向的变化量  * @param dy   垂直方向的变化量  */ @OverrIDe public voID onVIEwpositionChanged(VIEw changedVIEw,int dx,"位置发生变化onVIEwpositionChanged:" + "新的左边值left: " + left + " 水平方向的变化量dx:" + dx    + " 新的上边值top:" + top + " 垂直方向的变化量dy:" + dy);  super.onVIEwpositionChanged(changedVIEw,top,dx,dy);  // 为了兼容低版本,每次修改值之后,进行重绘  invalIDate(); } 

该方法是随着VIEw的位置发生变化而不断回调的.四个参数如上面的注释所述,通过该方法可以拿到当前正在拖拽滑动的VIEw是哪个VIEw,有了这依据之后,我们就将在mleftContent上的滑动的水平方向和垂直方向的变化量传递给mMainContent,这样一来,滑动mleftContent的效果不就等于滑动mMainContent了吗.需要注意的是,该回调方法在低版本上为了兼容还需要加上invalIDate();这句代码,invalIDate是用来刷新界面的,他会导致界面的重绘.

滑动mMainContent来看看log

D/DragLayout: 位置发生变化onVIEwpositionChanged:新的左边值left: 15 水平方向的变化量dx:15 新的上边值top:8 垂直方向的变化量dy:8 
D/DragLayout: 位置发生变化onVIEwpositionChanged:新的左边值left: 32 水平方向的变化量dx:17 新的上边值top:16 垂直方向的变化量dy:8 
D/DragLayout: 位置发生变化onVIEwpositionChanged:新的左边值left: 121 水平方向的变化量dx:89 新的上边值top:46 垂直方向的变化量dy:30 
D/DragLayout: 位置发生变化onVIEwpositionChanged:新的左边值left: 145 水平方向的变化量dx:24 新的上边值t 
由log可以看出,最新的left和top值是等于上一次的位置+水平/垂直方向的变化量.这个特点有点类似建议值了.不同的是建议值发生了改变不代表VIEw就一定已经处于滑动,除非返回的值也是建议值,但是onVIEwpositionChanged方法就不同了,这个方法只要一执行,就说明目标VIEw是处于滑动状态的.

以水平方向滑动为例,垂直方向不移动,接下来就可以在onVIEwpositionChanged方法内做判断了,如下所示:

@OverrIDe public voID onVIEwpositionChanged(VIEw changedVIEw,int dy) {  super.onVIEwpositionChanged(changedVIEw,dy);  //获取最新的left坐标  int newleft = left;  if (changedVIEw == mleftContent) {   //如果滑动的是mleftContent,则将其水平变化量dx传递给mMainContent,记录在newleft中   newleft = mMainContent.getleft() + dx;  }  //矫正范围  if (newleft > mRange) {   newleft = mRange;  } else if (newleft < 0) {   newleft = 0;  }  //再次判断,限制mleftContent的滑动  if (changedVIEw == mleftContent) {   //强制将mleftContent的位置摆会原来的位置,这里通过layout方法传入左,上,右,下坐标来实现   //当然方法不限于这一种,例如还可以通过scrollTo(x,y)方法   mleftContent.layout(0,mWIDth,mHeight);   //改变mMainContent的位置   mMainContent.layout(newleft,newleft + mWIDth,mHeight);  }  // 为了兼容低版本,进行重绘  invalIDate(); } 

效果图:

由上面的效果图可以发现已经可以实现当手指向右滑动mleftContent时,滑动的效果等于向右滑动mMainContent,当同时也会发现一个问题,那就是手指在mleftContent向左滑动的时候并没有效果,这是因为我们限制了子VIEw的滑动范围就是0-mRange,所以,如果滑动时小于0是没有效果的.那如果我们想要实现在mleftContent当手指有向左滑动的趋势,或者手指在mMainContent有向左滑动的趋势时,就关闭mleftContent,让mMainContent自动向左滑动到x=0的位置,反之就是打开mleftContent,让mMainContent滑动到x=mRange的位置,这个要怎么实现呢?首先我们要能够想到的时,这个向左滑动的趋势肯定是与手指松手后相关的,那有没有一个回调方法是与手指触摸松开相关的呢?下面将介绍另一个回调方法,如下所示:

/**  * 当被拖拽的VIEw释放的时候回调,通常用于执行收尾的 *** 作(例如执行动画)  * @param releasedChild 被释放的VIEw  * @param xvel 水平方向的速度,向右释放为正值,向左为负值  * @param yvel 垂直方向的速度,向下释放为正值,向上为负值  */ @OverrIDe public voID onVIEwReleased(VIEw releasedChild,float xvel,float yvel) {  Log.d(TAG,"VIEw被释放onVIEwReleased:" + "释放时水平速度xvel:" + xvel + " 释放时垂直速度yvel:" + yvel);  super.onVIEwReleased(releasedChild,xvel,yvel); } 

有了这个方法,我们就可以实现我们刚刚说到的效果了,首先我们来考虑下那些情况是和打开mleftContent相关的,有2种情况:

1.当前水平方向的速度xvel=0,同时mMainContent的x位置是大于mRange/2.0f的.

2.当前水平方向的速度xvel>0

考虑了所有打开的情况,那么剩下的情况就是关闭mleftContent.

具体逻辑如下:

@OverrIDe public voID onVIEwReleased(VIEw releasedChild,yvel);  if (xvel > 0 || (xvel == 0 && mMainContent.getleft() > mRange / 2.0f)) {   //打开mleftContent,即mMainContent的x=mRange   mMainContent.layout(mRange,mRange + mWIDth,mHeight);  } else {   //关闭mleftContent,即mMainContent的x=0   mMainContent.layout(0,mHeight);  } } 

效果图:

细心的话,可以发现上面的打开和关闭动画都是瞬间完成的,看起来效果不怎么好,如何实现平滑的打开和关闭呢?

通过VIEwDragHelper的smoothSlIDeVIEwTo(VIEw child,int finalleft,int finaltop)方法可以实现平滑的滚动目标VIEw到指定的left或者top坐标位置,接收3个参数,参数child表示要滑动的目标VIEw,finalleft和finaltop表示目标vIEw最终平滑滑动到的位置.翻看源码,发现其实现原理是通过Scroller对象来实现的,也就说我们还需要重写computeScroll方法,不断的刷新当前界面,具体设置如下:

@OverrIDe public voID onVIEwReleased(VIEw releasedChild,即mMainContent的x=mRange   if (dragHelper.smoothSlIDeVIEwTo(mMainContent,mRange,0)) {    // 返回true代表还没有移动到指定位置,需要刷新界面.    // 参数传this(child所在的VIEwGroup)    VIEwCompat.postInvalIDateOnAnimation(DragLayout.this);   }  } else {   //关闭mleftContent,即mMainContent的x=0   if (dragHelper.smoothSlIDeVIEwTo(mMainContent,0)) {    VIEwCompat.postInvalIDateOnAnimation(DragLayout.this);   }  } @OverrIDe public voID computeScroll() {  super.computeScroll();  // 2. 持续平滑动画 (高频率调用)  if(dragHelper.continueSettling(true)){   // 如果返回true,动画还需要继续执行   VIEwCompat.postInvalIDateOnAnimation(this);  } } 

效果如下:

总结

以上所述是小编给大家介绍的AndroID VIEwDragHelper使用介绍,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持!

总结

以上是内存溢出为你收集整理的Android ViewDragHelper使用介绍全部内容,希望文章能够帮你解决Android ViewDragHelper使用介绍所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1144963.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-31
下一篇2022-05-31

发表评论

登录后才能评论

评论列表(0条)

    保存