转载请注明出处http://blog.csdn.net/teisun/article/details/46480605
效果图
源码和例子
github:https://github.com/teisun/Android-PullPushScrollView
csdn:http://download.csdn.net/detail/teisun/8802179
效果描述
一个自定义控件继承自ScrollView,下拉时header会放大松开后会恢复原状,上滑时header会被下面的内容吃掉盖住而且会稍稍往上滑,在header高度范围内滑动时导航栏背景和导航栏的按钮会反向改变透明度形成一种对比效果。
实现原理
4个对象
ScrollView 也就是父View
Header View 是ScrollView的子View
Content View 也是ScrollView的子View,在Header View的下方
导航栏 位置固定在顶部
4个效果
上滑时Header view被吃掉的效果
ScrollView上滑时,Header根据父View上滑的百分比来下滑这种相对运动会让Header看起来好像被其下方的Content View盖住的效果。
上滑时Header view缓缓上滑效果
为什么是缓缓呢?在实现完上一个效果后感觉过于生硬,就让header也带有点往上滑动的效果感觉好多了,实现原理很简单就是改变header view的高度(上滑时是缩小),这样header看起来就有点被盖住又有点上滑的效果了,下拉反之。
下拉超出Header高度时Header放大松手时还原的效果
当Header完全显示时下拉父View,根据下拉偏移量系数来改变Header高度来达到放大的效果并且调用scrollTo函数让整个父View整体下移来保证Header放大后可以完全显示,手指离开屏幕后使用属性动画(属性动画这里不再展开自行百度吧)根据下拉偏移量整体上移还原到初始状态并缩小Header。
导航栏背景与按钮透明度反向改变效果
在Header高度范围内滑动时根据上滑下拉的偏移量动态改变背景和按钮的透明度(在Activity中实现)。
代码与注释
public class PullPushLayout extends ScrollView {
public interface OnTouchEventMoveListenre{
public void onSlide(int alpha);
}
private OnTouchEventMoveListenre mOnTouchEventMoveListenre;
public void setOnTouchEventMoveListenre(OnTouchEventMoveListenre l) {
mOnTouchEventMoveListenre = l;
}
private int mAlpha = 0;//透明度
private static final float MAX_ALPHA = 255.00000f;
// header的高度 单位:px
private int mHeaderHeight;//实时高度
private int lastTranslationY;//上次header所在的TranslationY
private int deltaTranslationY;//header的高度增量
private ViewGroup mHeader;
private View mHeaderChild;
private View mContent;
private ObjectAnimator oa;
private float lastY = -1;
private float deltaY = -1;
private int range;//header滑动范围的最大值
public PullPushLayout(Context context) {
super(context);
}
public PullPushLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullPushLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setVerticalScrollBarEnabled(false);
initView();
}
private void initView() {
mHeader = (ViewGroup) findViewById(R.id.rl_top);
mHeaderChild = mHeader.getChildAt(0);
mContent = findViewById(R.id.ll_content);
mHeader.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
mHeader.getViewTreeObserver().removeGlobalOnLayoutListener(this);
range = mHeader.getHeight();
mHeader.getLayoutParams().height = range;
mHeaderHeight = range;
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = ev.getY();
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
if (getScrollY() != 0) {
deltaY = 0;
lastY = ev.getY();
} else {
deltaY = ev.getY() - lastY;
if (deltaY > 0) {
//下拉
setT((int) -deltaY / 5);
return true;
}
}
break;
case MotionEvent.ACTION_UP:
if (getScrollY() < range) {
if (deltaY != 0) {
//还原
reset();
}
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 滑动时要处理相关逻辑
*/
private void scrollListen(float percent) {
mHeaderHeight -= deltaTranslationY;
if (mOnTouchEventMoveListenre != null) {
mAlpha = (int) (percent * MAX_ALPHA);
if( mHeaderHeight == range ){
mAlpha = 0;
}
if( mAlpha > 255 ){
mAlpha = 255;
}
if( mAlpha < 0 ){
mAlpha = 0;
}
mOnTouchEventMoveListenre.onSlide(mAlpha);
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
//bug修复,如果下载csdn的资源需要加上这几行
if (t > range) {
return;
}
//
float percent = animateScroll(t);
animateUpSlide(t);//动态改变Header高度
deltaTranslationY = t - lastTranslationY;
lastTranslationY = t;
scrollListen(percent);
}
/**
* 下拉超出Header高度时Header放大效果
* @param t
*/
public void setT(int t) {
scrollTo(0, t);
if (t < 0) {
animatePull(t);
}
}
/**
* 上滑时Header view被吃掉的效果
* @param t
* @return header增量部分占header高度的百分比
*/
private float animateScroll(int t) {
float percent = (float) t / range;
if(percent > 1){
percent = 1;
}
ViewHelper.setTranslationY(mHeader, t);
return percent;
}
//下拉时Header view放大的效果
private void animatePull(int t) {
Message msg = mAnimatePullHandler.obtainMessage();
msg.arg1 = t;
mAnimatePullHandler.sendMessage(msg);
}
//下拉的处理逻辑,放在函数中在某些机型上GPU会处理不过来造成重绘失败,所以要放在handler中处理
private Handler mAnimatePullHandler = new Handler(){
public void handleMessage(Message msg) {
int t = msg.arg1;
mHeader.getLayoutParams().height = range - t;
mHeaderChild.getLayoutParams().height = range - t;
mHeaderChild.requestLayout();
};
};
//Header高度范围内滑动时的效果
private void animateUpSlide(int t) {
Message msg = mAnimateUpSlideHandler.obtainMessage();
msg.arg1 = t;
mAnimateUpSlideHandler.sendMessage(msg);
}
//上滑的处理逻辑,放在函数中在某些机型上GPU会处理不过来造成重绘失败,所以要放在handler中处理
private Handler mAnimateUpSlideHandler = new Handler(){
public void handleMessage(Message msg) {
int t = msg.arg1;
mHeaderChild.getLayoutParams().height = range - t;
mHeaderChild.requestLayout();
};
};
/*
* 还原下拉超出原始高度的效果
*/
private void reset() {
if (oa != null && oa.isRunning()) {
return;
}
oa = ObjectAnimator.ofInt(this, "t", (int) -deltaY / 5, 0);
oa.setDuration(150);
oa.start();
}
}
mPullPushLayout.setOnTouchEventMoveListenre(new OnTouchEventMoveListenre() {
@Override
public void onSlide(int alpha) {
//导航栏背景与按钮透明度反向改变效果
int alphaReverse = alphaMax - alpha;
if (alphaReverse < 0) {
alphaReverse = 0;
}
bgBackDrawable.setAlpha(alphaReverse);
bgShareDrawable.setAlpha(alphaReverse);
bgNavBarDrawable.setAlpha(alpha);
bglineNavBarDrawable.setAlpha(alpha);
}
});