最近项目首页需要用到Banner + tab + ViewPager切换的效果,在思考实现的过程中我突然发现,这个效果与网易云音乐Android端的歌手资料页面十分相似,因此好好把玩了一下网易云音乐,然后模仿出了一个效果类似的页面,这里就将界面元素完全替换为模仿网易云音乐,作为一个demo拿来分享。
项目地址:网易云音乐歌手资料页面Demo
说一下实现思路:
页面分为三个部分,最上方是透明的自定义导航栏(Toolbar),下面是带有歌手图片和PagerSlidingTabStrip的Header,最下方是带有各个子页面的ViewPager。在滑动的过程中是不能够改变ViewPager里面滑动组件的高度的,不然会导致滑动距离判断出现异常,因此ViewPager实际上是占满整个屏幕的。为了保证上面的Header不会把列表项挡住,在各个Fragment中加入一个与Header高度一样的空View作为占位。这样基本上就完成了基本页面的样式。水平滑动基本上是没什么问题了。
接下来就要解决上下滑动时Header的伸缩距离与ViewPager内滑动元素的滚动同步的问题。
列表页面使用RecyclerView,各种控件组合的页面使用ScrollView,这些控件的滑动距离与Header伸缩的距离是1:1的,而滑动距离与Header变色实际上也是一次函数关系,因此只需要检测到RecyclerView和ScrollView的滑动距离,就可以根据这个值的变化来改变头部,RecyclerView可以通过addOnScrollChangedListener来监测滑动距离:
rcvGoodsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
scrolledX += dx;
scrolledY += dy;
if(HomeListFragment.this.isResumed()) {
doOnScrollChanged(scrolledX, scrolledY, dx, dy);
}
}
});
对于ScrollView则需要继承重写,并为其添加滑动距离监测的方法:
public class ObservableScrollView extends ScrollView {
public interface OnScrollChangedListener {
void onScrollChanged(ScrollView scrollView, int scrolledX, int scrolledY, int dx, int dy);
}
private int scrolledX;
private int scrolledY;
private OnScrollChangedListener listener;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setOnScrollChangedListener(OnScrollChangedListener listener) {
this.listener = listener;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
int dl = l - oldl;
int dt = t - oldt;
scrolledX += dl;
scrolledY += dt;
if(listener != null) {
listener.onScrollChanged(this, getScrollX(), getScrollY(), dl, dt);
}
}
}
/**
* 初始化滑动参数,k值
* */
private void initSlidingParams() {
int headerSize = getResources().getDimensionPixelOffset(R.dimen.home_header_size);
int navBarHeight = getResources().getDimensionPixelOffset(R.dimen.nav_bar_height);
int tabStripHeight = getResources().getDimensionPixelOffset(R.dimen.tabstrip_height);
slidingDistance = headerSize - navBarHeight - tabStripHeight;
Log.d("HomeFragment", "slidingDistance" + slidingDistance);
}
/**
* 根据页面滑动距离改变Header方法
* */
private void scrollChangeHeader(int scrolledY) {
if (scrolledY < 0) {
scrolledY = 0;
}
if (scrolledY < slidingDistance) {
rlNavBar.setBackgroundColor(Color.argb(scrolledY * 192 / slidingDistance, 0x00, 0x00, 0x00));
llHeader.setPadding(0, -scrolledY, 0, 0);
currScrollY = scrolledY;
} else {
rlNavBar.setBackgroundColor(Color.argb(192, 0x00, 0x00, 0x00));
llHeader.setPadding(0, -slidingDistance, 0, 0);
currScrollY = slidingDistance;
}
}
对于这个问题,我又看了一眼网易云音乐的页面,发现它的做法是在切换时把各个列表的滑动距离都设定为当前Header的滑动距离,so,我也采取了这样的方法
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
currentPosition = position;
displayFragments.get(position).setScrolledY(currScrollY);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
在Fragment基类中
public abstract void setScrolledY(int scrolledY);
@Override
public void setScrolledY(int scrolledY) {
if(rcvGoodsList != null) {
if (this.scrolledY >= scrolledY) {
int scrollDistahan'yorollBy(0, scrollDistance);
}
else {
rcvGoodsList.scrollBy(0, scrolledY);
}
}
}
@Override
public void setScrolledY(int scrolledY) {
if (osvHomeRecommend != null) {
osvHomeRecommend.scrollTo(0, scrolledY);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (listener != null && !listener.onInterceptTouchEvent(event)) {
return false;
}
return super.onInterceptTouchEvent(event);
}
设定监听器
srlRefresh.setOnInterceptTouchEventListener(new CusSwipeRefreshLayout.OnInterceptTouchEventListener() {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return currScrollY == 0;
}
});