Android ScrollView+ViewPager 固定頂部控件,自動吸頂效果

FixedTabScrollView

FixedTabScrollView是一個scrollView+ViewPager 并實(shí)現(xiàn)自動頂吸吵护,固定頂部控件列赎,動態(tài)計算Viewpager控件高度的 一個解決方式的Demo (這個Demo應(yīng)該可以給大家提供一種解決問題的思路,希望能幫助到大家骇窍,歡迎Star~~~)

點(diǎn)擊進(jìn)入項目地址

實(shí)際效果:

xiaoguo.gif

需求和問題:

在實(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添加到原父容器中刽漂。

1.png

我們來修改一下布局:

<?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地址下載跑一遍看看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贬芥,隨后出現(xiàn)的幾起案子吐辙,更是在濱河造成了極大的恐慌,老刑警劉巖蘸劈,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昏苏,死亡現(xiàn)場離奇詭異,居然都是意外死亡威沫,警方通過查閱死者的電腦和手機(jī)贤惯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壹甥,“玉大人救巷,你說我怎么就攤上這事【淠” “怎么了浦译?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵棒假,是天一觀的道長。 經(jīng)常有香客問我精盅,道長帽哑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任叹俏,我火速辦了婚禮妻枕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粘驰。我一直安慰自己屡谐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布蝌数。 她就那樣靜靜地躺著愕掏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪顶伞。 梳的紋絲不亂的頭發(fā)上饵撑,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音唆貌,去河邊找鬼滑潘。 笑死,一個胖子當(dāng)著我的面吹牛锨咙,可吹牛的內(nèi)容都是我干的语卤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蓖租,長吁一口氣:“原來是場噩夢啊……” “哼粱侣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蓖宦,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎油猫,沒想到半個月后稠茂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡情妖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年睬关,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毡证。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡电爹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出料睛,到底是詐尸還是另有隱情丐箩,我是刑警寧澤摇邦,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站屎勘,受9級特大地震影響施籍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜概漱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一丑慎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓤摧,春花似錦竿裂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至产喉,卻和暖如春捂掰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曾沈。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工这嚣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人塞俱。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓姐帚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親障涯。 傳聞我的和親對象是個殘疾皇子罐旗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 文/6號院長 世界萬千,總有遺憾唯蝶。 只不過有些遺憾可以一笑而過九秀,有些遺憾則懊惱終生。 每日為了面包匆忙粘我,或許咙崎,我們...
    山雞Samson閱讀 272評論 0 1
  • 幾天之后院促,我去了醫(yī)院固棚。因為嘔吐不止计寇,是周燁離送的我。 “醫(yī)生告訴我匙姜,我懷孕了畅厢。” 很狗血的劇情吧氮昧,但是確確實(shí)實(shí)發(fā)生...
    陌涼詞閱讀 174評論 0 0
  • 精力管理的課程第四天才感悟到此課程的魅力框杜。以《精力管理》這本書中的部分原文片段浦楣,包括力量訓(xùn)練、間歇性訓(xùn)練等相關(guān)...
    簡單的苿萊閱讀 222評論 0 0
  • 1 “讓一讓霸琴,讓我過去一下好嗎椒振?”在這擁擠的公交車?yán)铮缫褦D得水泄不通梧乘,只見一個穿著天藍(lán)色的校...
    阿念北閱讀 385評論 0 0