布局比较复杂的时候,可能会嵌套两层或者三层ViewPager,这时整个布局的高度就会有问题,需要自定义控件去适配
先提供两个类
public class ScrollableLayout extends LinearLayout {
private final String TAG = "scrollableLayout";
private float mDownX;
private float mDownY;
private float mLastY;
private int minY = 0;
private int maxY = 0;
private int maxGap = 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;
public boolean isScroll = false;
/**
* 滑动方向 *
*/
public enum DIRECTION {
UP,// 向上划
DOWN// 向下划
}
public interface OnScrollListener {
void onScroll(int currentY, int maxY);
}
private OnScrollListener onScrollListener;
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
//滚动状态监听,V1.1.3新增
// public interface OnScrollStatusListener {
// void onScrollStatus(boolean isScroll);
// }
//
// private OnScrollStatusListener onScrollStatusListener;
//
// public void setOnScrollStatusListener(OnScrollStatusListener l) {
// this.onScrollStatusListener = l;
// }
public interface OnScrollableLayoutTouchListener {
void onDispatchEvent(boolean isDown, float rawy);
}
private OnScrollableLayoutTouchListener onScrollableLayoutTouchListener;
public void setOnScrollableLayoutTouchListener(OnScrollableLayoutTouchListener listener) {
this.onScrollableLayoutTouchListener = listener;
}
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) {
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 boolean isHeadTop() {
return mCurY == minY;
}
public boolean canPtr() {
return updown && mCurY == minY && mHelper.isTop();
}
public int getMaxGap() {
return maxGap;
}
public void setMaxGap(int maxGap) {
this.maxGap = maxGap;
}
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:
if (null != onScrollableLayoutTouchListener) {
onScrollableLayoutTouchListener.onDispatchEvent(true, currentY);
}
mDisallowIntercept = false;
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 (null != onScrollableLayoutTouchListener) {
onScrollableLayoutTouchListener.onDispatchEvent(false, currentY);
}
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;
}
@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()) {
// if(null!=onScrollStatusListener){
// onScrollStatusListener.onScrollStatus(true);
// }
final int currY = mScroller.getCurrY();
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 {
// if(null!=onScrollStatusListener){
// onScrollStatusListener.onScrollStatus(false);
// }
//
// }
}
@Override
public void scrollBy(int x, int y) {
int scrollY = getScrollY();
// LogUtils.i("scrollBy===y===" + y + ";;;;scrollY==" + scrollY);
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) {
// LogUtils.i("scrollTo===maxY==" + maxY + ";;;;" + y);
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) {
mHeadView = getChildAt(0);
measureChildWithMargins(mHeadView, widthMeasureSpec, 0, MeasureSpec.UNSPECIFIED, 0);
maxY = mHeadView.getMeasuredHeight() - maxGap;
mHeadHeight = mHeadView.getMeasuredHeight();
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize
(heightMeasureSpec) + maxY, MeasureSpec.EXACTLY));
}
@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();
}
public DIRECTION getmDirection() {
return mDirection;
}
public void setmDirection(DIRECTION mDirection) {
this.mDirection = mDirection;
}
public class ScrollableHelper {
private static final String TAG = "ScrollableHelper";
private ScrollableContainer mCurrentScrollableCainer;
// public int viewTopMargin = 0;
/**
* a viewgroup whitch contains ScrollView/ListView/RecycelerView..
*/
public interface ScrollableContainer {
public int viewTopMargin = 0;
/**
* @return ScrollView/ListView/RecycelerView..'s instance
*/
View getScrollableView();
}
public void setCurrentScrollableContainer(ScrollableContainer scrollableContainer) {
this.mCurrentScrollableCainer = scrollableContainer;
}
private View getScrollableView() {
if (mCurrentScrollableCainer == null) {
return null;
}
return mCurrentScrollableCainer.getScrollableView();
}
/**
* 判断是否滑动到顶部方法,ScrollAbleLayout根据此方法来做一些逻辑判断
* 目前只实现了AdapterView,ScrollView,RecyclerView
* 需要支持其他view可以自行补充实现
*
* @return
*/
public boolean isTop() {
View scrollableView = getScrollableView();
if (scrollableView == null) {
// throw new NullPointerException("You should call ScrollableHelper
// .setCurrentScrollableContainer() to set ScrollableContainer.");
return true;
}
if (scrollableView instanceof AdapterView) {
return isAdapterViewTop((AdapterView) scrollableView);
}
if (scrollableView instanceof ScrollView) {
return isScrollViewTop((ScrollView) scrollableView);
}
if (scrollableView instanceof RecyclerView) {
return isRecyclerViewTop((RecyclerView) scrollableView);
}
if (scrollableView instanceof WebView) {
return isWebViewTop((WebView) scrollableView);
}
throw new IllegalStateException("scrollableView must be a instance of " +
"AdapterView|ScrollView|RecyclerView");
}
private static boolean isRecyclerViewTop(RecyclerView recyclerView) {
if (recyclerView != null) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
int firstVisibleItemPosition = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
View childAt = recyclerView.getChildAt(0);
if (null != childAt) {
// LogUtils.d("childAt.getTop()==" + childAt.getTop());
}
if (childAt == null || (firstVisibleItemPosition == 0 && childAt.getTop() > -1)) {
return true;
}
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] firstVisibleItemPositions = ((StaggeredGridLayoutManager) layoutManager)
.findFirstVisibleItemPositions(new int[]{0, 1});
int margin = 0;
// try {
// BaseStaggeredGridAdapter adapter = (BaseStaggeredGridAdapter) recyclerView
// .getAdapter();
// if (null == adapter || 0 >= adapter.getItemCount()) {
// return true;
// }
// margin = adapter.getViewTop();
// } catch (Exception e) {
// e.printStackTrace();
// LogUtil.e("StaggeredGridLayoutManager", e.getMessage());
// margin = 0;
// }
View childAt = recyclerView.getChildAt(firstVisibleItemPositions[0]);
if (childAt != null) {
// LogUtils.d("StaggeredGridLayoutManager", "childAt.getTop()==" + childAt.getTop
// ());
}
if (childAt != null && (firstVisibleItemPositions[0] == 0 && childAt.getTop() ==
margin)) {
return true;
}
}
}
return false;
}
private static boolean isAdapterViewTop(AdapterView adapterView) {
if (adapterView != null) {
int firstVisiblePosition = adapterView.getFirstVisiblePosition();
View childAt = adapterView.getChildAt(0);
int margin = 0;
if (null != childAt) {
// if (adapterView instanceof StaggeredGridView) {
// margin = ((StaggeredGridView) adapterView).getmItemMargin();
//
// } else {
// margin = childAt.getTop();
// }
// LogUtils.d("isAdapterViewTop", "childAt.getTop()==" + childAt.getTop() + "," +
// "margin==" + margin);
}
if (childAt == null || (firstVisiblePosition == 0 && childAt.getTop() == margin)) {
return true;
}
}
return false;
}
private static boolean isScrollViewTop(ScrollView scrollView) {
if (scrollView != null) {
int scrollViewY = scrollView.getScrollY();
// LogUtils.d("isScrollViewTop", "scrollViewY===" + scrollViewY);
return scrollViewY <= 0;
}
return false;
}
private static boolean isWebViewTop(WebView scrollView) {
if (scrollView != null) {
int scrollViewY = scrollView.getScrollY();
return scrollViewY <= 0;
}
return false;
}
@SuppressLint("NewApi")
public void smoothScrollBy(int velocityY, int distance, int duration) {
View scrollableView = getScrollableView();
if (scrollableView instanceof AbsListView) {
AbsListView absListView = (AbsListView) scrollableView;
if (Build.VERSION.SDK_INT >= 21) {
absListView.fling(velocityY);
} else {
absListView.smoothScrollBy(distance, duration);
}
} else if (scrollableView instanceof ScrollView) {
((ScrollView) scrollableView).fling(velocityY);
} else if (scrollableView instanceof RecyclerView) {
((RecyclerView) scrollableView).fling(0, velocityY);
} else if (scrollableView instanceof WebView) {
((WebView) scrollableView).flingScroll(0, velocityY);
}
}
在第一层的ViewPager布局里面使用ScrollableLayout,这里不需要进行任何配置
下部分ViewPager+Fragment的布局,正常的一个列表,其实实际情况需要放一个上拉加载的控件
到这里基本就可以实现正常的显示下部分Fragment的内容了,但是这个时候会发生上下滑动事件的冲突,需要在底部的Fragment里面继承ScrollableHelper.ScrollableContainer,实现getScrollableView方法,返回当前布局的RecyclerView
在上一层的Fragment里面设置ScrollableLayout.getHelper().setCurrentScrollableContainer(fagment),此时的fragment为当前显示的fragment,可以监听ViewPager的切换事件
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
OneFragment oneFragment1 = (OneFragment) mFragments.get(position);
scrollableLayout.getHelper().setCurrentScrollableContainer(oneFragment1);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
当然给ViewPager 设置adapter的时候,需要设置默认第一个Fragment
到此基本完成了适配,但是实际情况可能需要置顶中间部分的导航栏,实现下拉刷新和上拉加载,所以可以去监听scrollableLayout的滑动事件去判断
scrollableLayout.setOnScrollListener(new ScrollableLayout.OnScrollListener() {
@Override
public void onScroll(int currentY, int maxY) {
//1481
int top = layoutTabCls.getTop();
LogUtils.I("onScroll", "top:" + top + "---currentY:" + currentY + "---maxY:" + maxY);
//设置中部导航栏的显示和隐藏,如果不理解这里,可以去另外搜索或者留言
if (currentY >= top) {
layoutTabClsTop.setVisibility(View.VISIBLE);
} else {
layoutTabClsTop.setVisibility(View.GONE);
}
//当scrollableLayout向上滑动时,屏蔽刷新控件的下拉刷新,回到顶部是再打开,记得这个界面需要屏蔽上拉加载的事件
if (currentY >0){
refreshLayout.setEnableRefresh(false);
}else {
refreshLayout.setEnableRefresh(true);
}
}
});
这样基本适配了下拉刷新的操作,至于上拉加载,可以到具体的那个Fragment里面去实现,记得屏蔽掉下拉刷新的事件,跟上一步配合使用,应该能解决问题