纯粹是为了自己学习的过程,demo很简单。
实现效果:
分析:
- 首先这个布局是一个包含两个子View的ViewGroup,第二个子View是一个可以展示列表的View,比如ListView或者RecyclerView。VerticalDragLayout这个ViewGroup可以继承FrameLayout,这样默认第二个会显示在第一个上面。
- 列表是可以被拖拽的,那么什么时候可以拖拽?两种情况:
a、手指下滑,并且列表(RecyclerView或ListView)已经滑到顶部了,这时如果手指继续在屏幕上往下滑,列表就会被往下拖拽,第一个View就会显示出来。
b、当我们手指上滑,并且列表的top大于0时。这时如果手指继续在屏幕上往上滑,列表就会被往上拖拽,第一个View会被逐渐隐藏。 - 想要实现拖拽,需要借助ViewDragHelper这个类,并且只有第二个View也就是列表View可以被拖拽,被拖拽的方向是垂直方向,被拖拽的移动范围就是0到第一个View的高度。
- VerticalDragLayout需要重写onInterceptTouchEvent方法,根据可以被拖拽的情况,拦截触摸事件,交给VerticalDragLayout自己的onTouchEvent方法,在不该被拖拽的情况下,将事件分发给子View,让子View自己处理。
- 当我们的手指抬起时,如果第二个View被拖拽的距离大于头布局(第一个View)的一半就自动滚动到第一View高度处,否则滚回0处。
在布局中使用
<?xml version="1.0" encoding="utf-8"?>
<study.project.mao.view1.VerticalDragLayoutDemo.VerticalDragLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:text="Hello"
android:textSize="30sp"
android:gravity="center"
android:background="@color/colorAccent"
/>
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
/>
</study.project.mao.view1.VerticalDragLayoutDemo.VerticalDragLayout>
VerticalDragLayout的实现代码
VerticalDragLayout的实现很简单,主要的问题就是处理好事件的分发和拦截时机,直接贴代码。
/**
* 创建者: mao
* 功能描述:事件拦截demo
*/
public class VerticalDragLayout extends FrameLayout {
private ViewDragHelper mDragHelper;
private int mHeaderHeight;
private View mAbsListView;
private float mLastY;
public VerticalDragLayout(@NonNull Context context) {
this(context,null);
}
public VerticalDragLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public VerticalDragLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDragHelper=ViewDragHelper.create(this,0.1f,viewDragCallback);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
int childCount=getChildCount();
if (childCount!=2){
throw new RuntimeException("VerticalDragLayout can only two children but you gave "+childCount);
}
//获取到第二个View,这个View是需要被拖拽的View
mAbsListView= getChildAt(1);
}
/**
* 获取头布局的高度
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
View headerView=getChildAt(0);
mHeaderHeight=headerView.getMeasuredHeight();
}
/**
* View.canScrollVertically(1) 返回true表示还能下滑
* View.canScrollVertically(-1) 返回true表示还能上滑
*
* @param ev
* @return true 拦截事件 自己处理 ;false 不拦截 给子View处理
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
float currY= (float) ev.getY();
if (ev.getAction()==MotionEvent.ACTION_DOWN){
mDragHelper.processTouchEvent(ev);
mLastY=currY;
}
float y=currY-mLastY;
mLastY=currY;
//拦截的情况
//1、手指下滑,并且列表(RecyclerView或ListView)已经滑到顶部了
//2、手指上滑,并且mAbsListView的top大于0
if (y>0){
if (mAbsListView.getTop()==0&&!mAbsListView.canScrollVertically(-1)){
return true;
}
}else {
if (mAbsListView.getTop()>0){
return true;
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
mDragHelper.processTouchEvent(event);
break;
}
return true;
}
@Override
public void computeScroll()
{
if(mDragHelper.continueSettling(true))
{
invalidate();
}
}
private ViewDragHelper.Callback viewDragCallback=new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
//只允许mAbsListView可以拽拽
return mAbsListView==child;
}
/**
*
* @param child
* @param top 垂直方向拖拽移动的距离
* @param dy
* @return
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
if (top<0){
top=0;
}
if (top>mHeaderHeight){
top=mHeaderHeight;
}
return top;
}
/**
* 手指松开的时候调用
* 如果手指松开时 mAbsListView的拖拽距离大于头布局的一半就自动滚动到top=mHeaderHeight处,
* 否则滚动到top=0处
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (mAbsListView == releasedChild){
int top=mAbsListView.getTop();
if (top>mHeaderHeight/2){
mDragHelper.settleCapturedViewAt(0, mHeaderHeight);
invalidate();
}else {
mDragHelper.settleCapturedViewAt(0, 0);
invalidate();
}
}
}
};
}
学习资料:
ViewDragHelper实战 自己打造Drawerlayout
源码阅读分析 - View的Touch事件分发