Android 滑动定位+吸附悬停效果实现

作者: 程序猿tx | 来源:发表于2018-08-05 19:34 被阅读86次

    在前两篇文章中,分别介绍了tablayout+scrollview 和 tablayout+recyclerview 实现的滑动定位的功能,文章链接:
    Android 实现锚点定位
    Android tabLayout+recyclerView实现锚点定位
    仔细看的话,这种滑动定位的功能,还可以整体滑动,再加上顶部tablayout 吸附悬停的效果。

    实现效果:

    image

    布局

    这里采用的是两个 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/
    更多精彩欢迎关注微信号:春风十里不如认识你

    image.png

    有个「佛系码农圈」,欢迎大家加入畅聊,开心就好!

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

    相关文章

      网友评论

        本文标题:Android 滑动定位+吸附悬停效果实现

        本文链接:https://www.haomeiwen.com/subject/fwxivftx.html