22.png
44.png
一、效果描述
此控件由两部分组成:顶部布局 + list
向上滑动,顶部布局未隐藏,整体向上滑动外部view;顶部布局隐藏,滑动内部list
向下滑动,顶部布局未隐藏,整体向下滑动外部view;顶部布局隐藏,滑动内部list
二、控件实现
布局:
<?xml version="1.0" encoding="utf-8"?>
<com.example.didi.nestedscrollingdemo.view.NestedScrollingParentView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<TextView
android:id="@+id/top"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@color/colorAccent"
android:gravity="center"
android:textColor="@color/colorPrimaryDark"
android:text="@string/app_name"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark">
</android.support.v7.widget.RecyclerView>
</com.example.didi.nestedscrollingdemo.view.NestedScrollingParentView>
分析:
主要就是如何实现外部view(即NestedScrollingParentView),NestedScrollingParentView是继承LinearLayout的自定义view,它需要实现NestedScrollingParent,里面有三个方法比较重要,分别是onStartNestedScroll、onNestedPreScroll、onNestedFling
onStartNestedScroll:是否能接收到内部view滑动时的参数,可直接返回true
onNestedPreScroll:该方法会传入内部view的移动的dx、dy,可用consumed来控制是否消耗掉dx、dy(即是否不触发内部view滑动事件)
onNestedFling:可以捕获内部view的fling,返回true则表示拦截内部view事件
内部view List需要实现NestedScrollingChild(RecyclerView已经实现了NestedScrollingChild)
核心部分在我们的demo中的具体实现:
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
boolean hiddenTop = dy > 0 && getScrollY() < mTopViewHeight;//若向上滑动,且未隐藏顶部
boolean showTop = dy < 0 && getScrollY() >= 0 && !ViewCompat.canScrollVertically(target, -1);//若向下滑动,且内部view无法下拉
if (hiddenTop || showTop) {//实现滑动,且消耗掉dy(内部view不滑动)
scrollBy(0, dy);
consumed[1] = dy;
}
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
if (getScrollY() >= mTopViewHeight)//已隐藏顶部
return false;
fling((int) velocityY);
return true;
}
private void fling(int velocityY) {
mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, mTopViewHeight);
invalidate();
}
另需实现的方法如下:
@Override
public void scrollTo(int x, int y) { //滚动到的位置
if (y < 0) {
y = 0;
}
if (y > mTopViewHeight) {
y = mTopViewHeight;
}
if (y != getScrollY()) {
super.scrollTo(x, y);
}
}
@Override
protected void onFinishInflate() //加载完layout会回调自定义view的这个方法,可用于获取布局中的控件
{
super.onFinishInflate();
mTop = findViewById(R.id.top);
mRecycler = findViewById(R.id.recycler_view);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) //设置view的大小
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup.LayoutParams params = mRecycler.getLayoutParams(); //获取到RecyclerView之后,设置其高度,否则向上滑动,由于高度不够,RecyclerView也会跟着向上滑动
params.height = getMeasuredHeight();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int old)
//布局过程中,会先调用onMeasure设置其大小,再调用onLayout设置位置;布局发送变化时调用此方法,间接调用onMeasure、onLayout
{
super.onSizeChanged(w, h, oldw, oldh);
mTopViewHeight = mTop.getMeasuredHeight(); //获得顶部布局的高度
}
@Override
public void computeScroll() //这个方法不是让viewgroup滑动的,真正让viewgroup滑动的是scrollto、scroll by,它由draw来调用
{
if (mScroller.computeScrollOffset())//true:尚未滚动结束
{
scrollTo(0, mScroller.getCurrY());//完成实际滚动
invalidate();
}
}
网友评论