FixedTabScrollView
FixedTabScrollView是一個scrollView+ViewPager 并實(shí)現(xiàn)自動頂吸吵护,固定頂部控件列赎,動態(tài)計算Viewpager控件高度的 一個解決方式的Demo (這個Demo應(yīng)該可以給大家提供一種解決問題的思路,希望能幫助到大家骇窍,歡迎Star~~~)
實(shí)際效果:
需求和問題:
在實(shí)際項目中我們常常會碰到這樣的需求瓜晤,scrollView包裹ViewPager,ViewPager包裹Fragment腹纳,
在scrollView向上滑動時痢掠,會把ViewPager的選擇Tab欄固定在頂部。
但是在scrollView包裹ViewPager是的高度計算問題也很讓人頭疼只估,而且如果突然有個需求志群,讓你想做一個像翻頁一樣的效果,展示出來就像ViewPager占了一頁蛔钙,這個應(yīng)該怎么做呢?
自定義ScrollView
既然是要在scrollView滑動時候做一些操作荠医,那么我們就需要監(jiān)聽scrollView的滑動事件吁脱,但是原生控件沒有將 onScrollChanged()對外開放,所以我們需要自定義一個ScrollView,并且將滑動事件暴露出去
public class HoldTabScrollView extends ScrollView {
private Scroller mScroller;
public HoldTabScrollView(Context context) {
this(context, null);
mScroller = new Scroller(context);
}
public HoldTabScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
mScroller = new Scroller(context);
}
public HoldTabScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScroller = new Scroller(context);
}
//調(diào)用此方法滾動到目標(biāo)位置 duration滾動時間
public void smoothScrollToSlow(int fx, int fy, int duration) {
int dx = fx - getScrollX();//mScroller.getFinalX(); 普通view使用這種方法
int dy = fy - getScrollY(); //mScroller.getFinalY();
smoothScrollBySlow(dx, dy, duration);
}
//調(diào)用此方法設(shè)置滾動的相對偏移
public void smoothScrollBySlow(int dx, int dy, int duration) {
//設(shè)置mScroller的滾動偏移量
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, duration);//scrollView使用的方法(因為可以觸摸拖動)
// mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, duration); //普通view使用的方法
invalidate();//這里必須調(diào)用invalidate()才能保證computeScroll()會被調(diào)用彬向,否則不一定會刷新界面兼贡,看不到滾動效果
}
@Override
public void computeScroll() {
//先判斷mScroller滾動是否完成
if (mScroller.computeScrollOffset()) {
//這里調(diào)用View的scrollTo()完成實(shí)際的滾動
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必須調(diào)用該方法,否則不一定能看到滾動效果
postInvalidate();
}
super.computeScroll();
}
private OnHoldTabScrollViewScrollChanged mOnObservableScrollViewScrollChanged;
public void setOnObservableScrollViewScrollChanged(OnHoldTabScrollViewScrollChanged mOnObservableScrollViewScrollChanged) {
this.mOnObservableScrollViewScrollChanged = mOnObservableScrollViewScrollChanged;
}
public interface OnHoldTabScrollViewScrollChanged {
void onObservableScrollViewScrollChanged(int l, int t, int oldl, int oldt);
}
/**
* @param l Current horizontal scroll origin. 當(dāng)前滑動的x軸距離
* @param t Current vertical scroll origin. 當(dāng)前滑動的y軸距離
* @param oldl Previous horizontal scroll origin. 上一次滑動的x軸距離
* @param oldt Previous vertical scroll origin. 上一次滑動的y軸距離
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnObservableScrollViewScrollChanged != null) {
mOnObservableScrollViewScrollChanged.onObservableScrollViewScrollChanged(l, t, oldl, oldt);
}
}
@Override
public void fling(int velocityY) {
// super.fling(velocityY / 1000);
}
}
這樣一來娃胆,在我們使用HoldTabScrollView的時候遍希,通過setOnObservableScrollViewScrollChanged可以設(shè)置一個滑動監(jiān)聽,以此來對其滑動過程中做一些操作里烦。
修改布局
固定View的原理就是在頂部創(chuàng)建一個同View高度相同的父容器凿蒜,在View滑動上去的時候,移除掉原來的View胁黑,并且將此View添加到頂部父容器中废封,,同理丧蘸,在View滑動下來的時候漂洋,移除,頂部父容器中的View,并將此View添加到原父容器中刽漂。
我們來修改一下布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.app.demo.HoldTabScrollView xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="700px"
android:background="#770077"
android:gravity="center"
android:text="頂部布局"
android:textColor="#fff" />
<RelativeLayout
android:id="@+id/rl_center"
android:layout_width="match_parent"
android:layout_height="100px">
<TextView
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="100px"
android:background="#009999"
android:gravity="center"
android:text="tablayout"
android:textColor="#fff" />
</RelativeLayout>
<com.app.demo.CustomScrollViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff" />
</LinearLayout>
</com.app.demo.HoldTabScrollView>
<RelativeLayout
android:id="@+id/rl_top"
android:layout_width="match_parent"
android:layout_height="100px">
</RelativeLayout>
</RelativeLayout>
解決ScrollView 嵌套ViewPager高度計算問題
可以看到布局中有一個CustomScrollViewPager 這個ViewPager可以動態(tài)的計算它所包裹的Fragment的高度演训,并將這個實(shí)時計算的高度來設(shè)置ViewPager
public class CustomScrollViewPager extends ViewPager {
private int current;
private int height = 0;
/**
* 保存position與對于的View
*/
private HashMap<Integer, View> mChildrenViews = new LinkedHashMap<Integer, View>();
private boolean scrollble = true;
public CustomScrollViewPager(Context context) {
super(context);
}
public CustomScrollViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mChildrenViews.size() > current) {
View child = mChildrenViews.get(current);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
height = child.getMeasuredHeight();
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void resetHeight(int current) {
this.current = current;
if (mChildrenViews.size() > current) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
if (layoutParams == null) {
layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height);
} else {
layoutParams.height = height;
}
setLayoutParams(layoutParams);
}
}
/**
* 保存position與對于的View
*/
public void setObjectForPosition(View view, int position) {
mChildrenViews.put(position, view);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!scrollble) {
return true;
}
return super.onTouchEvent(ev);
}
public boolean isScrollble() {
return scrollble;
}
public void setScrollble(boolean scrollble) {
this.scrollble = scrollble;
}
}
我們在創(chuàng)建ViewPager中的Fragment實(shí)例的時候需要通過構(gòu)造函數(shù)將這個ViewPager傳進(jìn)去
mFragments.add(new Fragment1(viewPager));
mFragments.add(new Fragment2(viewPager));
在Fragment中 調(diào)用↓來重新計算內(nèi)部高度并設(shè)置ViewPager(第二個參數(shù)標(biāo)識當(dāng)前fragment在ViewPager中的位置)
viewPager.setObjectForPosition(view,0);
計算需要被固定的控件的位置
因為我們在監(jiān)聽scrollView滾動的時候需要知道,滾動到哪個地方就移除原tab贝咙,添加新tab仇祭,所以我們需要知道原來的控件位置,
我們在onWindowFocusChanged方法中去獲取
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
//獲取HeaderView的高度颈畸,當(dāng)滑動大于等于這個高度的時候乌奇,需要把tabView移除當(dāng)前布局,放入到外層布局
mHeight = centerRl.getTop();
}
}
根據(jù)這個高度來寫滑動監(jiān)聽事件
@Override
public void onObservableScrollViewScrollChanged(int l, int t, int oldl, int oldt) {
if (t >= mHeight) {
if (tablayout.getParent() != topRl) {
centerRl.removeView(tablayout);
topRl.addView(tablayout);
}
} else {
if (tablayout.getParent() != centerRl) {
topRl.removeView(tablayout);
centerRl.addView(tablayout);
}
}
}
自動頂吸效果
自動頂吸的效果就是 用戶向上滑動一點(diǎn)距離眯娱,頁面自動滑動到scrollView固定的空間位置礁苗,完全展示viewPager中的內(nèi)容。
我們使用ScrollView的ScrollTo來實(shí)現(xiàn)徙缴,不過因為這個方法滾動速度太快试伙,我們需要對速度做一些處理并且禁用慣性滾動
對滾動速度的處理我們使用Scroller這個對象,在自定義ScrollView中:
//調(diào)用此方法滾動到目標(biāo)位置 duration滾動時間
public void smoothScrollToSlow(int fx, int fy, int duration) {
int dx = fx - getScrollX();//mScroller.getFinalX(); 普通view使用這種方法
int dy = fy - getScrollY(); //mScroller.getFinalY();
smoothScrollBySlow(dx, dy, duration);
}
//調(diào)用此方法設(shè)置滾動的相對偏移
public void smoothScrollBySlow(int dx, int dy, int duration) {
//設(shè)置mScroller的滾動偏移量
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, duration);//scrollView使用的方法(因為可以觸摸拖動)
// mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, duration); //普通view使用的方法
invalidate();//這里必須調(diào)用invalidate()才能保證computeScroll()會被調(diào)用于样,否則不一定會刷新界面疏叨,看不到滾動效果
}
禁用慣性滾動
@Override
public void fling(int velocityY) {
//此處不進(jìn)行任何操作就可以了
}
修改activity中滑動監(jiān)聽
@Override
public void onObservableScrollViewScrollChanged(int l, int t, int oldl, int oldt) {
if (t >= mHeight) {
if (tablayout.getParent() != topRl) {
centerRl.removeView(tablayout);
topRl.addView(tablayout);
canJump = false;
}
} else {
if (tablayout.getParent() != centerRl) {
topRl.removeView(tablayout);
centerRl.addView(tablayout);
canJump = true;
}
}
if (canJump && t >= oldt) {
scrollView.smoothScrollToSlow(0, mHeight, 300);
} else if (canJump && t < oldt) {
scrollView.smoothScrollToSlow(0, 0, 300);
}
}
我們添加了一個布爾類型的判斷標(biāo)識,如果為true的時候就開啟自動吸頂(ViewPager沒有占滿一頁的時候)穿剖,為false的時候關(guān)閉自動吸頂蚤蔓。
利用ScrollView中自定義的smoothScrollToSlow方法來滑動到指定位置,最后一個參數(shù)可以調(diào)節(jié)滑動速度糊余。
整套實(shí)現(xiàn)方式的關(guān)鍵代碼都在這里了秀又,具體的代碼歡迎去頂部github地址下載跑一遍看看