介绍
仿招商银行手机银行APP8.1首页下拉效果,下拉震动出现二楼页面,上滑改变Title背景透明度,使用ScrollView控件自定义。
效果图
下拉二楼效果、上滑改变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代码
如发现问题,欢迎留言。