天天看點

Android 滑動定位+吸附懸停效果實作

在前兩篇文章中,分别介紹了tablayout+scrollview 和 tablayout+recyclerview 實作的滑動定位的功能,文章連結: Android 實作錨點定位 Android tabLayout+recyclerView實作錨點定位

仔細看的話,這種滑動定位的功能,還可以整體滑動,再加上頂部tablayout 吸附懸停的效果。

實作效果:

布局

這裡采用的是兩個 tablayout。

一個用于占位,位于原始位置,scrollview内部,随scrollview滾動;另一個則是在滑動過程中,不斷滑動,滑動到頂部時吸附在螢幕頂部,使用者實際操作的也是這個tablayout。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.tabscroll.CustomScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:background="#ccc"
                    android:gravity="center">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="這裡是頂部内容區域"
                        android:textSize="16sp" />

                </LinearLayout>

                <!--占位的tablayout-->
                <android.support.design.widget.TabLayout
                    android:id="@+id/tablayout_holder"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:background="#ffffff"
                    app:tabIndicatorColor="@color/colorPrimary"
                    app:tabMode="scrollable"
                    app:tabSelectedTextColor="@color/colorPrimary" />

                <LinearLayout
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="16dp" />

            </LinearLayout>


            <!--實際使用者操作的tablayout-->
            <android.support.design.widget.TabLayout
                android:id="@+id/tablayout_real"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#ffffff"
                android:visibility="invisible"
                app:tabIndicatorColor="@color/colorPrimary"
                app:tabMode="scrollable"
                app:tabSelectedTextColor="@color/colorPrimary" />
        </FrameLayout>


    </com.tabscroll.CustomScrollView>

</LinearLayout>           

實作

滑動定位的功能可以參考之前的文章,這裡主要是進行吸附懸停的效果。

資料初始化:

/**
 * 占位tablayout,用于滑動過程中去确定實際的tablayout的位置
 */
private TabLayout holderTabLayout;
/**
 * 實際操作的tablayout,
 */
private TabLayout realTabLayout;
private CustomScrollView scrollView;
private LinearLayout container;
private String[] tabTxt = {"客廳", "卧室", "餐廳", "書房", "陽台", "兒童房"};

private List<AnchorView> anchorList = new ArrayList<>();

//判讀是否是scrollview主動引起的滑動,true-是,false-否,由tablayout引起的
private boolean isScroll;
//記錄上一次位置,防止在同一内容塊裡滑動 重複定位到tablayout
private int lastPos = 0;
//監聽判斷最後一個子產品的高度,不滿一屏時讓最後一個子產品撐滿螢幕
private ViewTreeObserver.OnGlobalLayoutListener listener;

for (int i = 0; i < tabTxt.length; i++) {
    AnchorView anchorView = new AnchorView(this);
    anchorView.setAnchorTxt(tabTxt[i]);
    anchorView.setContentTxt(tabTxt[i]);
    anchorList.add(anchorView);
    container.addView(anchorView);
}
for (int i = 0; i < tabTxt.length; i++) {
    holderTabLayout.addTab(holderTabLayout.newTab().setText(tabTxt[i]));
    realTabLayout.addTab(realTabLayout.newTab().setText(tabTxt[i]));
}
           

一開始讓實際的tablayout 移動到占位的tablayout 處,覆寫占位的tablayout。

listener = new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        //計算讓最後一個view高度撐滿螢幕
        int screenH = getScreenHeight();
        int statusBarH = getStatusBarHeight(AliHomeMoreActivity.this);
        int tabH = holderTabLayout.getHeight();
        int lastH = screenH - statusBarH - tabH - 16 * 3;
        AnchorView anchorView = anchorList.get(anchorList.size() - 1);
        if (anchorView.getHeight() < lastH) {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            params.height = lastH;
            anchorView.setLayoutParams(params);
        }

        //一開始讓實際的tablayout 移動到 占位的tablayout處,覆寫占位的tablayout
        realTabLayout.setTranslationY(holderTabLayout.getTop());
        realTabLayout.setVisibility(View.VISIBLE);
        container.getViewTreeObserver().removeOnGlobalLayoutListener(listener);

    }
};
container.getViewTreeObserver().addOnGlobalLayoutListener(listener);

private int getScreenHeight() {
    return getResources().getDisplayMetrics().heightPixels;
}

public int getStatusBarHeight(Context context) {
    int result = 0;
    int resourceId = context.getResources()
            .getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = context.getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}           

scrollview滑動

主要在滑動過程這不斷監聽滑動的距離,再移動實際的tablayout ,當在螢幕内時,讓其一直覆寫在占位的tablayout 上,看上去是跟着scrollview 一起滑動的;當滑出螢幕時,實際的tablayout 不斷移動 使其相對螢幕靜止,看上去是吸附在螢幕頂部。

scrollView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            isScroll = true;
        }
        return false;
    }
});

//監聽scrollview滑動
scrollView.setCallbacks(new CustomScrollView.Callbacks() {
    @Override
    public void onScrollChanged(int x, int y, int oldx, int oldy) {
        //根據滑動的距離y(不斷變化的) 和 holderTabLayout距離父布局頂部的距離(這個距離是固定的)對比,
        //當y < holderTabLayout.getTop()時,holderTabLayout 仍在螢幕内,realTabLayout不斷移動holderTabLayout.getTop()距離,覆寫holderTabLayout
        //當y > holderTabLayout.getTop()時,holderTabLayout 移出,realTabLayout不斷移動y,相對的停留在頂部,看上去是靜止的
        int translation = Math.max(y, holderTabLayout.getTop());
        realTabLayout.setTranslationY(translation);

        if (isScroll) {
            for (int i = tabTxt.length - 1; i >= 0; i--) {
                //需要y減去頂部内容區域的高度(具體看項目的高度,這裡demo寫死的200dp)
                if (y - 200 * 3 > anchorList.get(i).getTop() - 10) {
                    setScrollPos(i);
                    break;
                }
            }
        }

    }
});

private void setScrollPos(int newPos) {
    if (lastPos != newPos) {
        realTabLayout.setScrollPosition(newPos, 0, true);
    }
    lastPos = newPos;
}           

tablayout點選切換

由于實際操作的是realtablayout ,是以這裡隻需要一直監聽該tablayout。

//實際的tablayout的點選切換
realTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        isScroll = false;
        int pos = tab.getPosition();
        int top = anchorList.get(pos).getTop();
        //同樣這裡滑動要加上頂部内容區域的高度(這裡寫死的高度)
        scrollView.smoothScrollTo(0, top + 200 * 3);
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {

    }
});
           

至此,滑動定位+頂部吸附懸停 的效果結束了。做完之後,再看這個效果,其實和 支付寶-首頁 更多 那個頁面裡的滑動效果一樣。

代碼與之前文章的在同一個git位址裡。

詳細代碼見

github位址:

https://github.com/taixiang/tabScroll

歡迎關注我的部落格:

https://blog.manjiexiang.cn/

更多精彩歡迎關注微信号:春風十裡不如認識你

有個「佛系碼農圈」,歡迎大家加入暢聊,開心就好!

過期了,可加我微信 tx467220125 拉你入群。

繼續閱讀