炫酷的滑动效果实现

1、先看效果图

二、实现原理

在网上搜的时候也发现了这个案例,列出出处,表示尊重

https://blog.csdn.net/qq_34709056/article/details/72765273

通过自定义ScrollView  然后在通过自定义LinearLayout ,然后去修改其中的参数,然后在添加平移、缩放或者透明度渐变效果就能实现

三、实现

1、首先继承ScrollView ,并重写  onScrollChanged方法

public class DiscrollView extends ScrollView {

    public DiscrollView(@NonNull Context context) {
        super(context);
    }

    public DiscrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

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

    private DiscrollViewContent mContent;

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //first first take all the parent height
        View first = mContent.getChildAt(0);
        if (first != null) {
            first.getLayoutParams().height = getHeight();
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() != 1) {
            throw new IllegalStateException("Discrollview must host one child.");
        }

        View content = getChildAt(0);
        if (!(content instanceof DiscrollViewContent)) {
            throw new IllegalStateException("Discrollview must host a DiscrollViewContent.");
        }
        mContent = (DiscrollViewContent) content;
        if (mContent.getChildCount() < 2) {
            throw new IllegalStateException("Discrollview must have at least 2 children.");
        }
    }

    private int getAbsoluteBottom() {
        View last = getChildAt(getChildCount() - 1);
        if (last == null) {
            return 0;
        }
        return last.getBottom();
    }

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


        int scrollViewHeight = getHeight();
        int scrollViewBottom = getAbsoluteBottom();
        int scrollViewHalfHeight = scrollViewHeight / 2;

        //判断当前控件,然后判断是不是有我们自定义的控件
        for (int index = 1; index < mContent.getChildCount(); index++) {
            View child = mContent.getChildAt(index);
            if (!(child instanceof DiscrollvableInterface)) {
                //不是跳出当前循环,进入下一循环
                continue;
            }
            DiscrollvableInterface discrollvable = (DiscrollvableInterface) child;
            int discrollvableTop = child.getTop();
            int discrollvableHeight = child.getHeight();
            int discrollvableAbsoluteTop = discrollvableTop - top;


            if (scrollViewBottom - child.getBottom() < discrollvableHeight + scrollViewHalfHeight) {

                if (discrollvableAbsoluteTop <= scrollViewHeight) {
                    int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;
                    discrollvable.onDiscrollview(clamp(visibleGap / (float) discrollvableHeight, 0.0f, 1.0f));
                } else {
                    discrollvable.onResetDiscrollview();
                }
            } else {
                if (discrollvableAbsoluteTop <= scrollViewHalfHeight) {
                    int visibleGap = scrollViewHalfHeight - discrollvableAbsoluteTop;
                    discrollvable.onDiscrollview(clamp(visibleGap / (float) discrollvableHeight, 0.0f, 1.0f));
                } else {
                    discrollvable.onResetDiscrollview();
                }
            }
        }
    }

    private float clamp(float value, float max, float min) {
        return Math.max(Math.min(value, min), max);
    }

}

2、因为我们自定义的属性,在原来xml中是没有的,所以就必须的让该属性去得到原来的值,并且在LayoutParams中构造我们有的带了自定义属性的Parmas,因为在获取xml的属性中通过源码得知 generateLayoutParams很关键,在这里可以得到xml中所有的参数,因此,我们可以重写该方法,并且组合自己需要的Parmas

 @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        //得到xml里边过来的参数
        return new MyLayoutParams(getContext(), attrs);
    }
 public static class MyLayoutParams extends LinearLayout.LayoutParams {
        private int mDiscrollviewFromBgColor;
        private int mDiscrollviewToBgColor;
        private float mDiscrollviewThreshold;
        public boolean mDiscrollviewAlpha;
        public boolean mDiscrollviewScaleX;
        public boolean mDiscrollviewScaleY;
        private int mDiscrollviewTranslation;

        public MyLayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
            try {
                mDiscrollviewAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_alpha, false);
                mDiscrollviewScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_scaleX, false);
                mDiscrollviewScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_scaleY, false);
                mDiscrollviewTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollview_translation, -1);
                mDiscrollviewThreshold = a.getFloat(R.styleable.DiscrollView_LayoutParams_discrollview_threshold, 0.0f);
                mDiscrollviewFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollview_fromBgColor, -1);
                mDiscrollviewToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollview_toBgColor, -1);
            } finally {
                a.recycle();
            }
        }

        public MyLayoutParams(int width, int height) {
            super(width, height);
        }
    }

通过查看加载源码知道,在加载前会通过 checkLayoutParams,去检查参数

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (mTransition != null) {
            // Don't prevent other add transitions from completing, but cancel remove
            // transitions to let them complete the process before we add to the container
            mTransition.cancel(LayoutTransition.DISAPPEARING);
        }

        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }

        if (mTransition != null) {
            mTransition.addChild(this, child);
        }

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }
 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return  p != null;
    }

所以我们在自定义控件中的判断是不是属于自己的参数

   @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof MyLayoutParams;
    }

所以最后包裹的代码完整的是

public class DiscrollViewContent extends LinearLayout {
    public DiscrollViewContent(Context context) {
        super(context);
        setOrientation(VERTICAL);
    }

    public DiscrollViewContent(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(VERTICAL);
    }

    public DiscrollViewContent(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setOrientation(VERTICAL);
    }


    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        //得到xml里边过来的参数
        return new MyLayoutParams(getContext(), attrs);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof MyLayoutParams;
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
        return new MyLayoutParams(lp.width, lp.height);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        //从child里边拿到自定义属性,传递到discrollvableView里边
        MyLayoutParams lp = (MyLayoutParams) params;
        if (!isDiscrollvable(lp)) {
            //判断该view是否穿了自定义属性,不是就不需要
            super.addView(child, index, params);
        } else {
            DiscrollvableView discrollvableChild = new DiscrollvableView(getContext());
            discrollvableChild.setDiscrollviewAlpha(lp.mDiscrollviewAlpha);
            discrollvableChild.setDiscrollviewTranslation(lp.mDiscrollviewTranslation);
            discrollvableChild.setDiscrollviewScaleX(lp.mDiscrollviewScaleX);
            discrollvableChild.setDiscrollviewScaleY(lp.mDiscrollviewScaleY);
            discrollvableChild.setDiscrollviewThreshold(lp.mDiscrollviewThreshold);
            discrollvableChild.setDiscrollviewFromBgColor(lp.mDiscrollviewFromBgColor);
            discrollvableChild.setDiscrollviewToBgColor(lp.mDiscrollviewToBgColor);
            discrollvableChild.addView(child);
            super.addView(discrollvableChild, index, params);
        }
    }

    private boolean isDiscrollvable(MyLayoutParams lp) {
        return lp.mDiscrollviewAlpha ||
                lp.mDiscrollviewTranslation != -1 ||
                lp.mDiscrollviewScaleX ||
                lp.mDiscrollviewScaleY ||
                (lp.mDiscrollviewFromBgColor != -1 && lp.mDiscrollviewToBgColor != -1);
    }

    public static class MyLayoutParams extends LinearLayout.LayoutParams {
        private int mDiscrollviewFromBgColor;
        private int mDiscrollviewToBgColor;
        private float mDiscrollviewThreshold;
        public boolean mDiscrollviewAlpha;
        public boolean mDiscrollviewScaleX;
        public boolean mDiscrollviewScaleY;
        private int mDiscrollviewTranslation;

        public MyLayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
            try {
                mDiscrollviewAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_alpha, false);
                mDiscrollviewScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_scaleX, false);
                mDiscrollviewScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_scaleY, false);
                mDiscrollviewTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollview_translation, -1);
                mDiscrollviewThreshold = a.getFloat(R.styleable.DiscrollView_LayoutParams_discrollview_threshold, 0.0f);
                mDiscrollviewFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollview_fromBgColor, -1);
                mDiscrollviewToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollview_toBgColor, -1);
            } finally {
                a.recycle();
            }
        }

        public MyLayoutParams(int width, int height) {
            super(width, height);
        }
    }
}

3、得到包裹参数,然后实现对应的动画

先定义接口,拿到滑动返回的值

public interface DiscrollvableInterface {
    void onDiscrollview(float ratio);

    void onResetDiscrollview();
}

然后去实现

public class DiscrollvableView extends FrameLayout implements DiscrollvableInterface {
    public DiscrollvableView(@NonNull Context context) {
        super(context);
    }

    public DiscrollvableView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

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

    public DiscrollvableView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    private static final int TRANSLATION_FROM_TOP = 0x01;
    private static final int TRANSLATION_FROM_BOTTOM = 0x02;
    private static final int TRANSLATION_FROM_LEFT = 0x04;
    ;
    private static final int TRANSLATION_FROM_RIGHT = 0x08;

    //颜色估值器
    private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();

    private float mDiscrollviewThreshold;
    private int mDiscrollviewFromBgColor;//背景颜色变化开始值
    private int mDiscrollviewToBgColor;//背景颜色变化结束值
    private boolean mDiscrollviewAlpha;//是否需要透明度动画
    private int mDiscrollviewTranslation;//平移值
    private boolean mDiscrollviewScaleX;//是否需要x轴方向缩放
    private boolean mDiscrollviewScaleY;//是否需要y轴方向缩放

    private int mWidth;//宽度
    private int mHeight;//本view的高度

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        onResetDiscrollview();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    public void setDiscrollviewTranslation(int discrollviewTranslation) {
        this.mDiscrollviewTranslation = discrollviewTranslation;
        Log.i("wxf","mDiscrollviewTranslation:"+mDiscrollviewTranslation);
        if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_BOTTOM) && isDiscrollviewTranslationFrom(TRANSLATION_FROM_TOP)) {
            throw new IllegalArgumentException("cannot translate from bottom and top");
        }
        if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_LEFT) && isDiscrollviewTranslationFrom(TRANSLATION_FROM_RIGHT)) {
            throw new IllegalArgumentException("cannot translate from left and right");
        }
    }

    public void setDiscrollviewThreshold(float discrollviewThreshold) {
        if (discrollviewThreshold < 0.0f || discrollviewThreshold > 1.0f) {
            throw new IllegalArgumentException("threshold must be >= 0.0f and <= 1.0f");
        }
        this.mDiscrollviewThreshold = discrollviewThreshold;
    }

    public void setDiscrollviewFromBgColor(int discrollviewFromBgColor) {
        mDiscrollviewFromBgColor = discrollviewFromBgColor;
    }

    public void setDiscrollviewToBgColor(int discrollviewToBgColor) {
        mDiscrollviewToBgColor = discrollviewToBgColor;
    }

    public void setDiscrollviewAlpha(boolean discrollviewAlpha) {
        mDiscrollviewAlpha = discrollviewAlpha;
    }

    public void setDiscrollviewScaleX(boolean discrollviewScaleX) {
        mDiscrollviewScaleX = discrollviewScaleX;
    }

    public void setDiscrollviewScaleY(boolean discrollviewScaleY) {
        mDiscrollviewScaleY = discrollviewScaleY;
    }
    private float withThreshold(float ratio) {
        return (ratio - mDiscrollviewThreshold) / (1.0f - mDiscrollviewThreshold);
    }
    @Override
    public void onDiscrollview(float ratio) {
        //ratio 0~1
        //控制自身的动画属性
        if (ratio >= mDiscrollviewThreshold) {
            ratio = withThreshold(ratio);
            float ratioInverse = 1 - ratio;
            if (mDiscrollviewAlpha) {
                setAlpha(ratio);
            }


            //判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
            //fromBottom
            if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
                setTranslationY(mHeight * ratioInverse);
            }
            if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_TOP)) {
                setTranslationY(-mHeight * ratioInverse);
            }
            if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_LEFT)) {
                setTranslationX(-mWidth * ratioInverse);
            }
            if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_RIGHT)) {
                setTranslationX(mWidth * ratioInverse);
            }

            if (mDiscrollviewScaleX) {
                setScaleX(ratio);
            }

            if (mDiscrollviewScaleY) {
                setScaleY(ratio);
            }

            if (mDiscrollviewFromBgColor!=-1&&mDiscrollviewToBgColor!=-1){
                setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio,mDiscrollviewFromBgColor,mDiscrollviewToBgColor));
            }
        }
    }

    @Override
    public void onResetDiscrollview() {
        if(mDiscrollviewAlpha) {
            setAlpha(0.0f);
        }
        if(isDiscrollviewTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
            setTranslationY(mHeight);
        }
        if(isDiscrollviewTranslationFrom(TRANSLATION_FROM_TOP)) {
            setTranslationY(-mHeight);
        }
        if(isDiscrollviewTranslationFrom(TRANSLATION_FROM_LEFT)) {
            setTranslationX(-mWidth);
        }
        if(isDiscrollviewTranslationFrom(TRANSLATION_FROM_RIGHT)) {
            setTranslationX(mWidth);
        }
        if(mDiscrollviewScaleX) {
            setScaleX(0.0f);
        }
        if(mDiscrollviewScaleY) {
            setScaleY(0.0f);
        }
        if(mDiscrollviewFromBgColor != -1 && mDiscrollviewToBgColor != -1) {
            setBackgroundColor(mDiscrollviewFromBgColor);
        }
    }



    private boolean isDiscrollviewTranslationFrom(int translationMask) {
        if (mDiscrollviewTranslation == -1) {
            return false;
        }
        return (mDiscrollviewTranslation & translationMask) == translationMask;
    }
}

然后在加上自定义属性

 <declare-styleable name="DiscrollView_LayoutParams">
        <attr name="discrollview_alpha" format="boolean" />
        <attr name="discrollview_scaleX" format="boolean" />
        <attr name="discrollview_scaleY" format="boolean" />
        <attr name="discrollview_threshold" format="float" />
        <attr name="discrollview_fromBgColor" format="color" />
        <attr name="discrollview_toBgColor" format="color" />
        <attr name="discrollview_translation" />
    </declare-styleable>
    <attr name="discrollview_translation">
        <flag name="fromTop" value="0x01" />
        <flag name="fromBottom" value="0x02" />
        <flag name="fromLeft" value="0x04" />
        <flag name="fromRight" value="0x08" />
    </attr>

注意:

1、第一张是不能做改变的,正常情况下第一张应该占满整个屏幕,然后滑动之后的子控件就能实现效果

2、这里设置的是竖直排列,你可以设置为水平排列之类的效果当时ScrollView也得改为水平的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值