仿 Airbnb 主页

2,494 阅读3分钟
原文链接: www.jianshu.com

用behavior实现的

项目地址

github.com/CSnowStack/…

实现的效果


preview.gif

建议

请先阅读这篇文章
www.jianshu.com/p/f7989a2a3…

好多东西抄自这里

思路

    1. 都是依赖的Tab
    1. 列表是跟随Tab
    1. 向上的箭头高度是写死的,图标由顶部移动到居中的位置
    1. 刚开始详细的搜索的头部跟全部搜索的头部一致,最终移动到向上箭头的下面
    1. 后面绿色背景也是跟随tab

ListBehvior 的实现

抄自 HeaderScrollingViewBehavior

1 列表的高度计算

//列表高度为=可用高度-依赖的view的高度
final int height = availableHeight - header.getMeasuredHeight();
final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
? View.MeasureSpec.EXACTLY
: View.MeasureSpec.AT_MOST);

2 列表的初始位置

available.set(parent.getPaddingLeft() + lp.leftMargin,
            header.getBottom() + lp.topMargin,
            parent.getWidth() - parent.getPaddingRight() - lp.rightMargin,
            parent.getHeight() + header.getBottom()
                        - parent.getPaddingBottom() - lp.bottomMargin);

3 跟随 Tab移动

//跟随tab移动
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        child.setTranslationY(dependency.getTranslationY());
        return true;
}

TabBehavior的实现

1 是否开始嵌套滑动的判断

//只要是垂直方向的就开始
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0

@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
mControlChange = true;//手动导致的改变
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}

2 Scroll的分配

/**
 * @param dy 向上滑大于0
 */
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
    mUp = dy > 0;//保留记录,是否向上滑动
    if (mValueAnimator.isRunning()) {//如果有已经开始的动画,则结束动画
        mValueAnimator.cancel();
    }

    //如果list需要滑动 且 不是(向上滑&&tab不在顶部)
    if (isChildRequestScroll(child.getTranslationY())) {
        consumed[1] = 0;
        return;
    }

    consumed[1] = dy;//全部消耗
    int distance = -dy / 2;//降低移动的速度


    if (child.getTranslationY() + distance > mTranslationMax) {//大于最大距离
        distance = mTranslationMax;
    } else if (child.getTranslationY() + distance < 0) {//到顶部
        distance = 0;
    } else {//正常
        distance = (int) (child.getTranslationY() + distance);
    }

    //判断应该显示的样式
    if (mUp && distance < (mTranslationMin - mHeightChild / 2) && !mWhiteStyle) {
        setWhiteStyle();
    } else if (!mUp && distance > (mTranslationMin + mHeightChild / 2) && mWhiteStyle) {
        setGreenStyle();
    }
    child.setTranslationY(distance);
}

3 Fling时候的判断

/**
 * list 不需要滑动就拦截.需要就不拦截
 */
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
    if (velocityY < -1000 && child.getTranslationY() == 0) {//向下滑的速度小于负1000
        mControlChange = false;
        showSearchAll();
    }

    return !isChildRequestScroll(child.getTranslationY());
}

4 Stop时候的判断

@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
    mControlChange = false;
    float translationY = child.getTranslationY();
    //如果是几个需要停住的点,则不管
    if (translationY == mTranslationMax || translationY == mTranslationMin || translationY == 0) {
        return;
    }

    scroll(child, translationY);

}


/**
 * 三种情况
 * 1 在顶部
 * 2 在all的下面
 * 3 在condition的下面
 */
private void scroll(final View child, final float translationY) {
    final float shouldMoveDistance;
    if (translationY < mHeightChild / 2) {//这段去最上面
        shouldMoveDistance = -translationY;
    } else if ((translationY > mHeightChild / 2 && translationY < (mTranslationMin + mHeightChild / 2)) ||
            (mUp && translationY < (mTranslationMax - mHeightChild))) {//回到中间的点
        shouldMoveDistance = mTranslationMin - translationY;
    } else {//去最下面
        shouldMoveDistance = mTranslationMax - translationY;
    }


    mValueAnimator.setDuration((long) (Math.abs(shouldMoveDistance) / mTranslationMax * Constants.DURATION_SCROLL));
    mValueAnimator.removeAllUpdateListeners();
    mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            child.setTranslationY(translationY + animation.getAnimatedFraction() * shouldMoveDistance);
        }
    });
    mValueAnimator.start();
}

SearchAllBehavior的实现

//最大的位移的高度
mChangeHeight = mHeightUp - mMarginTop;

@Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    float translationY = dependency.getTranslationY();
    if (translationY < mTranslationMin) {//向上移动的时候跟随tab
       //如果在屏幕外面则移动进来
        child.setTranslationX(0);
        child.setTranslationY(translationY - mTranslationMin);
    } else {//展开的时候,根据比例设置
        //tab 位移的比例
        float fraction = (translationY - mTranslationMin) / (mTranslationMax - mTranslationMin);
        //根据比例设置位移的距离
        if (fraction <= 1 / 3f) {//跟详细信息里面的where一样的移动
            child.setTranslationX(0);
            child.setTranslationY(fraction * mChangeHeight);
            child.setAlpha(1 - fraction * 3);
        } else {
            if (child.getTranslationX() != mTranslationGone) {//移动到屏幕外面,重置透明度为0
                child.setTranslationX(mTranslationGone);
                child.setAlpha(0);
            }

        }
    }
    return true;
}

SearchItemBehavior的实现

    @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        float translationY = dependency.getTranslationY();
        if (translationY >= mTranslationMin) {//开始显示各个item的时候,把child移动到屏幕里面,重置alpha为1
            if (child.getTranslationX() != 0) {
                child.setTranslationX(0);
                child.setAlpha(1);
            }

            //tab 位移的比例
            float fraction = (translationY - mTranslationMin) / (mTranslationMax - mTranslationMin);
            //根据比例设置位移的距离
            child.setTranslationY(fraction * mChangeHeight);

            //根据比例设置透明度
            if (fraction < 1 / 3f) {
                mItemWhere.setTranslationX(0);
                mItemWhen.setTranslationX(mTranslationGone);
                mItemWho.setTranslationX(mTranslationGone);
                mItemWhere.setAlpha(fraction * 3);
                mItemWhen.setAlpha(0);
                mItemWho.setAlpha(0);
            } else if (fraction < 2 / 3f) {
                mItemWhere.setTranslationX(0);
                mItemWhen.setTranslationX(0);
                mItemWho.setTranslationX(mTranslationGone);
                mItemWhere.setAlpha(1);
                mItemWhen.setAlpha(fraction * 3 - 1);
                mItemWho.setAlpha(0);
            } else {
                mItemWhere.setTranslationX(0);
                mItemWhen.setTranslationX(0);
                mItemWho.setTranslationX(0);
                mItemWhere.setAlpha(1);
                mItemWhen.setAlpha(1);
                mItemWho.setAlpha(fraction * 3 - 2);
            }
        } else if (child.getTranslationX() != mTranslationGone) {//改隐藏的时候移动到屏幕外面
                child.setTranslationX(mTranslationGone);
                child.setAlpha(0);
        }
        return true;
    }

UpIconBehavior的实现

@Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    float translationY = dependency.getTranslationY();
    float fraction = (translationY - mTranslationMin) / (mTranslationMax - mTranslationMin);
    //根据比例设置位移的距离
    if (fraction >= 1 / 3f) {
        child.setTranslationX(0);
        float fractionItem = (fraction * 3 - 1) / 2;
        child.setTranslationY(-(1 - fractionItem) * mChangeHeight);
        child.setAlpha(fractionItem);
    } else if (child.getTranslationX() != mTranslationGone) {
        child.setTranslationX(mTranslationGone);
        child.setAlpha(0);
    }
    return true;
}

BGBehavior的实现

@Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    child.setTranslationY(dependency.getTranslationY()-mTranslationMax);
    return true;
}