Android 实现锚点定位

Android 实现锚点定位

大家好,又见面了,我是你们的朋友全栈君。

原文链接:mp.weixin.qq.com/s/EYyTBtM9q…

相信做前端的都做过页面锚点定位的功能,通过<a href="#head"> 去设置页面内锚点定位跳转。
本篇文章就使用tablayoutscrollview来实现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);
    }

}

复制代码

布局文件里 tablayoutCustomScrollView,内容暂时使用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地址:github.com/taixiang/ta…

欢迎关注我的博客:blog.manjiexiang.cn/
更多精彩欢迎关注微信号:春风十里不如认识你

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/107407.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • jvm常量池和字符串常量池_常量池中的字符串是对象吗

    jvm常量池和字符串常量池_常量池中的字符串是对象吗JVM——字符串常量池详解引言在Java开发中不管是前后端交互的JSON串,还是数据库中的数据存储,我们常常需要使用到String类型的字符串。作为最常用也是最基础的引用数据类型,JVM为String提供了字符串常量池来提高性能,本篇文章我们一起从底层JVM中认识并学习字符串常量池的概念和设计原理。字符串常量池由来在日常开发过程中,字符串的创建是比较频繁的,而字符串的分配和其他对象的分配是类似的,需要耗费大量的时间和空间,从而影响程序的运行性能,所以作为最基础最常用的引用数据类型,Java设计者在

    2022年7月28日
    4
  • PHP在线客服系统平台源码(完全开源的网页在线客服系统)

    PHP在线客服系统平台源码(完全开源的网页在线客服系统)  在线客服系统是一个使用PHP、JavaScript和CSS开发的即时网页聊天咨询系统。该项目包含管理员和用户端。管理员端管理所有的管理,如编辑站点内容、管理提供者和预订,管理员在这个系统的管理中起着重要的作用。    在线客服系统源码及演示:zxkfym.top    对于用户部分,用户可以浏览主页、关于和服务。用户可以是顾客谁需要家庭服务或服务提供商提供家庭服务的人。为了注册为服务提供商,用户必须填写注册表格。然而,要将服务提供商作为客户预订,用户可以先搜索可用的服务提供商,然后再进行预订。该

    2022年7月19日
    70
  • 01_java语言基础

    01_java语言基础

    2021年5月24日
    158
  • drupal linux安装,在Debian 10(Buster) Linux服务器中安装drupal 8.8.0的说明

    drupal linux安装,在Debian 10(Buster) Linux服务器中安装drupal 8.8.0的说明按照本说明,你就可以成功的在Debian10(Buster)Linux服务器中安装好drupal8.8.0版本,已亲测能稳定运行。先决条件在开始安装之前,对安装的最低要求是:数据库服务器,如MySQL、MariaDB、PostgreSQL、Percona、SQLite等。Web服务器,如Nginx、Apache。PHP7.x,推荐>=7.2。至少1GB的磁盘空间。同时,要更新你的De…

    2022年7月20日
    15
  • 大数据分析应用领域有哪些[通俗易懂]

    大数据分析应用领域有哪些[通俗易懂]  软件和服务的大数据分析市场收入预计将从2018年的$42B增长到2027年的$103B,复合年增长率(CAGR)为10.48%。这就是为什么,大数据分析认证是业内最全神贯注的技能之一。在这个“大数据分析应用领域”文章中,我将带您进入各个行业领域,在这里我将解释大数据分析如何使它们发生革命性变化。  大数据分析应用  大数据分析应用程序的主要目标是通过分析大量数据来帮助公司做出更具信息量的业务决策。它可能包括Web服务器日志,Internet点击流数据,社交媒体内容和活动报告,来自客户电子邮

    2022年5月29日
    41
  • pycharm 常用快捷键_PyCharm快捷键

    pycharm 常用快捷键_PyCharm快捷键工欲善其事必先利其器,PyCharm是最popular的Python开发工具,它提供的功能非常强大,是构建大型项目的理想工具之一,如果能挖掘出里面实用技巧,能带来事半功倍的效果。我在Windows平台下的默认KeyMap设置,在Mac也是类似的。1、快速查找文件Ctrl+ECtrl+E可打开最近访问过的文件Ctrl+Shift+E打开最近编辑过的文件从Tab…

    2022年8月29日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号