Android自定义ScrollView下拉二楼效果和上滑改变Title背景透明度

介绍

仿招商银行手机银行APP8.1首页下拉效果,下拉震动出现二楼页面,上滑改变Title背景透明度,使用ScrollView控件自定义。

效果图

下拉二楼效果、上滑改变Title背景透明度
下拉二楼效果、上滑改变Title背景透明度。

原理

下拉二楼效果:

通过监听ScrollView滑动不断改变指定二楼布局的MarginTop值。

上滑改变Title背景透明度效果:

通过监听ScrollView滚动具体位置计算出滑动位置的比例,再通过滑动位置的比例计算出Title背景色的透明度。

自定义ScrollView 完整代码

package com.example.myapplication.views;
import android.animation.ValueAnimator;
import android.content.Context;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;

/**
 * 自定义ScrollView下拉二楼效果和上滑改变Title背景透明度
 */
public class PullScrollView extends ScrollView {
    /**
     * 下拉二楼效果参数
     */
    //阻尼系数,值越小下拉阻力就越大.
    private static final float SCROLL_RATIO = 0.5f;
    private float DOWN_Y = 0;
    private float MOVE_Dy = 0;
    private boolean isTwoViewMove = false;
    private boolean isTwoViewOpen = false;
    private boolean isVibrate = true;
    //下拉高度比例 开始震动和松开手指可打开二楼  值越大 下拉高度越大
    private static final float MOVE_SCALE_OPEN_TWO_VIEW = 0.1f;
    //下拉二楼监听器
    private ScrollTwoViewListener mScrollTwoViewListener;
    private int mScreenHeight;
    private FrameLayout twoView;
    private LinearLayout.LayoutParams layoutParams;
    private NoTouchView noTouchView;
    /**
     * 上滑改变Title背景透明度效果参数
     */
    //上滑计算比例值监听器
    private ScrollStateListener mScrollStateListener;
    private View headView;
    private boolean isHeadShow = true;
    public PullScrollView(Context context) {
        this(context, null);
    }
    public PullScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initCommon();
    }
    private void initCommon() {
        setOverScrollMode(OVER_SCROLL_NEVER);
    }
    //设置头部View 用于计算滑动时 比例值 通过比例值可改变Title背景透明度等
    public void setHeadView(View headView) {
        this.headView = headView;
    }
    //设置二楼View
    public void setTwoView(FrameLayout twoView) {
        this.twoView = twoView;
        WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        this.mScreenHeight = displayMetrics.heightPixels;
        noTouchView = new NoTouchView(getContext());
        LayoutParams fl = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        noTouchView.setLayoutParams(fl);
        twoView.removeView(noTouchView);
        twoView.addView(noTouchView);
        setTwoViewTopMargin(-mScreenHeight);

    }
    public void setScrollStateListener(ScrollStateListener scrollStateListener) {
        mScrollStateListener = scrollStateListener;
    }
    public void setScrollTwoViewListener(ScrollTwoViewListener mScrollTwoViewListener) {
        this.mScrollTwoViewListener = mScrollTwoViewListener;
    }
    //打开二楼View
    public void openTwoView() {
        if (twoView != null) {
            twoViewChangeAnim(false);
        }
    }
    //关闭二楼View
    public void closeTwoView() {
        if (twoView != null) {
            twoViewChangeAnim(true);
        }
    }
    //二楼View是否打开
    public boolean isTwoViewOpen() {
        return isTwoViewOpen;
    }
    private void twoViewChangeAnim(boolean isClose) {
        if (twoView != null) {
            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) twoView.getLayoutParams();
            final int startMargin = layoutParams.topMargin;
            final int endMargin = isClose ? -mScreenHeight : 0;
            ValueAnimator mValueAnim = ValueAnimator.ofInt(1);
            // 动画执行过程中的数值变化
            mValueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator mAnim) {
                    float fraction = mAnim.getAnimatedFraction();// 0.0 -->1.0
                    Integer evaluate = evaluate(fraction, startMargin, endMargin);
                    setTwoViewTopMargin(evaluate);
                }
            });
            mValueAnim.setDuration(250);// 设置动画执行时间
            mValueAnim.start();
        }
    }
    private void setTwoViewTopMargin(int topMargin) {
        if (layoutParams == null) {
            layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, mScreenHeight);
        }
        if (topMargin == -mScreenHeight) {
            //完全关闭了二楼VIEW
            isTwoViewOpen = false;
            layoutParams.height = mScreenHeight;
            if (mScrollTwoViewListener != null) {
                mScrollTwoViewListener.showTwoView(false);
            }
            twoView.removeView(noTouchView);
            twoView.addView(noTouchView);
            if (mScrollTwoViewListener != null) {
                mScrollTwoViewListener.closeTwoView();
            }
        } else if (topMargin == 0) {
            //完全打开了二楼View
            isTwoViewOpen = true;
            layoutParams.height = LinearLayout.LayoutParams.MATCH_PARENT;
            twoView.removeView(noTouchView);
            if (mScrollTwoViewListener != null) {
                mScrollTwoViewListener.showTwoView(true);
            }
            if (mScrollTwoViewListener != null) {
                mScrollTwoViewListener.openTwoView();
            }
        } else {
            //正在打开或关闭二楼VIEW过程中
            layoutParams.height = mScreenHeight;
            if (mScrollTwoViewListener != null) {
                mScrollTwoViewListener.showTwoView(true);
            }
        }
        layoutParams.topMargin = topMargin;
        twoView.setLayoutParams(layoutParams);
        twoView.requestLayout();
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (twoView != null) {
            int action = ev.getAction();
            float MOVE_Y;
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    if (getScaleY() == 0) {
                        DOWN_Y = ev.getY();
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (getScrollY() == 0) {
                        MOVE_Y = ev.getY();
                        if (DOWN_Y == 0) {
                            DOWN_Y = MOVE_Y;
                        }
                        MOVE_Dy = MOVE_Y - DOWN_Y;
                        int TOW_VIEW_MOVE_DY = -mScreenHeight + (int) (MOVE_Dy * SCROLL_RATIO);
                        if (TOW_VIEW_MOVE_DY >= -mScreenHeight && TOW_VIEW_MOVE_DY <= 0) {
                            isTwoViewMove = true;
                            setTwoViewTopMargin(TOW_VIEW_MOVE_DY);
                            if ((MOVE_Dy / mScreenHeight) > MOVE_SCALE_OPEN_TWO_VIEW && MOVE_Dy > 0) {
                                if (isVibrate) {
                                    vibrate();
                                    isVibrate = false;
                                }
                            } else {
                                isVibrate = true;
                            }
                            return true;
                        }
                    } else {
                        if (isTwoViewMove) {
                            isTwoViewMove = false;
                            DOWN_Y = 0;
                            setTwoViewTopMargin(-mScreenHeight);
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    // 判断是否可打开二楼
                    if ((MOVE_Dy / mScreenHeight) > MOVE_SCALE_OPEN_TWO_VIEW) {
                        DOWN_Y = 0;
                        openTwoView();
                        return true;
                    }
                    DOWN_Y = 0;
                    setTwoViewTopMargin(-mScreenHeight);
                    break;
                default:
                    break;
            }
        }
        return super.onTouchEvent(ev);
    }
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        if (headView != null) {
            int headView_height = headView.getMeasuredHeight();
            float scale;
            if (t <= 0) {
                scale = 0.0f;
            } else if (t <= headView_height) {
                scale = (float) t / headView_height;
            } else {
                scale = 1.0f;
            }
            if (mScrollStateListener != null) {
                mScrollStateListener.scrollState(scale);
                if (scale > 0.5) {
                    if (isHeadShow) {
                        mScrollStateListener.changedState(false);
                    }
                    isHeadShow = false;
                } else {
                    if (!isHeadShow) {
                        mScrollStateListener.changedState(true);
                    }
                    isHeadShow = true;
                }
            }
        }
        super.onScrollChanged(l, t, oldl, oldt);
    }
    //震动
    private void vibrate() {
        try {
            Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
            vibrator.vibrate(80);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //类型估值器
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int) (startInt + fraction * (endValue - startInt));
    }
    // 滚动改变比例监听器
    public interface ScrollStateListener {
        //滑动过程中比例值改变 scrollScale 0-1
        void scrollState(float scrollScale);
        //滑动过程中  比例值大于0.5 isOpen=false 小于等于0.5  isOpen=true
        void changedState(boolean isOpen);
    }
    //解决下拉过程中禁用二楼所有点击事件
    private class NoTouchView extends View {
        public NoTouchView(Context context) {
            super(context);
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return true;
        }
    }

    //二楼View打开关闭监听器
    public interface ScrollTwoViewListener {
        //是否已显示出二楼view 未开始下拉 isShow=false  下拉中isShow=true 完全打开二楼View isShow=true
        void showTwoView(boolean isShow);
        //已打开二楼View
        void openTwoView();
        //已关闭二楼View
        void closeTwoView();
    }
}

使用

页面布局

注意:如果使用下拉二楼效果, 建议根布局为LinearLayout ,且排列方式为垂直(vertical),因为PullScrollView中使用到给头部布局不断设置MarginTop值改变二楼布局的位置。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <FrameLayout
        android:id="@+id/ll_two_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@mipmap/b"
                android:orientation="vertical">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="TEST"
                    android:textSize="25sp" />
            </LinearLayout>
        </ScrollView>
        <Button
            android:id="@+id/bt_go_home"
            android:layout_width="90dip"
            android:layout_height="35dip"
            android:layout_gravity="center_horizontal|bottom"
            android:layout_marginBottom="50dip"
            android:background="@mipmap/d"
            android:onClick="closeTwoView"
            android:visibility="gone" />
    </FrameLayout>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <com.example.myapplication.views.PullScrollView
            android:id="@+id/pull_scroll"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <RelativeLayout
                android:id="@+id/ll_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@mipmap/a">
                <LinearLayout
                    android:id="@+id/ll_head"
                    android:layout_width="match_parent"
                    android:layout_height="200dip"
                    android:orientation="vertical" />
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentBottom="true"
                    android:text="TEST"
                    android:textSize="20sp" />
            </RelativeLayout>
        </com.example.myapplication.views.PullScrollView>
        <ImageView
            android:id="@+id/iv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@mipmap/c" />
    </FrameLayout>
</LinearLayout>

下拉二楼效果:

需要设置二楼View(二楼View根布局FrameLayout),设置方法:

pullScrollView.setTwoView(ll_two_view); 

监听二楼打开、关闭及滑动监听器,设置方法:

pullScrollView.setScrollTwoViewListener(new PullScrollView.ScrollTwoViewListener() {
            @Override
            public void showTwoView(boolean isShow) {//是否已显示出二楼view 未开始下拉 isShow=false  下拉中isShow=true 完全打开二楼View isShow=true
                iv_title.setVisibility(isShow ? View.GONE : View.VISIBLE);
            }
            @Override
            public void openTwoView() {//已打开二楼View
                bt_go_home.setVisibility(View.VISIBLE);
            }
            @Override
            public void closeTwoView() {//已关闭二楼View
                bt_go_home.setVisibility(View.GONE);
            }
        }); 

主动打开二楼,设置方法:

pullScrollView.openTwoView();

主动关闭二楼,设置方法:

pullScrollView.closeTwoView();

判断二楼是否已打开,设置方法:

pullScrollView.isTwoViewOpen()

上滑改变Title背景透明度效果:

需要设置头部View的布局,用于计算滑动过程中比例值(0.0-1.0),开始比例值为0,滑动具体超过指定头部View高度,比例值为1.

设置头部View,设置方法:

pullScrollView.setHeadView(iv_title);

监听滑动比例,设置方法:

pullScrollView.setScrollStateListener(new PullScrollView.ScrollStateListener() {
    @Override
    public void scrollState(float scrollScale) {
        //滑动过程中比例值改变 scrollScale 0.0-1.0
        titleBgDrawable.setAlpha((int) (scrollScale * 255));
        iv_title.setBackground(titleBgDrawable);
    }
    @Override
    public void changedState(boolean isOpen) {
    	//滑动过程中  比例值大于0.5 isOpen=false 小于等于0.5  isOpen=true
        Log.i("TTT", "changedState:" + isOpen);
    }
});

附录

完整Demo代码
如发现问题,欢迎留言。

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值