ViewDragHelper是一个鲜为人知的ViewGroup辅助类,它解决了android中手势处理过于复杂的问题。
其实ViewDragHelper并不是第一个用于分析手势处理的类,gesturedetector也是,但是在和拖动相关的手势分析方面gesturedetector只能说是勉为其难。
ViewDragHelper一般用在一个自定义ViewGroup的内部,我们先看下如何创建一个ViewDrawHelper:
ViewDragHelper mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
其中1.0f是敏感度参数参数越大越敏感。第一个参数为this,表示该类生成的对象,他是ViewDragHelper的拖动处理对象,必须为ViewGroup。
要让ViewDragHelper能够处理拖动需要将触摸事件传递给ViewDragHelper,这点和gesturedetector是一样的,也就是需要接管ViewGroup的onInterceptTouchEvent方法和onTouchEvent方法。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mDragHelper.processTouchEvent(ev);
return true;
}
我把ViewDragHelper所有回调方法含义标注一下,具体的使用,推荐一篇文章简单学习一下:
http://blog.csdn.net/lmj623565791/article/details/46858663
初始化
val mDragHelper = ViewDragHelper.create(this, 1.0f, object : ViewDragHelper.Callback() {
//是否捕捉视图
override fun tryCaptureView(child: View?, pointerId: Int): Boolean {
child ?: return false
return false
}
//水平滑动坐标
override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int {
return super.clampViewPositionHorizontal(child, left, dx)
}
//View拖行结束
override fun onViewReleased(releasedChild: View?, xvel: Float, yvel: Float) {
return super.onViewReleased(releasedChild, xvel, yvel)
}
//拖动坐标的变化
override fun onViewPositionChanged(changedView: View?, left: Int, top: Int, dx: Int, dy: Int) {
super.onViewPositionChanged(changedView, left, top, dx, dy)
}
//边缘拖动
override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {
}
//边界锁,false 为unlock
override fun onEdgeLock(edgeFlags: Int): Boolean {
return super.onEdgeLock(edgeFlags)
}
//触摸ViewGroup边缘
override fun onEdgeTouched(edgeFlags: Int, pointerId: Int) {
super.onEdgeTouched(edgeFlags, pointerId)
}
//调用captureChildView确定拖动目标时,回调此方法
override fun onViewCaptured(capturedChild: View?, activePointerId: Int) {
super.onViewCaptured(capturedChild, activePointerId)
}
//拖拉状态变化
override fun onViewDragStateChanged(state: Int) {
super.onViewDragStateChanged(state)
}
//垂直滑动坐标
override fun clampViewPositionVertical(child: View?, top: Int, dy: Int): Int {
return super.clampViewPositionVertical(child, top, dy)
}
//水平拖拉范围
override fun getViewHorizontalDragRange(child: View?): Int {
return super.getViewHorizontalDragRange(child)
}
})
拖动行为的处理
处理横向的拖动:
在DragHelperCallback中实现clampViewPositionHorizontal方法, 并且返回一个适当的数值就能实现横向拖动效果,clampViewPositionHorizontal的第二个参数是指当前拖动子view应该到达的x坐标。所以按照常理这个方法原封返回第二个参数就可以了,当然为了让被拖动的view遇到边界之后就不在拖动,对返回的值可以做控制,你懂。
clampViewPositionVertical与clampViewPositionHorizontal用法相同,不同的是一个处理横向一个处理竖向。
通过DragHelperCallback的tryCaptureView方法的返回值可以决定一个parentview中哪个子view可以拖动,现在假设有两个子views (mDragView1和mDragView2) ,如下实现tryCaptureView之后,则只有mDragView1是可以拖动的。
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mDragView1;
}
滑动边缘:
分为滑动左边缘还是右边缘:EDGE_LEFT和EDGE_RIGHT,下面的代码设置了可以处理滑动左边缘:
mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
设置之后,onEdgeTouched方法会在左边缘滑动的时候被调用,这种情况下一般都是没有和子view接触的情况。
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags, pointerId);
}
如果你想在边缘滑动的时候根据滑动距离移动一个子view,可以通过实现onEdgeDragStarted方法,并在onEdgeDragStarted方法中手动指定要移动的子View
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
mDragHelper.captureChildView(mDragView2, pointerId);
}
意思就是,mDragView2接下的行为mDragHelper接管了,直接在回调方法里:clampViewPositionVertical与clampViewPositionHorizontal做处理即可。
下面来看通过ViewDragHelper实现侧滑的简单逻辑:
1.重写任意一个ViewGroup,这里我使用的是RelativeLayout。
2.(假设你已经了解了ViewDragHelper)通过ViewDragHelper接管触摸事件,细节处理我会注释在代码中。
MyGroupView类
open class MyGroupView : RelativeLayout {
private lateinit var mDragHelper: ViewDragHelper
private lateinit var menu: View
private lateinit var content: View
private var showPer: Int = 80
private var currentLeft: Int = -1
private val parentSize = object {
var width = 0
var height = 0
}
private val menuSize = parentSize
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
init()
}
private fun init() {
mDragHelper = ViewDragHelper.create(this, 1.0f, object : ViewDragHelper.Callback() {
//是否捕捉视图
override fun tryCaptureView(child: View?, pointerId: Int): Boolean {
child ?: return false
return false
}
//水平滑动坐标
override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int {
child ?: return super.clampViewPositionHorizontal(child, left, dx)
//始终都是取left的值,初始值为-child.getWidth(),当向右拖动的时候left值增大,当left大于0的时候取0
return if (left <= 0 && -menuSize.width < left) left
else if (-menuSize.width >= left) -menuSize.width
else 0
}
//View拖行结束
override fun onViewReleased(releasedChild: View?, xvel: Float, yvel: Float) {
releasedChild ?: return super.onViewReleased(releasedChild, xvel, yvel)
if (Math.abs(currentLeft) > menuSize.width / 2) {
mDragHelper.settleCapturedViewAt(-menuSize.width, 0)
} else {
mDragHelper.settleCapturedViewAt(0, 0)
}
postInvalidate()
}
//拖动坐标的变化
override fun onViewPositionChanged(changedView: View?, left: Int, top: Int, dx: Int, dy: Int) {
super.onViewPositionChanged(changedView, left, top, dx, dy)
currentLeft = left
}
//边缘拖动
override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {
if (edgeFlags == ViewDragHelper.EDGE_LEFT)
mDragHelper.captureChildView(menu, pointerId)
}
})
mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
ev ?: return super.onInterceptTouchEvent(ev)
return mDragHelper.shouldInterceptTouchEvent(ev)
}
override fun computeScroll() {
if (mDragHelper.continueSettling(true))
invalidate()
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
event ?: return super.onTouchEvent(event)
mDragHelper.processTouchEvent(event)
return true
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
parentSize.width = MeasureSpec.getSize(widthMeasureSpec)
parentSize.height = MeasureSpec.getSize(heightMeasureSpec)
//重新设置菜单宽高,控制宽高
menu.layoutParams.apply {
width = parentSize.width * showPer / 100
height = parentSize.height
//组装侧滑视图的宽高
menuSize.width = width
menuSize.height = height
}
mDragHelper.smoothSlideViewTo(menu, -width, height)
}
//布局完成 xml 的解析
override fun onFinishInflate() {
super.onFinishInflate()
//获取主视图View
content = getChildAt(0)
//获取菜单View
menu = getChildAt(1)
}
public fun closeMenu() {
mDragHelper.smoothSlideViewTo(menu, -menuSize.width, menuSize.height)
currentLeft = -menuSize.width
}
public fun openMenu() {
mDragHelper.smoothSlideViewTo(menu, 0, menuSize.height)
currentLeft = 0
}
}
XML视图
<?xml version="1.0" encoding="utf-8"?>
<com.example.aml.gittext.activity.view.MyGroupView 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="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000"
android:clickable="true">
<TextView
android:id="@+id/id_content_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="我是主视图"
android:textSize="40sp"/>
</RelativeLayout>
<FrameLayout
android:id="@+id/id_container_menu"
android:layout_width="200dp"
android:layout_height="match_parent">
</FrameLayout>
</com.example.aml.gittext.activity.view.MyGroupView>
MenuFragment类
class MenuFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
inflater?: return View(context)
return inflater.inflate(R.layout.menu_fragment, container, false)
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
android.support.v4.app.FragmentManager manager = getSupportFragmentManager();
manager.findFragmentById(R.id.id_container_menu);
manager.beginTransaction().add(R.id.id_container_menu, new MenuFragment()).commit();
}
}
结束,代码需要优化,不建议直接使用到项目中~
源码已上传Github:https://github.com/519401502/draghelper
笔者能力有限,不足之处欢迎指出。