上拉檢視詳情和下拉隱藏詳情
阿新 • • 發佈:2019-01-31
專案商品詳情頁的需求,實現上拉顯示和下拉隱藏詳情的功能,最終效果圖如圖:
實現思路:上下通過判斷兩個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;
}
});
}
佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<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包裹上部內容-->
<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">
<!--給marginTop一個高度的負值,隱藏下拉的頭-->
<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>
重寫的ScrollView,可獲取最大滾動高度
//滾動到底部時,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);
}
}
重寫後的ViewPager,可禁止水平滑動
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);
}
}
}
Activity中控制標題顯示
/*******************控制標題顯示*********************/
@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);
}
}