Android拖拽和嵌套滑动

View.onDragListener(View)

  • Api 11引入的工具类,用于实现View的拖拽操作,列表拖动

  • 适用于用户的托起,放下操作,重在内容的移动,拖拽时可以附加拖拽数据,数据分为本地数据LocalState(App内进行拖拽),跨进程数据ClipData(两个App之间进行拖拽);比如添加物品进购物车

  • 不需要进行自定义View,使用view.startDrag()/startDragAndDrop()来启动拖拽操作
    通过view.setDragListener()或者重写View的onDragEvent()来监听View的拖拽状态

  • 拖拽原理:创建一个图层(DragShadowBuilder)在屏幕的最上层,这个图层会随着用户手指的移动而移动

使用

开启拖拽

val clipData = ClipData.newPlainText("name", "drag data")
ViewCompat.startDragAndDrop(view, clipData, View.DragShadowBuilder(it), "LocalState", 0)

//或者view.startDragAndDrop()
val clipData = ClipData.newPlainText("name", "drag data")
view.startDragAndDrop(clipData, View.DragShadowBuilder(it), "LocalState", 0)

拖拽监听

view.setOnDragListener(HDragListener)

//重写OnDragListener实现拖拽监听
inner class HDragListener : OnDragListener {
     override fun onDrag(v: View, event: DragEvent): Boolean {
           when (event.action) {
               DragEvent.ACTION_DRAG_STARTED ->{
                   //开始拖拽时回调
               }
               DragEvent.ACTION_DRAG_ENTERED ->{
                   //拖拽到View的边界内回调(可进行排序操作)
               }
               DragEvent.ACTION_DRAG_ENDED ->{
                   //结束拖拽时回调
               }
          }

          return true
    }
}

//或者重写View的onDragEvent()实现拖拽监听
override fun onDragEvent(event: DragEvent): Boolean {
  //当前界面中所有View都会回调这个方法
}

ViewDragHelper(ViewGroup)

  • 2015年 SupportV4 包新增的工具类,主要用于ViewGroup中子View的拖拽操作

  • 需要开发者在自定义ViewGroup中使用,重写ViewGroup的onInterceptTouchEvent()和onTouchEvent()来接管触摸事件

  • 拖拽原理:实时修改被拖拽的子View的mLeft,mTop,mRight,mBottom值

使用:

自定义Callback实现ViewDragHelper.callback接口,监听拖拽回调

inner class HDragCallback : ViewDragHelper.Callback() {
 //记录下被托起的View的初始位置
 private var capturedLeft = 0
 private var capturedTop = 0

 /*** 返回true时View可以被拖起来*/
 override fun tryCaptureView(child: View, pointerId: Int): Boolean {
      return true
 }

 override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
      return left  //水平偏移
 }

 override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
      return top  //垂直偏移
 }

 /*** View被拖起时回调*/
 override fun onViewCaptured(capturedChild: View, activePointerId: Int) {
      capturedChild.elevation = elevation + 1
      capturedLeft = capturedChild.left
      capturedTop = capturedChild.top
 }

 /*** 拖拽状态改变时回调*/
 override fun onViewDragStateChanged(state: Int) {
      if (state == ViewDragHelper.STATE_IDLE) {
          val capturedView = mDragHelper.capturedView!!
          capturedView.elevation = capturedView.elevation - 1
      }
 }

 override fun onViewPositionChanged(changedView: View, left: Int, top: Int,dx: Int, dy: Int) {
      //位置改变时回调
 }

 /*** View被松开时回调*/
 override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
      mDragHelper.settleCapturedViewAt(capturedLeft, capturedTop)
          //更新下一帧的绘制 和computeScroll结合
          postInvalidateOnAnimation()
  }
}

重写ViewGroup的onInterceptTouchEvent()和onTouchEvent()来接管触摸事件

private val mDragHelper by lazy {
  ViewDragHelper.create(this, HDragCallback())
}

override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
  return mDragHelper.shouldInterceptTouchEvent(ev)
}

override fun onTouchEvent(event: MotionEvent): Boolean {
  mDragHelper.processTouchEvent(event)
  return true
}

//和DragCallback的onViewReleased()相结合,循环绘制每一帧
override fun computeScroll() {
  if (mDragHelper.continueSettling(true)) {
          ViewCompat.postInvalidateOnAnimation(this)
  }
}

嵌套滑动

不同向嵌套

  • onInterceptTouchEvent 父 View 拦截子View

  • requestDisallowInterceptTouchEvent() 子 View 阻止父 View 拦截

同向嵌套

  • 父 View 会彻底卡住子 View(滑动冲突)
    原因:抢夺条件一致,但父View的onInterceptTouchEvent() 早于子View的dispatchTouchEvent()

  • 本质上是策略问题:嵌套状态下用户手指滑动,他是想滑谁?

    1. 场景一:NestedScrollView 子View能滑动的时候滑动子View;滑不动的时候滑动父View

    2. 场景二:Google 的样例

      父View展开的时候:

      ​ 上滑:优先滑动父View

      ​ 下滑:滑不动

      父View半展开的时候:

      ​ 上滑:优先滑动父View,滑到父View完全折叠后开始滑动子View

      ​ 下滑:优先滑动父View,滑到父View完全展开后开始滑动子View

      父View折叠的时候:

      ​ 上滑:滑动子View

      ​ 下滑:优先滑动子View,滑到子View顶部后开始滑动父View

  • 滑动嵌套解决方案: 自定义滑动策略(父View,子View谁来消费滑动事件)

  • 实现:

    1. 大多数场景下SDk就能解决:ScrollView嵌套问题,换成NestedScrollView

    2. 自己实现:实现 NestedScrollingChild2 接口来实现自定义的嵌套滑动逻辑

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容