这里写自定义目录标题
Android 仿支付宝首页下拉刷新
页面上的布局是从支付宝首页上截取下来的,这里定义(扫一扫到卡包)的布局为蓝布局,(转载到更多)的布局为白布局。
市面上实现支付宝首页头部效果的Demo,我能百度到的案例,基本上使用CoordinatorLayout布局方案。
这些方案嵌套下拉刷新后体验不是很友好,所以自己撸一个。
图片演示
链接: link.
源码
源码地址: https://gitee.com/sophuron/copy_alipay_home.
下载体验: https://www.pgyer.com/EXOu.
最好直接打开源码运行一下吧,下面都是废话。
布局结构
布局整体都是靠NestedScrollView的滚动来实现支付宝首页效果的。
ll_offset布局顶部预留280dp为了避免布局遮盖并且隐藏下拉刷新提示布局。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AliPayHomeActivity"
android:background="#f4f4f4">
<android.support.v4.widget.NestedScrollView
android:id="@+id/nsv_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/ll_offset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="280dp"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_refresh"
android:layout_width="match_parent"
android:layout_height="60dp"
android:paddingTop="10dp"
android:gravity="center"
android:text="下拉刷新"/>
<TextView
android:layout_width="match_parent"
android:layout_height="1000dp"
android:gravity="center"
android:text="内容"
android:background="#fff"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@+id/ll_offset"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.constraint.ConstraintLayout
android:id="@+id/cl_parallax"
android:layout_width="match_parent"
android:layout_height="140dp"
android:text="Hello World!"
android:gravity="center"
android:background="#3e7fcb"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<LinearLayout
android:id="@+id/ll_category1"
android:layout_width="match_parent"
android:layout_height="90dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/ic_category1"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
<LinearLayout
android:id="@+id/ll_category2"
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="horizontal"
android:background="#fff"
app:layout_constraintTop_toBottomOf="@+id/cl_parallax">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/ic_category2"/>
</LinearLayout>
<View
android:id="@+id/view_toolbar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#3e7fcb"
android:alpha="0"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这个是仿支付宝首页"
android:textColor="#fff"
app:layout_constraintTop_toTopOf="@+id/view_toolbar"
app:layout_constraintLeft_toLeftOf="@+id/view_toolbar"
app:layout_constraintRight_toRightOf="@+id/view_toolbar"
app:layout_constraintBottom_toBottomOf="@+id/view_toolbar"/>
</android.support.constraint.ConstraintLayout>
根据 nsv_scroll 的滚动事件来移动cl_parallax和ll_category2的位置,也就是蓝布局和白布局。
nsv_scroll.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (lastScrollY < hScrollParallax) {
scrollY = Math.min(hScrollParallax, scrollY);
m_scroll_y = scrollY > hScrollParallax ? hScrollParallax : scrollY;
cl_parallax.setTranslationY((m_offset - m_scroll_y) / 2);
ll_category2.setTranslationY(m_offset - m_scroll_y);
}
if(scrollY <= hCategory1) {
float alpha = 1 - (float) scrollY / hCategory1;
ll_category1.setAlpha(alpha);
}
if(scrollY >= hCategory1) {
view_toolbar.setAlpha(1.0f);
}else {
view_toolbar.setAlpha(0);
}
lastScrollY = scrollY;
}
});
根据 nsv_scroll 的触摸事件来实现一些效果。
当 nsv_scroll 已经滑动到顶部时,判断用户手势是否继续向下拖拽。
用户继续向下拖拽则将 ll_offset 布局整个向下偏移。
用户手指抬起后判断拖拽距离是否大于刷新距离。
大于则执行刷新方法。
nsv_scroll.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(refreshState == refreshing) {
return true;
}
m_event = event;
//继承了Activity的onTouchEvent方法,直接监听点击事件
if(event.getAction() == MotionEvent.ACTION_DOWN) {
//当手指按下的时候记录手指点击的坐标
y1 = event.getY();
y3 = event.getY();
setRefreshTips(R.string.pull_to_refresh);// 展示下拉提示内容
}
if(event.getAction() == MotionEvent.ACTION_UP) {
y2 = event.getY();
//当手指离开的时候根据refreshState的状态判断是否需要执行刷新方法
if(refreshState == toRefresh) {
setRefreshTips(R.string.is_refreshing);
refreshState = refreshing;
ll_offset.setTranslationY(position + refreshableDistance);
new Handler().postDelayed(runnable, 3000);
}else {
// 根据拖拽距离判断是否需要执行收缩方法
if(ll_offset.getTranslationY() > position) {
endPosition = ll_offset.getTranslationY();
timer.start();
}
}
quickScrollDistance = y2 - y3;
}
if(event.getAction() == MotionEvent.ACTION_MOVE) {
//当手指离开的时候
y2 = event.getY();
// 当nsv_scroll移动到顶部,并且y2 - y1 > 0(表示用户正在下拉)
if(y2 - y1 > 0 && nsv_scroll.getScrollY() == 0) {
// 下拉距离大于可刷新距离
if((int)((y2 - y1) / pullDamp) > refreshableDistance) {
refreshState = toRefresh;
setRefreshTips(R.string.loosen_refresh);
}else {
refreshState = refreshNoTrigger;
setRefreshTips(R.string.pull_to_refresh);
}
float py = y2 - y1;
ll_offset.setTranslationY(py / pullDamp);// 移动布局
return true;
}
if(y1 - y2 >0) {
y3 = Math.min(y3, y2);
}
}
return false;
}
});
写完收工,代码会慢慢完善。