Android NestedScrollView悬浮固定顶部

这篇具有很好参考价值的文章主要介绍了Android NestedScrollView悬浮固定顶部。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

项目中有页面涉及到多个元素组 需要NestedScrollView包裹来上下滑动
接到需求 一些标题在滑动到顶部时需要置顶

我之前做过关于Android Behavior之ViewPager+Fragment+RecyclerView实现吸顶效果

大概就是这种效果 只不过这次是随意的组件 比如TextView或布局组件RelativeLayout
废话不多说来看下实现效果

动态效果图

关于标题title勘测信息这个View组件
在向下滑动时 滑动到屏幕的顶部 需要悬浮顶部 实现这种效果

Android NestedScrollView悬浮固定顶部,android,悬浮,ScrollView,android滑动,吸顶

代码实现

写一个自定义View继承NestedScrollView 需要实现构造
设置标记Tag STICKY_TAG = "sticky"

使用说明 凡是被NestedScrollView的子组件 只要在xml中标记sticky
就可以实现悬浮

public class StickyScrollView extends NestedScrollView {


    /**
     * 标记应粘贴并具有恒定图形的视图。例如TextViews、ImageViews等
     */
    public static final String STICKY_TAG = "sticky";

    /**
     * 应粘贴并具有非恒定图形的视图的标志。例如按钮、进度条等
     */
    public static final String FLAG_NONCONSTANT = "-nonconstant";

    /**
     * 具有非完全不透明的视图的标志
     */
    public static final String FLAG_HASTRANSPARANCY = "-hastransparancy";

    /**
     * 在粘贴视图下方向外窥视的阴影的默认高度。
     */
    private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp;

    private ArrayList<View> stickyViews;
    private View currentlyStickingView;
    private float stickyViewTopOffset;
    private int stickyViewLeftOffset;
    private boolean redirectTouchesToStickyView;
    private boolean clippingToPadding;
    private boolean clipToPaddingHasBeenSet;

    private int mShadowHeight;
    private Drawable mShadowDrawable;

    private final Runnable invalidateRunnable = new Runnable() {

        @Override
        public void run() {
            if (currentlyStickingView != null) {
                int l = getLeftForViewRelativeOnlyChild(currentlyStickingView);
                int t = getBottomForViewRelativeOnlyChild(currentlyStickingView);
                int r = getRightForViewRelativeOnlyChild(currentlyStickingView);
                int b = (int) (getScrollY() + (currentlyStickingView.getHeight() + stickyViewTopOffset));
                invalidate(l, t, r, b);
            }
            postDelayed(this, 16);
        }
    };

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

    public StickyScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.scrollViewStyle);
    }

    public StickyScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup();

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.StickyScrollView, defStyle, 0);

        final float density = context.getResources().getDisplayMetrics().density;
        int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);

        mShadowHeight = a.getDimensionPixelSize(
                R.styleable.StickyScrollView_stuckShadowHeight,
                defaultShadowHeightInPix);

        int shadowDrawableRes = a.getResourceId(
                R.styleable.StickyScrollView_stuckShadowDrawable, -1);

        if (shadowDrawableRes != -1) {
            mShadowDrawable = context.getResources().getDrawable(
                    shadowDrawableRes);
        }

        a.recycle();
    }

    /**
     * 设置阴影可绘制的高度(以像素为单位)。
     *
     * @param height
     */
    public void setShadowHeight(int height) {
        mShadowHeight = height;
    }


    public void setup() {
        stickyViews = new ArrayList<View>();
    }

    private int getLeftForViewRelativeOnlyChild(View v) {
        int left = v.getLeft();
        while (v.getParent() != getChildAt(0)) {
            v = (View) v.getParent();
            left += v.getLeft();
        }
        return left;
    }

    private int getTopForViewRelativeOnlyChild(View v) {
        int top = v.getTop();
        while (v.getParent() != getChildAt(0)) {
            v = (View) v.getParent();
            top += v.getTop();
        }
        return top;
    }

    private int getRightForViewRelativeOnlyChild(View v) {
        int right = v.getRight();
        while (v.getParent() != getChildAt(0)) {
            v = (View) v.getParent();
            right += v.getRight();
        }
        return right;
    }

    private int getBottomForViewRelativeOnlyChild(View v) {
        int bottom = v.getBottom();
        while (v.getParent() != getChildAt(0)) {
            v = (View) v.getParent();
            bottom += v.getBottom();
        }
        return bottom;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (!clipToPaddingHasBeenSet) {
            clippingToPadding = true;
        }
        notifyHierarchyChanged();
    }

    @Override
    public void setClipToPadding(boolean clipToPadding) {
        super.setClipToPadding(clipToPadding);
        clippingToPadding = clipToPadding;
        clipToPaddingHasBeenSet = true;
    }

    @Override
    public void addView(View child) {
        super.addView(child);
        findStickyViews(child);
    }

    @Override
    public void addView(View child, int index) {
        super.addView(child, index);
        findStickyViews(child);
    }

    @Override
    public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        findStickyViews(child);
    }

    @Override
    public void addView(View child, int width, int height) {
        super.addView(child, width, height);
        findStickyViews(child);
    }

    @Override
    public void addView(View child, android.view.ViewGroup.LayoutParams params) {
        super.addView(child, params);
        findStickyViews(child);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (currentlyStickingView != null) {
            canvas.save();
            canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() + stickyViewTopOffset + (clippingToPadding ? getPaddingTop() : 0));

            canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0),
                    getWidth() - stickyViewLeftOffset,
                    currentlyStickingView.getHeight() + mShadowHeight + 1);

            if (mShadowDrawable != null) {
                int left = 0;
                int right = currentlyStickingView.getWidth();
                int top = currentlyStickingView.getHeight();
                int bottom = currentlyStickingView.getHeight() + mShadowHeight;
                mShadowDrawable.setBounds(left, top, right, bottom);
                mShadowDrawable.draw(canvas);
            }

            canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth(), currentlyStickingView.getHeight());
            if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
                showView(currentlyStickingView);
                currentlyStickingView.draw(canvas);
                hideView(currentlyStickingView);
            } else {
                currentlyStickingView.draw(canvas);
            }
            canvas.restore();
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            redirectTouchesToStickyView = true;
        }

        if (redirectTouchesToStickyView) {
            redirectTouchesToStickyView = currentlyStickingView != null;
            if (redirectTouchesToStickyView) {
                redirectTouchesToStickyView =
                        ev.getY() <= (currentlyStickingView.getHeight() + stickyViewTopOffset) &&
                                ev.getX() >= getLeftForViewRelativeOnlyChild(currentlyStickingView) &&
                                ev.getX() <= getRightForViewRelativeOnlyChild(currentlyStickingView);
            }
        } else if (currentlyStickingView == null) {
            redirectTouchesToStickyView = false;
        }
        if (redirectTouchesToStickyView) {
            ev.offsetLocation(0, -1 * ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));
        }
        return super.dispatchTouchEvent(ev);
    }

    private boolean hasNotDoneActionDown = true;

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (redirectTouchesToStickyView) {
            ev.offsetLocation(0, ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));
        }

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            hasNotDoneActionDown = false;
        }

        if (hasNotDoneActionDown) {
            MotionEvent down = MotionEvent.obtain(ev);
            down.setAction(MotionEvent.ACTION_DOWN);
            super.onTouchEvent(down);
            hasNotDoneActionDown = false;
        }

        if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
            hasNotDoneActionDown = true;
        }

        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        doTheStickyThing();
    }

    private void doTheStickyThing() {
        View viewThatShouldStick = null;
        View approachingView = null;
        for (View v : stickyViews) {
            int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop());
            if (viewTop <= 0) {
                if (viewThatShouldStick == null || viewTop > (getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))) {
                    viewThatShouldStick = v;
                }
            } else {
                if (approachingView == null || viewTop < (getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))) {
                    approachingView = v;
                }
            }
        }
        if (viewThatShouldStick != null) {
            stickyViewTopOffset = approachingView == null ? 0 : Math.min(0, getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight());
            if (viewThatShouldStick != currentlyStickingView) {
                if (currentlyStickingView != null) {
                    stopStickingCurrentlyStickingView();
                }
                // only compute the left offset when we start sticking.
                stickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);
                startStickingView(viewThatShouldStick);
            }
        } else if (currentlyStickingView != null) {
            stopStickingCurrentlyStickingView();
        }
    }

    private void startStickingView(View viewThatShouldStick) {
        currentlyStickingView = viewThatShouldStick;
        if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
            hideView(currentlyStickingView);
        }
        if (((String) currentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)) {
            post(invalidateRunnable);
        }
    }

    private void stopStickingCurrentlyStickingView() {
        if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
            showView(currentlyStickingView);
        }
        currentlyStickingView = null;
        removeCallbacks(invalidateRunnable);
    }

    /**
     * 通知已在“视图”层次结构中的一个或多个视图中添加或删除粘性属性
     */
    public void notifyStickyAttributeChanged() {
        notifyHierarchyChanged();
    }

    private void notifyHierarchyChanged() {
        if (currentlyStickingView != null) {
            stopStickingCurrentlyStickingView();
        }
        stickyViews.clear();
        findStickyViews(getChildAt(0));
        doTheStickyThing();
        invalidate();
    }

    private void findStickyViews(View v) {
        if (v instanceof ViewGroup) {
            ViewGroup vg = (ViewGroup) v;
            for (int i = 0; i < vg.getChildCount(); i++) {
                String tag = getStringTagForView(vg.getChildAt(i));
                if (tag != null && tag.contains(STICKY_TAG)) {
                    stickyViews.add(vg.getChildAt(i));
                } else if (vg.getChildAt(i) instanceof ViewGroup) {
                    findStickyViews(vg.getChildAt(i));
                }
            }
        } else {
            String tag = (String) v.getTag();
            if (tag != null && tag.contains(STICKY_TAG)) {
                stickyViews.add(v);
            }
        }
    }

    private String getStringTagForView(View v) {
        Object tagObject = v.getTag();
        return String.valueOf(tagObject);
    }

    private void hideView(View v) {
        if (Build.VERSION.SDK_INT >= 11) {
            v.setAlpha(0);
        } else {
            AlphaAnimation anim = new AlphaAnimation(1, 0);
            anim.setDuration(0);
            anim.setFillAfter(true);
            v.startAnimation(anim);
        }
    }

    private void showView(View v) {
        if (Build.VERSION.SDK_INT >= 11) {
            v.setAlpha(1);
        } else {
            AlphaAnimation anim = new AlphaAnimation(0, 1);
            anim.setDuration(0);
            anim.setFillAfter(true);
            v.startAnimation(anim);
        }
    }

}

资源文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="StickyScrollView">
        <attr name="stuckShadowHeight" format="dimension" />
        <attr name="stuckShadowDrawable" format="reference" />
    </declare-styleable>
</resources>

布局文件使用

重点关注 该子组件标记了 android:tag="sticky" 所以在滑动时会悬浮顶部
具体参考代码

 private void findStickyViews(View v) {
        if (v instanceof ViewGroup) {
            ViewGroup vg = (ViewGroup) v;
            for (int i = 0; i < vg.getChildCount(); i++) {
                String tag = getStringTagForView(vg.getChildAt(i));
                if (tag != null && tag.contains(STICKY_TAG)) {
                    stickyViews.add(vg.getChildAt(i));
                } else if (vg.getChildAt(i) instanceof ViewGroup) {
                    findStickyViews(vg.getChildAt(i));
                }
            }
        } else {
            String tag = (String) v.getTag();
            if (tag != null && tag.contains(STICKY_TAG)) {
                stickyViews.add(v);
            }
        }
    }

重点关注 该子组件标记了 android:tag="sticky" 所以在滑动时会悬浮顶部
具体参考代码文章来源地址https://www.toymoban.com/news/detail-819509.html

 <RelativeLayout
                android:id="@+id/rl_survey"
                android:layout_width="match_parent"
                android:layout_height="@dimen/dp_40"
                android:layout_margin="@dimen/dp_5"
                android:background="@drawable/shape_rec_3c79f7_radius_10dp"
                android:focusable="true"
                android:focusableInTouchMode="true"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:padding="@dimen/dp_5"
                android:tag="sticky"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent">

                <TextView
                    android:id="@+id/tv_tip_survey"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="@dimen/dp_10"
                    android:text="勘测信息"
                    android:textColor="@color/white"
                    android:textSize="@dimen/sp_15" />

                <ImageView
                    android:id="@+id/iv_arrow_survey"
                    android:layout_width="@dimen/dp_14"
                    android:layout_height="@dimen/dp_14"
                    android:layout_alignParentEnd="true"
                    android:layout_marginEnd="@dimen/dp_10"
                    android:rotation="180"
                    android:src="@mipmap/ic_arrow_down_white" />

            </RelativeLayout>

到了这里,关于Android NestedScrollView悬浮固定顶部的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Android Studio】常用布局 --- 滚动视图ScrollView

    问题引入 :手机屏幕的显示空间有限,常常需要上下滑动或左右滑动才能拉出其余页面内容,可惜一般的布局节点 都不支持自行滚动,这时就要借助滚动视图了。与线性布局类似,滚动视图也分为垂直方向和水平方向 两类,其中垂直滚动视图名为ScrollView,水平滚动视图名为

    2023年04月11日
    浏览(42)
  • Android 项目必备(四十四)-->Android 实现悬浮窗

    悬浮窗是一种比较常见的需求。例如把视频通话界面缩小成一个悬浮窗,然后用户可以在其他界面上处理事情。 本文将讲解悬浮窗实现步骤、原理、实例代码等 1. WindowMananger 接口 Android 的界面绘制,都是通过 WindowMananger 的服务来实现的。那么,既然要实现一个能够在自身应

    2024年02月12日
    浏览(38)
  • android 悬浮窗 模拟微信通话返回桌面悬浮

    现有一款IM聊天需求,在通话页面点击缩小视图或者Home键返回桌面,点击悬浮窗回到通话页面这样一个需求。 权限 首先是权限的获取,请注意,在Android 8.0及以上版本中,需要申请悬浮窗权限(SYSTEM_ALERT_WINDOW)才能显示悬浮窗。你可以在应用启动时请求该权限,或者引导用

    2024年02月01日
    浏览(36)
  • Android——使用ScrollView实现滚动效果,当内容超出屏幕范围时自动滑动显示

    Android——使用ScrollView实现滚动效果,当内容超出屏幕范围时自动滑动显示 ScrollView是Android中常用的布局容器,用于在屏幕空间有限的情况下实现内容的滑动显示。当内容超出屏幕范围时,用户可以通过滑动屏幕来查看更多内容,提供了更好的用户体验。 在Android中,使用Sc

    2024年01月16日
    浏览(46)
  • 怎么实现android 全局悬浮窗

    要实现 Android 全局悬浮窗,可以按照以下步骤: 在 AndroidManifest.xml 文件中添加 SYSTEM_ALERT_WINDOW 权限: 创建一个 Service 类,并在其中创建一个 WindowManager 来管理悬浮窗: 在悬浮窗 View 的布局文件中添加需要展示的内容: 在 Activity 中启动 Service: 最后,记得在不需要展示悬浮

    2024年02月11日
    浏览(42)
  • Android 应用弹出悬浮窗

    Android开发者经常遇到应用想弹出悬浮窗的操作,而且有可能还想要高层级弹窗,就像ipone的浮标touch一样。android当然也有类似的悬浮图标,比如前些年我们的流量监控提醒。  这里我们忽略UI美学,简单记录一下: 1、基本使用 它的基本使用步骤是不会变的,只是有时候我们

    2024年02月16日
    浏览(36)
  • taro(小程序一样) ScrollView 滚动到底部或者顶部 再次设置scrollIntoView 无效

    产生这个bug的原因:当我们第一次滑倒底部点击A回到顶部成功,再次滑倒底部,再次点击A无法回到顶部,因为此时的scrollIntoView 对应的值还是A,需要将scrollIntoView对应的值清空,然后重新赋值A,即可解决 解决办法:滑动到顶部或者底部时,清空之前设置的scrollIntoId即可 完

    2024年02月11日
    浏览(44)
  • Android 去除默认的顶部导航栏

    在Android Studio中,我们新建项目运行之后,在应用顶部,会有一个默认顶部栏,很影响体验。那么我们怎样去掉他,有三种方法。 无需自定义style配置的,直接调用系统的就行了,会去掉所有页面的默认顶部栏 : 修改AndroidManifest.xml文件中的android:theme即可,将 DartActionBar修改

    2024年02月05日
    浏览(25)
  • 【Android入门到项目实战-- 11.1】—— 实现悬浮按钮

            悬浮按钮是悬浮于界面之上,滑动屏幕时按钮不会跟随屏幕移动,它是Design Support库中提供的一个控件,这个控件可以帮助我们轻松实现悬浮按钮效果。         下面实现一个发布的悬浮按钮。        由于需要用到floatingActionButton,所以先导入,可参考这篇文

    2024年02月13日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包