`

NestedScrollView滚动到顶部固定子View悬停挂靠粘在顶端

阅读更多
网上有一个StickyScrollView,称之为粘性ScrollView,比如一个垂直方向的布局,依次摆放几个子View,当某一个子View滚到到顶端时候要停靠在顶部,悬停在顶部的位置不动。这很像联系人的pinned,假设联系人按照Z分组后,若滚动到顶部,Z就悬停靠在顶端不动,下面到的联系人继续滚到。当用户下拉时候,又恢复正常。

StickyScrollView原先是继承自ScrollView,然而现在ScrollView已经被新型的NestedScrollView逐渐取代了,现在我把它改写成继承自NestedScrollView后,整理出来:

package zhangphil.test;
 
import java.util.ArrayList;
 
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.widget.NestedScrollView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
 
public class StickyScrollView extends NestedScrollView {
 
    /**
     * Tag for views that should stick and have constant drawing. e.g. TextViews, ImageViews etc
     */
    public static final String STICKY_TAG = "sticky";
 
    /**
     * Flag for views that should stick and have non-constant drawing. e.g. Buttons, ProgressBars etc
     */
    public static final String FLAG_NONCONSTANT = "-nonconstant";
 
    /**
     * Flag for views that have aren't fully opaque
     */
    public static final String FLAG_HASTRANSPARANCY = "-hastransparancy";
 
    /**
     * Default height of the shadow peeking out below the stuck view.
     */
    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();
    }
 
    /**
     * Sets the height of the shadow drawable in pixels.
     *
     * @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);
    }
 
    /**
     * Notify that the sticky attribute has been added or removed from one or more views in the View hierarchy
     */
    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);
        }
    }
}


同时需要在res/values/attr.xml加入定义的属性:
<?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>


使用起来很简单,在xml布局中把某一个子View增加一个属性:
android:tag="sticky"


即可实现该子View在NestedScrollView中,当滚动到顶部后悬停挂靠在顶部不动,直到下一个粘性子View滚动上来把它顶飞,或者用户下拉,拉出上面的View。如xml代码:
<?xml version="1.0" encoding="utf-8"?>
<zhangphil.test.StickyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
 
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_red_light"
            android:tag="sticky"
            android:text="姓名"
            android:textColor="@android:color/white" />
 
        <TextView
            android:layout_width="match_parent"
            android:layout_height="280dp"
            android:background="@android:color/holo_blue_bright"
            android:gravity="center"
            android:text="zhang phil"
            android:textColor="@android:color/white" />
 
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_red_light"
            android:tag="sticky"
            android:text="blog"
            android:textColor="@android:color/white" />
 
        <TextView
            android:layout_width="match_parent"
            android:layout_height="280dp"
            android:background="@android:color/holo_orange_light"
            android:gravity="center"
            android:text="zhang phil csdn blog"
            android:textColor="@android:color/white" />
 
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_red_light"
            android:tag="sticky"
            android:text="链接"
            android:textColor="@android:color/white" />
 
        <TextView
            android:layout_width="match_parent"
            android:layout_height="280dp"
            android:background="@android:color/holo_purple"
            android:gravity="center"
            android:text="http://blog.csdn.net/zhangphil"
            android:textColor="@android:color/white" />
    </LinearLayout>
</zhangphil.test.StickyScrollView>



ViewPager与NestedScrollView套嵌会引起冲突,滑动不了的问题
https://blog.csdn.net/wjr1949/article/details/72479583?utm_source=itdadao&utm_medium=referral
分享到:
评论

相关推荐

    微信小程序点击item使之滚动到屏幕中间位置

    想做类似猫眼电影选场次会自动滚动到屏幕中间的效果 如图是最终效果  2.分析 实现这种效果的方法有三种: 1.直接使用原生CSS+js; 2.使用swiper,一屏显示多个item,点击item会自动滚动到中间 3.使用微信的scroll-view...

    开关磁阻电机定子系统磁固耦合解析建模分析

    建立了开关磁阻电机定子系统磁固耦合振动微分方程;在考虑开关磁阻电机位移场与电磁场相互作用、相互影响的条件下,利用麦克斯韦张量法和磁路法,推导出开关磁阻电机定子系统所受径向电磁力;结合梁函数组合法解得定子...

    定子绕组故障-常见定子绕组故障分析及维修方案

    定子绕组后的电机在运行中出现各种报错问题,本文详细介绍了电动机的运转原理和方法,对电动机出现的短路故障进行处理,对于电动机的正常工作有十分重要的作用,对异步电动 机定子绕组匝间短路故障进行了数字仿真,并...

    UGUI循环滚动+滑动居中

    支持横向纵向循环滚动和滑动居中,支持任意锚点布局(没有锚点限制),3种居中效果(不回弹,回弹,双向回弹)Grid(布局)不依赖任何脚本可以单独使用,不锁定子物体随意排序。WrapCententy(循环+居中)

    易语言复制组件到指定子夹

    易语言复制组件到指定子夹源码,复制组件到指定子夹

    定子叠加机原理设计说明书

    工艺过程中工序多、流程慢、效率低、产品质量难以保证,而且由于叠压而成的矽钢片的八个焊缝不是同步焊接,在焊接过程中热变形不同,因此加工成的Z4电机定子质量良莠不齐,无法保证Z4电机各种稳定的态势特性。...

    ABB定子调压调试步骤 调试说明.doc

    ABB定子调压调试步骤 调试说明doc,ABB定子调压调试步骤 调试说明

    111_定子电流励磁分量_

    将定子电流励磁分量和转矩分量作为状态变量,通过矢量变换的基本控制方法,给出了M-T坐标系下的状态方程,实现了磁通和转矩的解耦控制,并且分析了电压空间矢量控制(SVPWM)的原理,

    MATLAB在双作用叶片泵定子曲线中的应用

    MATLAB在双作用叶片泵定子曲线中的应用,周玉芳,毛君,双作用叶片泵定子曲线对叶片泵的各项性能都有重要影响,高次曲线是一种各项性能都比较优越的曲线,但方程比较复杂,如何绘制曲线

    接地故障传递电压对发电机定子接地保护的影响

    高压大接地系统发生接地故障时,故障点的零序电压会通过升压变压器高低压绕组间耦合电容传递到发电机侧,从而形成发电机定子虚拟接地现象。通过计算2伊300MW电厂高压母线单相及两相接地故障的接地零序电压,采用近似...

    基于定子电流的转子轴承系统故障诊断

    研究表明:建立的机电耦合模型可以研究转子轴承系统在故障激励下定子电流的特性,通过激励频率的不同能够辨别转子轴承系统的故障。该模型的建立为改进转子系统动力学和故障诊断提供了新的理论依据。

    电子功用-定子冲片、定子铁芯、定子和电机

    行业资料-电子功用-定子冲片、定子铁芯、定子和电机

    汽车助力液压马达定子曲线

    汽车助力液压马达定子曲线,其中定子曲线包括了8种算法。示波器控件类,参照NI Labview中的波形控件进行编写的,功能十分强大,非常好用(在网上只能找到功能简单的波形控件,不适用,故自己写了一个),希望对其它...

    IIS域名绑定子目录系统v1.2开源免费版

    目前大多数能绑定子目录的主机管理系统都通过建立多个虚拟网站的方式来变相实现解析到子目录的方式,这样做的缺点是无法对系统资源进行整体共享,只能单独限制某个所谓的子目录的资源,同时在IIS里面建立过多的虚拟...

    感应电动机按定子磁场定向控制

    在矢量控制和直接转矩控制的基础上,取长补短,提出一种新型的感应电动机按定子 磁场定 向控制的方法,采用定子电阻压降补偿和 电流转矩分量的闭环控制,实现了定子磁链和 电 磁转矩的协调控制。实验结果表明,该...

    IIS域名绑定子目录系统无限子目录不用多开网站

    目前大多数能绑定子目录的主机管理系统都通过建立多个虚拟网站的方式来变相实现解析到子目录的方式,这样做的缺点是无法对系统资源进行整体共享,只能单独限制某个所谓的子目录的资源,同时在IIS里面建立过多的虚拟...

    易语言源码易语言复制组件到指定子夹源码.rar

    易语言源码易语言复制组件到指定子夹源码.rar

    双水内冷发电机定子绝缘水管漏水分析与措施

    双水内冷发电机定子绝缘水管是发电机定子的重要部件,其严密性及完好性是保证发电机长期正常运行的关键,其检修相当重要,一旦出现定子绝缘水管漏水,会造成停机停炉,甚至主机损坏,给兴能发电公司带来不可估量的经济损失...

Global site tag (gtag.js) - Google Analytics