最终效果
源码实现
布局文件
- 整体布局
<?xml version="1.0" encoding="utf-8"?>
<com.crystal.view.VerticalDragListView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@color/red"
android:gravity="center"
android:text="TopView"
android:textColor="@color/white"
android:textSize="16sp" />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/blue" />
</com.crystal.view.VerticalDragListView>
- ListView的item布局
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:textColor="@color/white"
android:textSize="20sp">
</TextView>
Activity代码
private var lists = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_slide_menu)
for (i in 0..30) {
lists.add("item -->$i")
}
val listView = findViewById<ListView>(R.id.listView)
val arrayAdapter = ArrayAdapter(this, R.layout.layout_list_item, lists)
listView.adapter = arrayAdapter
}
自定义VerticalDragListView
package com.crystal.view
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.AbsListView
import android.widget.FrameLayout
import androidx.customview.widget.ViewDragHelper
/**
* 垂直方向的滑动
* on 2022/10/28
*/
class VerticalDragListView : FrameLayout {
/**
* 拖拽的listview
*/
private lateinit var dragListView: View
/**
* 上方view对应的高度
*/
private var topViewHeight: Int = 0
/**
* 当前最上层View是否为展开状态
*/
private var isTopViewOpen: Boolean = false
private var downY: Float = 0f
/**
* 创建ViewDragHelper对象用于实现拖拽效果
*/
private val viewDragHelper by lazy {
ViewDragHelper.create(this, DragHelperCallback())
}
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context, attrs, defStyleAttr
)
private inner class DragHelperCallback : ViewDragHelper.Callback() {
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
//用于判断是否为支持拖拽的对象,这里仅设置dragListView为可拖拽对象
return child == dragListView
}
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
//设置列表垂直方向拖动的距离,本文仅需要垂直方向滑动,如果需要水平方向拖动,则重写clampViewPositionHorizontal方法
var resultTop = top
if (resultTop < 0) {
resultTop = 0
}
if (resultTop > topViewHeight) {
resultTop = topViewHeight
}
return resultTop
}
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
//松开时,判断如果拖动距离>topViewHeight/2,则滑动到[0,topViewHeight]位置,否则滑动到【0,0】位置
isTopViewOpen = if (dragListView.top > topViewHeight / 2) {
viewDragHelper.settleCapturedViewAt(0, topViewHeight)
true
} else {
viewDragHelper.settleCapturedViewAt(0, 0)
false
}
invalidate()
}
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (isTopViewOpen) {
//如果当前topView已展开,则事件交给ViewGroup处理
return true
}
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
downY = ev.y
viewDragHelper.processTouchEvent(ev)
}
MotionEvent.ACTION_MOVE -> {
if (ev.y - downY > 0 && !canChildScrollUp()) {
return true
}
}
}
return super.onInterceptTouchEvent(ev)
}
override fun onFinishInflate() {
super.onFinishInflate()
if (childCount != 2) {
throw IllegalArgumentException("The VerticalDragListView Child size must be 2!")
}
dragListView = getChildAt(1)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (changed) {
//获取最上方View的测量高度
topViewHeight = getChildAt(0).measuredHeight
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
viewDragHelper.processTouchEvent(event)
return true
}
override fun computeScroll() {
super.computeScroll()
//判断拖拽是否完成,没有完成不断进行重绘
if (viewDragHelper.continueSettling(true)) {
invalidate()
}
}
/**
* 判断listview是否已滑动到顶部
*/
private fun canChildScrollUp(): Boolean {
return if (Build.VERSION.SDK_INT < 14) {
if (dragListView is AbsListView) {
val absListView = dragListView as AbsListView
(absListView.childCount > 0 && (absListView.firstVisiblePosition > 0 || absListView.getChildAt(
0
).top < absListView.paddingTop))
} else {
dragListView.canScrollVertically(-1) || dragListView.scrollY > 0
}
} else {
dragListView.canScrollVertically(-1)
}
}
}
总结
通过对ViewDragHelper工具类的使用,实现了拖拽效果,同时也通过事件分发处理了滑动冲突,对自定义View的学习很有帮助!