ViewPager跟随整个页面向上移动,并固定头部效果实现记录
2018-02-01 本文已影响0人
smallestt
主要实现ViewPager跟随整个页面一起向上滑动,并把头部固定在顶部,解决ViewPager里fragment的刷新操作与整体向上移动操作的冲突。具体实现步骤:
- 自定义LinearLayout,实现向上滑动并固定的功能
StickyNavLayout.java
public class StickyNavLayout extends LinearLayout implements NestedScrollingParent {
private NestedScrollingParentHelper parentHelper = new NestedScrollingParentHelper(this);
private View mTop;
private View mNav;
private ViewPager mViewPager;
private int mTopViewHeight;
private OverScroller mScroller;
private boolean showTop = true;
public OnLayoutScrollListener scroll = null;
/**
* 向外部暴露接口,当头部view消失后和显示时的监听
* */
public interface OnLayoutScrollListener {
abstract void isTopShow(boolean isTopShow);
}
public void setOnListener(Fragment fragment) {
scroll = (OnLayoutScrollListener) fragment;
}
public StickyNavLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(LinearLayout.VERTICAL);
mScroller = new OverScroller(context);
}
@Override
protected void onFinishInflate() {
mTop = findViewById(R.id.id_stickynavlayout_topview);
mNav = findViewById(R.id.id_stickynavlayout_indicator);
mViewPager = (ViewPager) findViewById(R.id.id_stickynavlayout_viewpager);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mTopViewHeight = mTop.getMeasuredHeight();
//上面测量的结果是viewPager的高度只能占满父控件的剩余空间
//重新设置viewPager的高度
ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams();
layoutParams.height = getMeasuredHeight() - mNav.getMeasuredHeight();
mViewPager.setLayoutParams(layoutParams);
}
@Override
public void scrollTo(int x, int y) {
//限制滚动范围
if (y < 0) {
y = 0;
}
if (y > mTopViewHeight) {
y = mTopViewHeight;
}
super.scrollTo(x, y);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
invalidate();
}
}
public void fling(int velocityY) {
mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, mTopViewHeight);
invalidate();
}
//实现NestedScrollParent接口-------------------------------------------------------------------------
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return true;
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
parentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
}
@Override
public void onStopNestedScroll(View target) {
parentHelper.onStopNestedScroll(target);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
boolean hiddenTop = dy > 0 && getScrollY() < mTopViewHeight;
showTop = dy < 0 && getScrollY() >= 0 && !ViewCompat.canScrollVertically(target, -1);
if (getScrollY() > 0)
scroll.isTopShow(false);//头部view处于隐藏状态
else scroll.isTopShow(true);//头部view处于显示状态
if (hiddenTop || showTop) {
scrollBy(0, dy);
consumed[1] = dy;
}
}
//boolean consumed:子view是否消耗了fling
//返回值:自己是否消耗了fling。可见,要消耗只能全部消耗
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
Log.e("onNestedFling", "called");
return false;
}
//返回值:自己是否消耗了fling。可见,要消耗只能全部消耗
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
Log.e("onNestedPreFling", "called");
if (getScrollY() < mTopViewHeight) {
fling((int) velocityY);
return true;
} else {
return false;
}
}
@Override
public int getNestedScrollAxes() {
return parentHelper.getNestedScrollAxes();
}
- value下创建id资源文件
ids_sticky_nav_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="id_stickynavlayout_topview" type="id"/>
<item name="id_stickynavlayout_viewpager" type="id"/>
<item name="id_stickynavlayout_indicator" type="id"/>
<item name="id_stickynavlayout_innerscrollview" type="id"/>
</resources>
注:因为要在StickyNavLayout里计算头部view的高度,所以在xml布局里头部控件的id也需引用ids_sticky_nav_layout.xml里的资源。
- xml布局里引入自定义LinearLayout
<com.nongji.ah.custom.StickyNavLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical">
<RelativeLayout
android:id="@id/id_stickynavlayout_topview"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!--头部view-->
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_45">
<!--切换ViewPager每个Item的tab导航条-->
<com.viewpagerindicator.TabPageIndicator
android:id="@id/id_stickynavlayout_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/dp_15"
android:background="@color/white"
android:visibility="gone" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/dp_1"
android:background="@color/e0"></View>
<!--需要跟随主页面一起移动的ViewPager-->
<android.support.v4.view.ViewPager
android:id="@id/id_stickynavlayout_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.nongji.ah.custom.StickyNavLayout>
注:TabPageIndicator的实现方法在下一篇的记录。
4.解决ViewPager子Fragment下拉刷新和下滑ViewPager显示出头部view这两个操作冲突问题。
- 在StickNavLayout里定义OnLayoutScrollListener接口,当ViewPager头部View显示或者隐藏时,实现isTopShow方法,这样方便ViewPager子Fragment通过isTopShow方法判断是否要刷新(当TopView显示时执行刷新操作,当TopView隐藏时,禁掉刷新操作而是执行主View的滑动让TopView显示的操作)。
/**
* 暴露接口供外部操作
* */
public interface OnLayoutScrollListener {
abstract void isTopShow(boolean isTopShow);
}
/**
* 初始化
* */
public void setOnListener(Fragment fragment) {
scroll = (OnLayoutScrollListener) fragment;
}
在StickyNavLayout的onNestedPreScroll方法里判断TopView的状态
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
boolean hiddenTop = dy > 0 && getScrollY() < mTopViewHeight;
showTop = dy < 0 && getScrollY() >= 0 && !ViewCompat.canScrollVertically(target, -1);
if (getScrollY() > 0)
scroll.isTopShow(false);//头部view处于隐藏状态
else scroll.isTopShow(true);//头部view处于显示状态
if (hiddenTop || showTop) {
scrollBy(0, dy);
consumed[1] = dy;
}
}
- 在ViewPager操作类里实现StickyNavLayout.OnLayoutScrollListener接口实现isTopShow方法
@Override
public void isTopShow(boolean isTopShow) {
isShowTop = isTopShow;
//获取当前显示的Fragment
pagerAdapter.getCurrentFragment().refresh(isTopShow);
}
在Fragment类里
/*是否可以刷新数据*/
public void refresh(boolean isShowTop) {
if (isShowTop)
mRefreshLayout.setPullDownRefreshEnable(true);//头部view处于显示状态可刷新
else mRefreshLayout.setPullDownRefreshEnable(false);//反之。。。
}
5.解决ViewPager切选项卡时,Fragment里列表的位置紊乱问题,实现目标:当切换ViewPager选项卡时,头部View处于显示状态时,Fragment里列表应该位于头部。
/**
* 切选项卡时
*/
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
public void onPageSelected(int arg0) {
// 获取当前显示着的Fragment,当TopView处于显示时,让列表滚到头部
FragmentStatePagerAdapter f = (FragmentStatePagerAdapter) pager.getAdapter();
CommunityFragment fragment = (CommunityFragment) f.instantiateItem(pager, arg0);
fragment.scrollTo(isShowTop);
fragment.refresh(isShowTop);
}
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
public void onPageScrollStateChanged(int arg0) {
}
});
Fragment里:
public void scrollTo(boolean isShowTop) {
if (isShowTop)
mRvNews.scrollToPosition(0);
}