Android 从 0 开始学习自定义 View(十) 可拖动回
2021-05-31 本文已影响0人
是刘航啊
效果图
效果描述:当滑动距离设定的距离( 默认后背景内容一半 )时会。当滑动的距离设定的距离时会。当列表向上滑动时,如果背景是会先将背景关闭,之后再处理列表的滑动。
效果实现分析
- 处理 ViewDragHelper
- 处理拖动以及拖动的范围
- 处理回弹效果
- 滑动冲突
1、处理 ViewDragHelper
public class VerticalDragView extends FrameLayout {
private ViewDragHelper mDragHelper;
public VerticalDragView(@NonNull Context context) {
this(context, null);
}
public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDragHelper = ViewDragHelper.create(this, mCallback);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
return true;
}
@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
return top;
}
@Override
public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
return left;
}
@Override
public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
}
};
}
这里只是创建了 ViewDragHelper,并没有做什么特殊处理,接下来处理可以拖动的 View 以及拖动的范围。
2、处理拖动以及拖动的范围
public class VerticalDragView extends FrameLayout {
private ViewDragHelper mDragHelper;
//可以拖动的View
private View mDragView;
//拖动范围
int menuHeight = 0;
private boolean mMenuIsOpen = false;
public VerticalDragView(@NonNull Context context) {
this(context, null);
}
public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDragHelper = ViewDragHelper.create(this, mCallback);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
menuHeight = getChildAt(0).getMeasuredHeight();
}
}
private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
return return child == mDragView;
}
@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
//垂直滑动移动的位置
if (top <= 0) {
top = 0;
}
if (top >= menuHeight) {
top = menuHeight;
}
return top;
}
@Override
public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
}
};
}
需要注意的是 View 的高度在 onMeasere 才会被计算,如果直接获取是拿不到高度的。我是在 onLayout 获取高度,onMeasure 也可以,看个人选择。
3、处理回弹效果
public class VerticalDragView extends FrameLayout {
private ViewDragHelper mDragHelper;
//可以拖动的View
private View mDragView;
//拖动范围
int menuHeight = 0;
public VerticalDragView(@NonNull Context context) {
this(context, null);
}
public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDragHelper = ViewDragHelper.create(this, mCallback);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
menuHeight = getChildAt(0).getMeasuredHeight();
}
}
private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
return return child == mDragView;
}
@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
//垂直滑动移动的位置
if (top <= 0) {
top = 0;
}
if (top >= menuHeight) {
top = menuHeight;
}
return top;
}
@Override
public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
if (mDragView.getTop() > menuHeight / 2) {
//滚动到菜单的高度(打开)
mDragHelper.settleCapturedViewAt(0, menuHeight);
mMenuIsOpen = true;
} else {
//滚动到0(关闭)
mDragHelper.settleCapturedViewAt(0, 0);
mMenuIsOpen = false;
}
invalidate();
}
};
@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
invalidate();
}
}
}
4、滑动冲突
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mMenuIsOpen) {
return true;
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownY = ev.getY();
mDragHelper.processTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
float offSetY = ev.getY() - mDownY;
if (offSetY > 0 && !canChildScrollUp()) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
/**
* 判断是否滚动到顶部
*
* @return
*/
public boolean canChildScrollUp() {
if (mDragView instanceof ListView) {
return ListViewCompat.canScrollList((ListView) mDragView, -1);
}
return mDragView.canScrollVertically(-1);
}
滑动冲突主要是判断菜单的开合状态、滑动的状态( 向上滑动|向下划动 )、列表是否滑动到头部