最近两个月一直在做视频播放以及代码重构方面的事情,收获挺多,但是一直没有时间来总结一下。
效果图参考:这里写链接内容
此文就是对顶部标题栏的一个简单封装。
关键的几个方法:
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;
}
}