1. 程式人生 > >Android TabLayout+ScrollView 實現仿html錨點

Android TabLayout+ScrollView 實現仿html錨點

概述

在瀏覽網頁的時候,如果網頁內容過長,新增網頁內部導航會增加使用者體驗,也就是新增錨點。
這裡是用 TabLayout+ScrollView 為頁面新增錨點,實現仿html頁面導航功能。

TabLayout+ScrollView 實現仿html錨點

先順一下思路,2點功能:

  1. 點選TabLayout條目的時候,對應區域滑動到當前展示位置
  2. 滑動ScrollView,對應的標籤變為選中狀態,並且移動到中間位置

這裡需要考慮一個問題:如果點選TabLayout條目,內容區域ScrollView發生改變,這種狀態下ScrollView滑動是不會導致TabLayout條目發生改變的。

知識點

1、TabLayout的選中監聽

 mTabLayout.setOnTabSelectedListener(new
TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { int position = tab.getPosition(); // 根據點選的位置,使ScrollView 滑動到對應區域 ...... } @Override public void onTabUnselected
(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } });

2、ScrollView 的滑動監聽

sv_bodyContainer.setScrollViewListener(new ScrollChangedScrollView.ScrollViewListener() {

            @Override
            public
void onScrollChanged(ScrollView scrollView, int x, int y, int oldx, int oldy) { // 根據滑動到的當前位置,改變TabLayout的選中位置 } @Override public void onScrollStop(boolean isStop) { } });

3、獲得ScrollView滑動距離

   int scrollY = scrollView.getScrollY();

4、TabLayout 滑動到指定位置

  mTabLayout.setScrollPosition(int position, float positionOffset, boolean updateSelectedText);

5、區分好兩個操作動作:1. ScrollView 滾動,改變TabLayout ;2. 點選TabLayout 標籤,ScrollView 發生滑動。避免當TabLayout 點選條目的時候,因ScrollView 發生滑動,同時又導致TabLayout改變的迴圈情況。可用一個標記位來標明當前的動作是由TabLayout 點選條目發起還是ScrollView 手動滑動發起。

6、如果ScrollView是在一個內容模組中滑動,要避免重複呼叫TabLyout的滑動。

程式碼實現

佈局檔案 activity_tab_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_tab_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.skx.tomike.activity.TabLayoutActivity">

    <android.support.design.widget.TabLayout
        android:id="@+id/anchor_tagContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#a930c3a6"
        app:tabIndicatorColor="@color/skx_323232"
        app:tabMode="scrollable"
        app:tabSelectedTextColor="@color/skx_323232"
        app:tabTextColor="@color/skx_878787" />

    <com.skx.tomike.customview.ScrollChangedScrollView
        android:id="@+id/anchor_bodyContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/anchor_tagContainer">

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

            <ImageView
                android:id="@+id/iv_image"
                android:layout_width="match_parent"
                android:layout_height="240dp"
                android:scaleType="fitXY"
                android:src="@drawable/image_07" />

            <TextView
                android:id="@+id/tv_1"
                android:layout_width="match_parent"
                android:layout_height="486dp"
                android:background="@color/skx_33ff4081"
                android:gravity="center"
                android:textSize="36sp" />

            <TextView
                android:id="@+id/tv_2"
                android:layout_width="match_parent"
                android:layout_height="420dp"
                android:background="@color/skx_2cb298"
                android:gravity="center"
                android:textSize="36sp" />

            <TextView
                android:id="@+id/tv_3"
                android:layout_width="match_parent"
                android:layout_height="630dp"
                android:background="#568463"
                android:gravity="center"
                android:textSize="36sp" />

            <TextView
                android:id="@+id/tv_4"
                android:layout_width="match_parent"
                android:layout_height="120dp"
                android:background="@color/skx_2c3e50"
                android:gravity="center"
                android:textSize="36sp" />

            <TextView
                android:id="@+id/tv_5"
                android:layout_width="match_parent"
                android:layout_height="420dp"
                android:background="@color/skx_4dbbcf"
                android:gravity="center"
                android:textSize="36sp" />

            <TextView
                android:id="@+id/tv_6"
                android:layout_width="match_parent"
                android:layout_height="320dp"
                android:background="@color/skx_007aff"
                android:gravity="center"
                android:textSize="36sp" />

            <TextView
                android:id="@+id/tv_7"
                android:layout_width="match_parent"
                android:layout_height="503dp"
                android:background="@color/skx_7f000000"
                android:gravity="center"
                android:textSize="36sp" />

            <TextView
                android:id="@+id/tv_8"
                android:layout_width="match_parent"
                android:layout_height="272dp"
                android:background="@color/skx_85f3f3f3"
                android:gravity="center"
                android:textSize="36sp" />

            <TextView
                android:id="@+id/tv_9"
                android:layout_width="match_parent"
                android:layout_height="338dp"
                android:background="@color/skx_959ea7"
                android:gravity="center"
                android:textSize="36sp" />

        </LinearLayout>
    </com.skx.tomike.customview.ScrollChangedScrollView>

</RelativeLayout>

Acitivity 程式碼,註釋得也算詳細了

public class TabLayoutActivity extends SkxBaseActivity {

    private TabLayout tab_tagContainer;
    private ScrollChangedScrollView sv_bodyContainer;
    private TextView tv_1;
    private TextView tv_2;
    private TextView tv_3;
    private TextView tv_4;
    private TextView tv_5;
    private TextView tv_6;
    private TextView tv_7;
    private TextView tv_8;
    private TextView tv_9;
    // 頭部導航標籤
    private String[] navigationTag = {"圖片", "啤酒", "飲料", "礦泉水", "瓜子", "花生", "八寶粥", "泡麵", "雞爪", "火腿腸"};
    /**
     * 是否是ScrollView主動動作
     * false:是ScrollView主動動作
     * true:是TabLayout 主動動作
     */
    private boolean tagFlag = false;
    /**
     * 用於切換內容模組,相應的改變導航標籤,表示當一個所處的位置
     */
    private int lastTagIndex = 0;
    /**
     * 用於在同一個內容模組內滑動,鎖定導航標籤,防止重複重新整理標籤
     * true: 鎖定
     * false ; 沒有鎖定
     */
    private boolean content2NavigateFlagInnerLock = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initializeView();
        refreshView();
        installListener();
    }

    @Override
    public void initializeView() {
        super.initializeView();
        setContentView(R.layout.activity_tab_layout);
        tab_tagContainer = (TabLayout) findViewById(R.id.anchor_tagContainer);
        sv_bodyContainer = (ScrollChangedScrollView) findViewById(R.id.anchor_bodyContainer);
        tv_1 = (TextView) findViewById(R.id.tv_1);
        tv_2 = (TextView) findViewById(R.id.tv_2);
        tv_3 = (TextView) findViewById(R.id.tv_3);
        tv_4 = (TextView) findViewById(R.id.tv_4);
        tv_5 = (TextView) findViewById(R.id.tv_5);
        tv_6 = (TextView) findViewById(R.id.tv_6);
        tv_7 = (TextView) findViewById(R.id.tv_7);
        tv_8 = (TextView) findViewById(R.id.tv_8);
        tv_9 = (TextView) findViewById(R.id.tv_9);
    }

    @Override
    public void refreshView() {
        super.refreshView();
        tv_1.setText(navigationTag[1]);
        tv_2.setText(navigationTag[2]);
        tv_3.setText(navigationTag[3]);
        tv_4.setText(navigationTag[4]);
        tv_5.setText(navigationTag[5]);
        tv_6.setText(navigationTag[6]);
        tv_7.setText(navigationTag[7]);
        tv_8.setText(navigationTag[8]);
        tv_9.setText(navigationTag[9]);

        // 新增頁內導航標籤
        for (String item : navigationTag) {
            tab_tagContainer.addTab(tab_tagContainer.newTab().setText(item));
        }
    }

    @Override
    public void installListener() {
        super.installListener();
        sv_bodyContainer.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //表明當前的動作是由 ScrollView 觸發和主導
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    tagFlag = true;
                }
                return false;
            }
        });
        sv_bodyContainer.setScrollViewListener(new ScrollChangedScrollView.ScrollViewListener() {

            @Override
            public void onScrollChanged(ScrollView scrollView, int x, int y, int oldx, int oldy) {
                scrollRefreshNavigationTag(scrollView);
            }

            @Override
            public void onScrollStop(boolean isStop) {
            }
        });
        tab_tagContainer.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                //表明當前的動作是由 TabLayout 觸發和主導
                tagFlag = false;
                // 根據點選的位置,使ScrollView 滑動到對應區域
                int position = tab.getPosition();
                // 計算點選的導航標籤所對應內容區域的高度
                int targetY = 0;
                switch (position) {
                    case 0:
                        break;
                    case 1:
                        targetY = tv_1.getTop();
                        break;
                    case 2:
                        targetY = tv_2.getTop();
                        break;
                    case 3:
                        targetY = tv_3.getTop();
                        break;
                    case 4:
                        targetY = tv_4.getTop();
                        break;
                    case 5:
                        targetY = tv_5.getTop();
                        break;
                    case 6:
                        targetY = tv_6.getTop();
                        break;
                    case 7:
                        targetY = tv_7.getTop();
                        break;
                    case 8:
                        targetY = tv_8.getTop();
                        break;
                    case 9:
                        targetY = tv_9.getTop();
                        break;
                    default:
                        break;
                }
                // 移動到對應的內容區域
                sv_bodyContainer.smoothScrollTo(0, targetY + 5);
            }

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

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

    /**
     * 內容區域滑動重新整理導航標籤
     *
     * @param scrollView 內容模組容器
     */
    private void scrollRefreshNavigationTag(ScrollView scrollView) {
        if (scrollView == null) {
            return;
        }
        // 獲得ScrollView滑動距離
        int scrollY = scrollView.getScrollY();
        // 確定ScrollView當前展示的頂部內容屬於哪個內容模組
        if (scrollY > tv_9.getTop()) {
            refreshContent2NavigationFlag(9);

        } else if (scrollY > tv_8.getTop()) {
            refreshContent2NavigationFlag(8);

        } else if (scrollY > tv_7.getTop()) {
            refreshContent2NavigationFlag(7);

        } else if (scrollY > tv_6.getTop()) {
            refreshContent2NavigationFlag(6);

        } else if (scrollY > tv_5.getTop()) {
            refreshContent2NavigationFlag(5);

        } else if (scrollY > tv_4.getTop()) {
            refreshContent2NavigationFlag(4);

        } else if (scrollY > tv_3.getTop()) {
            refreshContent2NavigationFlag(3);

        } else if (scrollY > tv_2.getTop()) {
            refreshContent2NavigationFlag(2);

        } else if (scrollY > tv_1.getTop()) {
            refreshContent2NavigationFlag(1);

        } else {
            refreshContent2NavigationFlag(0);
        }
    }

    /**
     * 重新整理標籤
     *
     * @param currentTagIndex 當前模組位置
     */
    private void refreshContent2NavigationFlag(int currentTagIndex) {
        // 上一個位置與當前位置不一致是,解鎖內部鎖,是導航可以發生變化
        if (lastTagIndex != currentTagIndex) {
            content2NavigateFlagInnerLock = false;
        }
        if (!content2NavigateFlagInnerLock) {
            // 鎖定內部鎖
            content2NavigateFlagInnerLock = true;
            // 動作是由ScrollView觸發主導的情況下,導航標籤才可以滾動選中
            if (tagFlag) {
                tab_tagContainer.setScrollPosition(currentTagIndex, 0, true);
            }
        }
        lastTagIndex = currentTagIndex;
    }
}

滑動監聽的ScrollView

監聽ScrollView 的滑動狀態,滑動停止。

package com.skx.tomike.customview;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;

/**
 * @author shiguotao
 *         <p>
 *         滑動監聽的ScrollView
 */
public class ScrollChangedScrollView extends ScrollView {

    private ScrollViewListener scrollViewListener = null;
    private int handlerWhatId = 65984;
    private int timeInterval = 20;
    private int lastY = 0;
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == handlerWhatId) {
                if (lastY == getScrollY()) {
                    if (scrollViewListener != null) {
                        scrollViewListener.onScrollStop(true);
                    }
                } else {
                    if (scrollViewListener != null) {
                        scrollViewListener.onScrollStop(false);
                    }
                    handler.sendMessageDelayed(handler.obtainMessage(handlerWhatId, this), timeInterval);
                    lastY = getScrollY();
                }
            }
        }
    };

    public ScrollChangedScrollView(Context context) {
        super(context);
    }

    public ScrollChangedScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ScrollChangedScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
        super.onScrollChanged(x, y, oldx, oldy);
        if (scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
        }
    }

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    public interface ScrollViewListener {
        /**
         * 滑動監聽
         *
         * @param scrollView ScrollView控制元件
         * @param x          x軸座標
         * @param y          y軸座標
         * @param oldx       上一個x軸座標
         * @param oldy       上一個y軸座標
         */
        void onScrollChanged(ScrollView scrollView, int x, int y, int oldx, int oldy);

        /**
         * 是否滑動停止
         *
         * @param isScrollStop true:滑動停止;false:未滑動停止
         */
        void onScrollStop(boolean isScrollStop);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            handler.sendMessageDelayed(handler.obtainMessage(handlerWhatId, this), timeInterval);
        }
        return super.onTouchEvent(ev);
    }
}