天天看點

Android 實作錨點定位

原文連結: https://mp.weixin.qq.com/s/EYyTBtM9qCdmB9nlDEF-3w

相信做前端的都做過頁面錨點定位的功能,通過

<a href="#head">

去設定頁面内錨點定位跳轉。

本篇文章就使用

tablayout

scrollview

來實作android錨點定位的功能。

效果圖:

實作思路

1、監聽

scrollview

滑動到的位置,

tablayout

切換到對應标簽

2、

tablayout

各标簽點選,

scrollview

可滑動到對應區域

自定義scrollview

因為我們需要監聽到滑動過程中

scrollview

的滑動距離,自定義

scrollview

通過接口暴露滑動的距離。

public class CustomScrollView extends ScrollView {

    public Callbacks mCallbacks;

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

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

    public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setCallbacks(Callbacks callbacks) {
        this.mCallbacks = callbacks;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (mCallbacks != null) {
            mCallbacks.onScrollChanged(l, t, oldl, oldt);
        }
    }
    //定義接口用于回調
    public interface Callbacks {
        void onScrollChanged(int x, int y, int oldx, int oldy);
    }

}
           

布局檔案裡

tablayout

CustomScrollView

,内容暫時使用

LinearLayout

填充。

<?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">

    <android.support.design.widget.TabLayout
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabIndicatorColor="@color/colorPrimary"
        app:tabMode="scrollable"
        app:tabSelectedTextColor="@color/colorPrimary" />

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

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

        </LinearLayout>

    </com.tabscroll.CustomScrollView>

</LinearLayout>           

資料模拟

資料模拟,動态添加

scrollview

内的内容,這裡自定義了

AnchorView

當作每一塊的填充内容。

private String[] tabTxt = {"客廳", "卧室", "餐廳", "書房", "陽台", "兒童房"};
//内容塊view的集合
private List<AnchorView> anchorList = new ArrayList<>();
//判讀是否是scrollview主動引起的滑動,true-是,false-否,由tablayout引起的
private boolean isScroll;
//記錄上一次位置,防止在同一内容塊裡滑動 重複定位到tablayout
private int lastPos;

//模拟資料,填充scrollview
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);
}

//tablayout設定标簽
for (int i = 0; i < tabTxt.length; i++) {
    tabLayout.addTab(tabLayout.newTab().setText(tabTxt[i]));
}           

定義變量标志

isScroll

,用于判斷

scrollview

的滑動由誰引起的,避免通過點選

tabLayout

引起的scrollview滑動問題。

lastPos

,當

scrollview

在同一子產品中滑動時,則不再去調用

tabLayout.setScrollPosition

重新整理标簽。

自定義的

AnchorView

public class AnchorView extends LinearLayout {

    private TextView tvAnchor;
    private TextView tvContent;

    public AnchorView(Context context) {
        this(context, null);
    }

    public AnchorView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AnchorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.view_anchor, this, true);
        tvAnchor = view.findViewById(R.id.tv_anchor);
        tvContent = view.findViewById(R.id.tv_content);
        Random random = new Random();
        int r = random.nextInt(256);
        int g = random.nextInt(256);
        int b = random.nextInt(256);
        tvContent.setBackgroundColor(Color.rgb(r, g, b));
    }

    public void setAnchorTxt(String txt) {
        tvAnchor.setText(txt);
    }

    public void setContentTxt(String txt) {
        tvContent.setText(txt);
    }
}
           

實作

scrollview

的滑動監聽:

scrollView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //當由scrollview觸發時,isScroll 置true
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            isScroll = true;
        }
        return false;
    }
});

scrollView.setCallbacks(new CustomScrollView.Callbacks() {
    @Override
    public void onScrollChanged(int x, int y, int oldx, int oldy) {
        if (isScroll) {
            for (int i = tabTxt.length - 1; i >= 0; i--) {
                //根據滑動距離,對比各子產品距離父布局頂部的高度判斷
                if (y > anchorList.get(i).getTop() - 10) {
                    setScrollPos(i);
                    break;
                }
            }
        }
    }
});

//tablayout對應标簽的切換
private void setScrollPos(int newPos) {
    if (lastPos != newPos) {
        //該方法不會觸發tablayout 的onTabSelected 監聽
        tabLayout.setScrollPosition(newPos, 0, true);
    }
    lastPos = newPos;
}           

tabLayout

的點選切換:

tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        //點選标簽,使scrollview滑動,isScroll置false
        isScroll = false;
        int pos = tab.getPosition();
        int top = anchorList.get(pos).getTop();
        scrollView.smoothScrollTo(0, top);
    }

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

    }

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

    }
});           

至此效果出來了,但是

問題來了 可以看到當點選最後一項時,

scrollView

滑動到底部時并沒有呈現出我們想要的效果,希望滑到最後一個時,全屏隻有最後一塊内容顯示。

是以這裡需要處理下最後一個view的高度,當不滿全屏時,重新設定他的高度,通過計算讓其撐滿螢幕。

//監聽判斷最後一個子產品的高度,不滿一屏時讓最後一個子產品撐滿螢幕
private ViewTreeObserver.OnGlobalLayoutListener listener;

listener = new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int screenH = getScreenHeight();
        int statusBarH = getStatusBarHeight(MainActivity.this);
        int tabH = tabLayout.getHeight();
        //計算内容塊所在的高度,全屏高度-狀态欄高度-tablayout的高度-内容container的padding 16dp
        int lastH = screenH - statusBarH - tabH - 16 * 3;
        AnchorView lastView = anchorList.get(anchorList.size() - 1);
        //當最後一個view 高度小于内容塊高度時,設定其高度撐滿
        if (lastView.getHeight() < lastH) {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            params.height = lastH;
            lastView.setLayoutParams(params);
        }
        container.getViewTreeObserver().removeOnGlobalLayoutListener(listener);

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

這樣就達到了預期的效果了。

寫到這裡,tablayout + scrollview的錨點定位成型了,在實際項目中,我們還可以使用tablayout + recyclerview 來完成同樣的效果,後續的話會帶來這樣的文章。

這段時間自己在做一個小程式,包括資料爬取 + 背景 + 小程式的,可能要過段時間才能出來,主要是資料爬蟲那邊比較麻煩的...期待下!

詳細代碼見

github位址:

https://github.com/taixiang/tabScroll

歡迎關注我的部落格:

https://blog.manjiexiang.cn/

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

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

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

繼續閱讀