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

来源:互联网 发布:关节机器人编程 编辑:程序博客网 时间:2024/06/10 00:19

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

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

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

关键的几个方法:

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;    }}
0 0