自定义悬浮头部标题栏HeaderFloatTitle(支持背景/透明度/位移的变化)

最近两个月一直在做视频播放以及代码重构方面的事情,收获挺多,但是一直没有时间来总结一下。

效果图参考:这里写链接内容

此文就是对顶部标题栏的一个简单封装。

关键的几个方法:

1.设置需要监听的View的Y轴变化区间。(比如想监听一个图片从Y坐标100移动到-100过程中,顶部标题栏有颜色透明度变化,则可以在这个方法中设置setRange(100, -100)

/**
     * 设置变化区间Y值坐标
     * @param mRangeMax
     * @param mRangeMin
     * @return
     */
    public HeaderFloatTitle setRange(float mRangeMax, float mRangeMin){
        this.mRangeMax = mRangeMax;
        this.mRangeMin = mRangeMin;
        return this;
    }

2.设置起始终点颜色

    /**
     * 设置背景色变化范围
     * @param startColor 起始颜色
     * @param endColor 最终颜色
     * @return
     */
    public HeaderFloatTitle setBackgroundRangeColor(int startColor, int endColor){
        mStartColor = startColor;
        mEndColor = endColor;
        return this;
    }

3.添加子View,这些子View会随着监听View的位置变化而进行透明度的变幻

    /**
     * 添加Alpha由1-0的View
     * @param mStartView
     * @return
     */
    public HeaderFloatTitle addStartView(View mStartView){
        this.mStartView = mStartView;
        addView(mStartView, 0);
        return this;
    }

    /**
     * 添加Alpha由0-1的View
     * @param mEndView
     * @return
     */
    public HeaderFloatTitle addEndView(View mEndView){
        this.mEndView = mEndView;
        addView(mEndView, 1);
        return this;
    }

4.添加分割线,有时候UI可以想在标题栏底部加一条很细的线,可以调用此方法。

    /**
     * 添加底部分割线
     * @return
     */
    public HeaderFloatTitle addBottomSepLine(){
        mBottomSepLine = new View(getContext());
        mBottomSepLine.setBackgroundColor(Color.parseColor("#dddddd"));
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Local.dip2px(0.5f));
        params.gravity = Gravity.BOTTOM;
        mBottomSepLine.setLayoutParams(params);
        addView(mBottomSepLine);
        return this;
    }

5.下面两个方法是最重要的两个方法。

onDependentViewChanged用来设置监听View移动过程中的Y坐标,调用这个方法,可以实现标题栏的透明度/背景色/位移变化。

onDependentViewScrollChanged用来监听滚动过程中,控制头部悬浮栏显示隐藏。

    /**
     * 依赖的View位置发送变化时监听
     * @param dependY 顶部y坐标
     */
    public void onDependentViewChanged(float dependY){

        //纪录当前位置
        mDependY = dependY;

        float percent = getPercent(dependY);

        LogUtils.i_debug(TAG, "percent:" + percent + "   nowY:" + dependY + "   height:" + mHeight);

        loadAlpha(percent);

        loadBackgroundColor(percent);

        loadOffset(percent);

        showLine(percent);

    }


    /**
     * 依赖的View滚动时头部显示隐藏控制
     * @param dy
     */
    public void onDependentViewScrollChanged(float dy){
        loadScrollAnimate(dy);
    }

使用:

//在RecyclerView中,比如想监听头图ldv_head从完全显示到完全隐藏过程中titleBar有颜色透明度位移变化。
//当列表滚动时,控制titleBar的显示隐藏,则设置如下:
myRecyclerView.setScrollLinsteners(new RefreshRecyclerView.ScrollLinsteners() {
            @Override
            public void onScrolled(int firstVisibleItem, int dx, int dy) {
        if (mLayoutManager != null && mLayoutManager.findViewByPosition(0) != null) {
            View view = mLayoutManager.findViewByPosition(0);
            int ivH = view.findViewById(R.id.ldv_head).getHeight();
            int paddingBottom = view.findViewById(R.id.ldv_head).getPaddingBottom();
                int top = view.getTop();
            mHeaderFloatTitle.setHasOffset(false).setRange(0, -ivH + paddingBottom);
            mHeaderFloatTitle.onDependentViewChanged(top);
        }else {
            mHeaderFloatTitle.onDependentViewScrollChanged(dy);
        }
}

在CoordinatorLayout中,也可以通过自定义Behavior来使用。比如:

headerFloatTitle.addStartView(rl_head_w);
            headerFloatTitle.addEndView(rl_head_b);
            headerFloatTitle.addBottomSepLine();
            headerFloatTitle.setHasScrollAnimate(false).setHasOffset(false).setHasAlpha(true);
            float image_hight = Local.getWidthPx() / 1.33f;
            headerFloatTitle.setRange(image_hight , 0);

……….

public class BrandDetailTitleBehavior extends CoordinatorLayout.Behavior<HeaderFloatTitle> {
    float height = Local.getWidthPx() / 1.33f;

    public BrandDetailTitleBehavior() {
    }

    public BrandDetailTitleBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, HeaderFloatTitle child, View dependency) {
//        child.setRange(200, 0);
        return dependency instanceof AppBarLayout;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, HeaderFloatTitle child, View dependency) {
        child.onDependentViewChanged(dependency.getTop() + height);
        return super.onDependentViewChanged(parent, child, dependency);
    }
}

全部代码:

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import com.loookapp.loook.Utils.Local;
import com.loookapp.loook.Utils.LogUtils;

/**
 * Created by lk on 16/11/11.
 *
 * 头部悬浮控件
 *
 */

public class HeaderFloatTitle extends FrameLayout {
    private static final String TAG = "HeaderFloatTitle";
    private final ArgbEvaluator mArgbEvaluator;//颜色估值器
    private float mRangeMax;//最大Y偏移量
    private float mRangeMin;//最小Y偏移量
    private float mDependY;//依赖控件Y的偏移量
    private int mStartColor;//开始颜色
    private int mEndColor;//最终颜色
    private View mStartView, mEndView;

    private boolean hasAlpha;//是否有alpha变化
    private boolean hasOffset;//是否有偏移变化
    private boolean hasScrollAnimate;//是否随着滚动而显示隐藏
    private boolean hasColor;//是否有颜色变化
    private boolean deleteTitleBarHeight;//计算range是否需要减去titlebar高度

    private boolean animateLoading;//动画进行中
    private float mHeight = -1;//自身高度
    private long animateDuration = 160; //动画执行时间
    private View mBottomSepLine;//分割线

    public HeaderFloatTitle(Context context) {
        this(context, null);
    }

    public HeaderFloatTitle(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HeaderFloatTitle(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mArgbEvaluator = new ArgbEvaluator();
        mStartColor = 0x00FFFFFF;
        mEndColor = 0xFFFFFFFF;
        hasAlpha = true;
        hasOffset = true;
        hasScrollAnimate = true;
        hasColor = true;
        deleteTitleBarHeight = true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mHeight = getMeasuredHeight();
    }

    /**
     * 依赖的View位置发送变化时监听
     * @param dependY 顶部y坐标
     */
    public void onDependentViewChanged(float dependY){

        //纪录当前位置
        mDependY = dependY;

        float percent = getPercent(dependY);

        LogUtils.i_debug(TAG, "percent:" + percent + "   nowY:" + dependY + "   height:" + mHeight);

        loadAlpha(percent);

        loadBackgroundColor(percent);

        loadOffset(percent);

        showLine(percent);

    }


    /**
     * 依赖的View滚动时头部显示隐藏控制
     * @param dy
     */
    public void onDependentViewScrollChanged(float dy){
        loadScrollAnimate(dy);
    }


    /**
     * 判断是否显示线
     * @param percent
     */
    private void showLine(float percent) {
        if (mBottomSepLine != null) {
            if (percent < 1 && hasColor) {
                mBottomSepLine.setVisibility(GONE);
            } else {
                mBottomSepLine.setVisibility(VISIBLE);
            }
        }
    }

    private void loadOffset(float percent) {
        if(hasOffset){
            float outOfRangeDy;
            if(percent == 1) {
                outOfRangeDy = mDependY - mRangeMin - mHeight;//超出的dy
            }else{
                outOfRangeDy = 0;
            }
            LogUtils.i_debug(TAG, "outOfRangeDy:" + outOfRangeDy + "   height:" + mHeight);
            animate().translationY(outOfRangeDy).setDuration(0).start();
        }
    }

    private void loadScrollAnimate(float dy) {
        LogUtils.i_debug(TAG, "mDependY:" + mDependY + "   mRangeMin:" + mRangeMin);
        LogUtils.i_debug(TAG, "hasScrollAnimate:" + hasScrollAnimate + " dy" + dy);
        if(hasScrollAnimate && !animateLoading){// mDependY < mRangeMin &&
            //位置移动
            if (dy < -10 && getTranslationY() != 0) {
                animate().translationY(0).setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        animateLoading = true;
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        animateLoading = false;
                    }
                }).setDuration(animateDuration).start();
            } else if (dy > 10 && getTranslationY() != -mHeight) {
                animate().translationY(-mHeight).setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        animateLoading = true;
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        animateLoading = false;
                    }
                }).setDuration(animateDuration).start();
            }
        }
    }

    private void loadBackgroundColor(float percent) {
        if(hasColor){
            int bgColor = getBackgroundColor(percent);
            setBackgroundColor(bgColor);
        }else {
            setBackgroundColor(mEndColor);
        }
    }

    private int getBackgroundColor(float percent){
        return (int) mArgbEvaluator.evaluate(percent, mStartColor, mEndColor);
    }

    private void loadAlpha(float percent) {
        if(hasAlpha) {
            if(mStartView != null) {
                mStartView.setAlpha(1 - percent);//1~0 start
            }

            if(mEndView != null) {
                mEndView.setAlpha(percent); //0~1 end
            }
        }
    }

    /**
     * 获取当前百分比
     * @param dependY
     * @return
     */
    private float getPercent(float dependY){
        if(dependY > mRangeMax)return 0;
        if(dependY < mRangeMin + mHeight)return 1;

        float range = getRange();
        float now = dependY - mRangeMin;
        if (deleteTitleBarHeight) {
            now -= mHeight;
        }
        //max >= dy >= min;
        float percent = (range != 0) ? (1 - Math.abs(now / range)) : 0;//0~1
        percent = percent > 1 ? 1 : (percent < 0 ? 0 : percent);
        return percent;
    }


    /**
     * 获取颜色或alpha变化的dy范围
     * @return
     */
    private float getRange(){
        float range = mRangeMax - mRangeMin;
        if(deleteTitleBarHeight && range > mHeight){
            range -= mHeight;
        }else {
            hasOffset = false;
        }
        return range;
    }


    /**
     * 添加Alpha由1-0的View
     * @param mStartView
     * @return
     */
    public HeaderFloatTitle addStartView(View mStartView){
        this.mStartView = mStartView;
        addView(mStartView, 0);
        return this;
    }

    /**
     * 添加Alpha由0-1的View
     * @param mEndView
     * @return
     */
    public HeaderFloatTitle addEndView(View mEndView){
        this.mEndView = mEndView;
        addView(mEndView, 1);
        return this;
    }

    /**
     * 添加底部分割线
     * @return
     */
    public HeaderFloatTitle addBottomSepLine(){
        mBottomSepLine = new View(getContext());
        mBottomSepLine.setBackgroundColor(Color.parseColor("#dddddd"));
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Local.dip2px(0.5f));
        params.gravity = Gravity.BOTTOM;
        mBottomSepLine.setLayoutParams(params);
        addView(mBottomSepLine);
        return this;
    }

    /**
     * 子View是否进行alpha变化
     * @param hasAlpha
     * @return
     */
    public HeaderFloatTitle setHasAlpha(boolean hasAlpha) {
        this.hasAlpha = hasAlpha;
        return this;
    }

    /**
     * 是否随着位置移动改变floatTitle的位置
     * @param hasOffset
     * @return
     */
    public HeaderFloatTitle setHasOffset(boolean hasOffset) {
        this.hasOffset = hasOffset;
        return this;
    }


    /**
     * 滚动时是否加载显示隐藏动画
     * @param hasScrollAnimate
     * @return
     */
    public HeaderFloatTitle setHasScrollAnimate(boolean hasScrollAnimate) {
        this.hasScrollAnimate = hasScrollAnimate;
        return this;
    }

    /**
     * 设置是否有颜色变化
     * @param hasColor
     * @return
     */
    public HeaderFloatTitle setHasColor(boolean hasColor) {
        this.hasColor = hasColor;
        return this;
    }

    /**
     * 是否去除titleBar高度
     * @param deleteTitleBarHeight
     * @return
     */
    public HeaderFloatTitle setDeleteTitleBarHeight(boolean deleteTitleBarHeight){
        this.deleteTitleBarHeight = deleteTitleBarHeight;
        return this;
    }

    /**
     * 设置变化区间Y值坐标
     * @param mRangeMax
     * @param mRangeMin
     * @return
     */
    public HeaderFloatTitle setRange(float mRangeMax, float mRangeMin){
        this.mRangeMax = mRangeMax;
        this.mRangeMin = mRangeMin;
        return this;
    }

    /**
     * 设置背景色变化范围
     * @param startColor 起始颜色
     * @param endColor 最终颜色
     * @return
     */
    public HeaderFloatTitle setBackgroundRangeColor(int startColor, int endColor){
        mStartColor = startColor;
        mEndColor = endColor;
        return this;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值