项目商品详情页的需求,实现上拉显示和下拉隐藏详情的功能,最终效果图如图:
实现思路:上下通过判断两个ScrollView的滑动位置及触摸事件发生的位置,对他们进行隐藏和显示。
上拉部分,由于内部包裹了大量带有点击事件的组件,故需要在上拉条上进行拖动,否则会冲突无法判断。这部分是通过对其包裹内容动态的调用layout方法来实现拖拽效果;
下拉部分,通过对下拉头动态设置marginTop的值,来改变其高度,并达到隐藏和显示的效果。
//上拉组件
@Bind(R.id.xscrollview)
XScrollView mXscrollview;
@Bind(R.id.scrollContainer)
LinearLayout scrollContainer;
//下拉组件
@Bind(R.id.scrollContainer2)
RelativeLayout scrollContainer2;
@Bind(R.id.svBottomDetails)
ScrollView svBottomDetails;
@Bind({R.id.llDownScroll})
LinearLayout llDownScroll;
//上拉部分
private Rect rect = null;//记录scrollView包裹组件的位置
private int fullScroll;//scrollView滚动到底部时ScrollView的scrollY值
private float mDeltaY;//(上拉模块)拖动的距离
private int downScrollY;//开始拖动的ScrollView的scrollY值
private float startY;//开始拖动的MotionEvent的y值
//下拉部分
private float startY2;
private float downScrollY2;
private float mDeltaY2;
private boolean touched;//是否触摸,在ACTION_MOVE中做标记,记录按下时需要的值
private RelativeLayout.LayoutParams mLayoutParams;
/*******************监听详情的隐藏和显示*******************/
private OnGoodsDetailsListener mOnGoodsDetailsListener;
public void setOnGoodsDetailsListener(OnGoodsDetailsListener onGoodsDetailsListener) {
mOnGoodsDetailsListener = onGoodsDetailsListener;
}
public interface OnGoodsDetailsListener{
void onShow(boolean showDetails);
}
//下拉隐藏详情
private void initPullDown() {
mLayoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
svBottomDetails.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//可能的冲突,改为在ACTION_MOVE中获取
break;
case MotionEvent.ACTION_UP:
//向下拖动距离大于100
if (mDeltaY2 > 120 && downScrollY2 == 0) {
Log.d(TAG, "onTouch: 隐藏详情了");
//显示上部
mXscrollview.setVisibility(View.VISIBLE);
mXscrollview.smoothScrollTo(0, fullScroll);//滚动到底部
//隐藏详情
svBottomDetails.setVisibility(View.GONE);
//还原标题-->商品 详情 评价
//修改返回按键和小箭头的事件->点击结束act
//将状态传到activity,改变标题
mOnGoodsDetailsListener.onShow(false);
}
// //恢复原有marginTop高度,隐藏头
mLayoutParams.setMargins(0, (int) getResources().getDimension(R.dimen.pulldown_head_margin), 0, 0);
llDownScroll.setLayoutParams(mLayoutParams);
//重置
mDeltaY2 = 0;
downScrollY2 = 0;
startY2 = 0;
touched = false;
break;
case MotionEvent.ACTION_MOVE:
//从顶部开始的滑动,且向下滑
//在此记录按下位置,取代ACTION_DOWN中
if (!touched) {
startY2 = event.getY();
downScrollY2 = svBottomDetails.getScrollY();
Log.d(TAG, "onTouch: startY2 = " + startY2 + " , downScrollY2 = " + downScrollY2);
}
touched = true;
mDeltaY2 = 0.5f * (event.getY() - startY2);
Log.d(TAG, "onTouch: downScrollY2 = " + downScrollY2);
Log.d(TAG, "onTouch: startY2 = " + startY2);
Log.d(TAG, "onTouch: mDeltaY2 = " + mDeltaY2);
if (downScrollY2 == 0 && mDeltaY2 > 0) {
//计算marginTop高度,动态显示头高度
int top = (int) (-120 + mDeltaY2);
Log.d(TAG, "onTouch: marginTop = " + top);
mLayoutParams.setMargins(0, top, 0, 0);
llDownScroll.setLayoutParams(mLayoutParams);
}
break;
default:
break;
}
return false;
}
});
}
/**
* 上拉查看详情
*/
private void initPullUp() {
//这一步操作为,获取ScrollView的完全高度,在上拉时,判断是否从最底部开始
mXscrollview.setOnScrollToBottomLintener(new XScrollView.OnScrollToBottomListener() {
@Override
public void onScrollBottomListener(boolean isBottom) {
if (isBottom) {
fullScroll = mXscrollview.getScrollY();
Log.d(TAG, "onScrollBottomListener: scrollY = " + fullScroll);
}
}
});
mXscrollview.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录按下时的Y值
startY = event.getY();
downScrollY = mXscrollview.getScrollY();
Log.d(TAG, "onTouch: startY = " + startY + " , downScrollY = " + downScrollY);
if (rect == null) {
rect = new Rect(scrollContainer.getLeft(), scrollContainer.getTop(), scrollContainer.getRight(), scrollContainer.getBottom());
}
break;
case MotionEvent.ACTION_UP:
//拖动距离大于120
if (Math.abs(mDeltaY) > 120) {
Log.d(TAG, "onTouch: 显示详情了");
//显示详情
svBottomDetails.setVisibility(View.VISIBLE);
svBottomDetails.smoothScrollTo(0, 0);
//隐藏上部
mXscrollview.setVisibility(View.GONE);
//改变标题-->图文详情
//修改返回按键和小箭头的事件->点击还原
//将状态传到activity,改变标题
mOnGoodsDetailsListener.onShow(true);
}
// 恢复原有高度
if (rect != null) {
scrollContainer.layout(rect.left, rect.top, rect.right, rect.bottom);
Log.d(TAG, "onTouch: 松手了");
}
//重置
mDeltaY = 0;
downScrollY = 0;
startY = 0;
break;
case MotionEvent.ACTION_MOVE:
//downScrollY != 0:不是从顶部开始的滑动;fullScroll不为0且不为负值(bug?)
if (downScrollY != 0 && fullScroll > 0 && downScrollY >= fullScroll - 20) {
//deltaY<0,向上滑动
mDeltaY = 0.5f * (event.getY() - startY);
Log.d(TAG, "onTouch: downScrollY = " + downScrollY);
Log.d(TAG, "onTouch: fullScroll = " + fullScroll);
Log.d(TAG, "onTouch: mDeltaY = " + mDeltaY);
if (rect != null) {
scrollContainer.layout(rect.left, (int) (rect.top + mDeltaY), rect.right, (int) (rect.bottom + mDeltaY));
}
}
break;
default:
break;
}
return false;
}
});
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rlContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/nc_good_bg">
<ScrollView
android:id="@+id/xscrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_below="@+id/tvLoading"
android:visibility="visible">
<LinearLayout
android:id="@+id/scrollContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@color/app_white"
android:drawableLeft="@drawable/arrow_top"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/arrow_top"/>
<TextView
android:id="@+id/tvUpScroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/app_white"
android:gravity="center"
android:paddingBottom="20dp"
android:paddingLeft="4dp"
android:paddingTop="20dp"
android:text="上拉查看图文详情"/>
LinearLayout>
LinearLayout>
ScrollView>
<ScrollView
android:id="@+id/svBottomDetails"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<RelativeLayout
android:id="@+id/scrollContainer2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<LinearLayout
android:id="@+id/llDownScroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/pulldown_head_margin"
android:background="@color/app_white"
android:gravity="center"
android:orientation="horizontal"
android:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/arrow_down"/>
<TextView
android:id="@+id/tvDownScroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/app_white"
android:gravity="center"
android:paddingBottom="20dp"
android:paddingLeft="4dp"
android:paddingTop="20dp"
android:text="下拉收起图文详情"/>
LinearLayout>
<LinearLayout
android:id="@+id/llTabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/llDownScroll"
android:background="@color/app_white"
android:orientation="horizontal"
android:padding="@dimen/dp8"
android:visibility="visible">
LinearLayout>
<WebView
android:id="@+id/wvImg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/llTabs"/>
RelativeLayout>
ScrollView>
RelativeLayout>
//滚动到底部时,clampedY变为true,其余情况为false,通过回调将状态传出去即可。
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
if (scrollY != 0 && null != mOnScrollToBottomListener) {
mOnScrollToBottomListener.onScrollBottomListener(clampedY);
}
}
public class MyViewPager extends ViewPager {
public MyViewPager(Context context) {
super(context);
}
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
//默认为true
private boolean isCanScroll = true;
//通过设置为false,禁止ViewPager的水平滑动
public void setCanScroll(boolean isCanScroll) {
this.isCanScroll = isCanScroll;
}
@Override
public void scrollTo(int x, int y) {
if (isCanScroll) {
super.scrollTo(x, y);
}
}
}
/*******************控制标题显示*********************/
@Override
public void onShow(boolean showDetails) {
this.showDetails = showDetails;
if (showDetails){
setCommonHeader("图文详情");
tabGoods.setVisibility(View.GONE);
//禁止ViewPager水平滑动
vpFragment.setCanScroll(false);
}else {
setCommonHeader("");
//显示TabLayout
tabGoods.setVisibility(View.VISIBLE);
//恢复ViewPager水平滑动
vpFragment.setCanScroll(true);
}
}