给部分代码看个大概实现,继承 linearLayout,自己处理滑动,自己计算子布局中第一个布局为折叠部分。
其实,用scrollview,将listview以及悬浮部分高度设置为屏幕高度,效果直接,省事,滑动体验更好。嵌套布局也没有滑动冲突
public class ScrollableLayout extends LinearLayout {
private final String tag = "cp:scrollableLayout";
private float mDownX;
private float mDownY;
private float mLastY;
private int minY = 0;
private int maxY = 0;
private int mHeadHeight;
private int mExpandHeight;
private int mTouchSlop;
private int mMinimumVelocity;
private int mMaximumVelocity;
// 方向
private DIRECTION mDirection;
private int mCurY;
private int mLastScrollerY;
private boolean needCheckUpdown;
private boolean updown;
private boolean mDisallowIntercept;
private boolean isClickHead;
private boolean isClickHeadExpand;
private View mHeadView;
private ViewPager childViewPager;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
private Context context;
private boolean isMain;
/**
* 滑动方向 *
*/
enum DIRECTION {
UP,// 向上划
DOWN// 向下划
}
public interface OnScrollListener {
void onScroll(int currentY, int maxY);
}
private OnScrollListener onScrollListener;
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
private ScrollableHelper mHelper;
public ScrollableHelper getHelper() {
return mHelper;
}
public ScrollableLayout(Context context) {
super(context);
init(context);
}
public ScrollableLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public ScrollableLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ScrollableLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
this.context = context;
mHelper = new ScrollableHelper();
mScroller = new Scroller(context);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
public boolean isSticked() {
return mCurY == maxY;
}
/**
* 扩大头部点击滑动范围
*
* @param expandHeight
*/
public void setClickHeadExpand(int expandHeight) {
mExpandHeight = expandHeight;
}
public int getMaxY() {
return maxY;
}
public void isMainPage(boolean isMain) {
this.isMain = isMain;
}
public boolean isHeadTop() {
return mCurY == minY;
}
public int getCurY() {
return mCurY;
}
public boolean canPtr() {
return updown && mCurY == minY && mHelper.isTop();
}
public void requestScrollableLayoutDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
mDisallowIntercept = disallowIntercept;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float currentX = ev.getX();
float currentY = ev.getY();
float deltaY;
int shiftX = (int) Math.abs(currentX - mDownX);
int shiftY = (int) Math.abs(currentY - mDownY);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
needCheckUpdown = true;
updown = true;
mDownX = currentX;
mDownY = currentY;
mLastY = currentY;
checkIsClickHead((int) currentY, mHeadHeight, getScrollY());
checkIsClickHeadExpand((int) currentY, mHeadHeight, getScrollY());
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
mScroller.forceFinished(true);
break;
case MotionEvent.ACTION_MOVE:
if (mDisallowIntercept) {
break;
}
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
deltaY = mLastY - currentY;
if (needCheckUpdown) {
if (shiftX > mTouchSlop && shiftX > shiftY) {
needCheckUpdown = false;
updown = false;
} else if (shiftY > mTouchSlop && shiftY > shiftX) {
needCheckUpdown = false;
updown = true;
}
}
if (updown && shiftY > mTouchSlop && shiftY > shiftX &&
(!isSticked() || mHelper.isTop() || isClickHeadExpand)) {
if (childViewPager != null) {
childViewPager.requestDisallowInterceptTouchEvent(true);
}
scrollBy(0, (int) (deltaY + 0.5));
}
mLastY = currentY;
break;
case MotionEvent.ACTION_UP:
if (updown && shiftY > shiftX && shiftY > mTouchSlop) {
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
float yVelocity = -mVelocityTracker.getYVelocity();
boolean dislowChild = false;
if (Math.abs(yVelocity) > mMinimumVelocity) {
mDirection = yVelocity > 0 ? DIRECTION.UP : DIRECTION.DOWN;
if ((mDirection == DIRECTION.UP && isSticked()) || (!isSticked() && getScrollY() == 0 && mDirection == DIRECTION.DOWN)) {
dislowChild = true;
} else {
mScroller.fling(0, getScrollY(), 0, (int) yVelocity, 0, 0, -Integer.MAX_VALUE, Integer.MAX_VALUE);
mScroller.computeScrollOffset();
mLastScrollerY = getScrollY();
invalidate();
}
}
if (!dislowChild && (isClickHead || !isSticked())) {
int action = ev.getAction();
ev.setAction(MotionEvent.ACTION_CANCEL);
boolean dispathResult = super.dispatchTouchEvent(ev);
ev.setAction(action);
return dispathResult;
}
}
break;
default:
break;
}
super.dispatchTouchEvent(ev);
return true;
}
public void scrolltoTop() {
updown = false;
mScroller.startScroll(0, mCurY, 0, -mCurY, 500);
invalidate();
}
public void scrolltoBottom() {
updown = false;
mScroller.startScroll(0, 0, 0, maxY, 500);
invalidate();
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private int getScrollerVelocity(int distance, int duration) {
if (mScroller == null) {
return 0;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return (int) mScroller.getCurrVelocity();
} else {
return distance / duration;
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
int currY = mScroller.getCurrY();
if (updown) {
if (mDirection == DIRECTION.UP) {
// 手势向上划
if (isSticked()) {
int distance = mScroller.getFinalY() - currY;
int duration = calcDuration(mScroller.getDuration(), mScroller.timePassed());
mHelper.smoothScrollBy(getScrollerVelocity(distance, duration), distance, duration);
mScroller.forceFinished(true);
return;
} else {
scrollTo(0, currY);
}
} else {
// 手势向下划
if (mHelper.isTop() || isClickHeadExpand) {
int deltaY = (currY - mLastScrollerY);
int toY = getScrollY() + deltaY;
scrollTo(0, toY);
if (mCurY <= minY) {
mScroller.forceFinished(true);
return;
}
}
invalidate();
}
mLastScrollerY = currY;
} else {
scrollTo(0, currY);
if (mCurY <= minY) {
mScroller.forceFinished(true);
return;
}
invalidate();
mLastScrollerY = currY;
}
}
}
@Override
public void scrollBy(int x, int y) {
int scrollY = getScrollY();
int toY = scrollY + y;
if (toY >= maxY) {
toY = maxY;
} else if (toY <= minY) {
toY = minY;
}
y = toY - scrollY;
super.scrollBy(x, y);
}
@Override
public void scrollTo(int x, int y) {
if (updown) {
if (y >= maxY) {
y = maxY;
} else if (y <= minY) {
y = minY;
}
}
mCurY = y;
if (onScrollListener != null) {
onScrollListener.onScroll(y, maxY);
}
super.scrollTo(x, y);
}
private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
}
private void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
private void checkIsClickHead(int downY, int headHeight, int scrollY) {
isClickHead = downY + scrollY <= headHeight;
}
private void checkIsClickHeadExpand(int downY, int headHeight, int scrollY) {
if (mExpandHeight <= 0) {
isClickHeadExpand = false;
}
isClickHeadExpand = downY + scrollY <= headHeight + mExpandHeight;
}
private int calcDuration(int duration, int timepass) {
return duration - timepass;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) + maxY, MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mHeadView = getChildAt(0);
if (isMain) {
maxY = mHeadView.getMeasuredHeight() - DensityUtils.dip2px(context, 48);//减去首页titlebar顶部高度
} else {
maxY = mHeadView.getMeasuredHeight();
}
mHeadHeight = mHeadView.getMeasuredHeight();
}
@Override
protected void onFinishInflate() {
if (mHeadView != null && !mHeadView.isClickable()) {
mHeadView.setClickable(true);
}
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childAt = getChildAt(i);
if (childAt != null && childAt instanceof ViewPager) {
childViewPager = (ViewPager) childAt;
}
}
super.onFinishInflate();
}
}