商品詳情頁上拉檢視詳情
阿新 • • 發佈:2019-04-22
商品詳情頁上拉檢視詳情
目錄介紹
- 01.該庫介紹
- 02.效果展示
- 03.如何使用
- 04.注意要點
- 05.優化問題
- 06.部分程式碼邏輯
- 07.參考案例
01.該庫介紹
- 模仿淘寶、京東、考拉等商品詳情頁分頁載入的UI效果。可以巢狀RecyclerView、WebView、ViewPager、ScrollView等等。
- 專案地址:https://github.com/yangchong211/YCShopDetailLayout
02.效果展示
2.1 使用SlideLayout效果
2.2 使用SlideAnimLayout帶有載入動畫效果
03.如何使用
3.1 第一種,直接上拉載入分頁【SlideLayout有兩個子ChildView】
- SlideDetailsLayout有兩個子ChildView:一個是商品頁layout,一個是詳情頁layout
- 在佈局中
<com.ycbjie.slide.SlideLayout android:id="@+id/slideDetailsLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:default_panel="front" app:duration="200" app:percent="0.1"> <!--商品佈局--> <FrameLayout android:id="@+id/fl_shop_main" android:layout_width="match_parent" android:layout_height="wrap_content"/> <!--分頁詳情webView佈局--> <include layout="@layout/include_shop_detail"/> </com.ycbjie.slide.SlideLayout>
- 在程式碼中
mSlideDetailsLayout.setOnSlideDetailsListener(new SlideLayout.OnSlideDetailsListener() { @Override public void onStatusChanged(SlideLayout.Status status) { if (status == SlideLayout.Status.OPEN) { //當前為圖文詳情頁 Log.e("FirstActivity","下拉回到商品詳情"); } else { //當前為商品詳情頁 Log.e("FirstActivity","繼續上拉,檢視圖文詳情"); } } }); //關閉商詳頁 mSlideDetailsLayout.smoothClose(true); //開啟詳情頁 mSlideDetailsLayout.smoothOpen(true);
3.2 第一種,上拉載入有動畫效果,然後展示分頁【SlideAnimLayout有三個子ChildView】
- SlideAnimLayout有三個子ChildView:一個是商品頁layout,一個是上拉載入動畫layout,一個是詳情頁layout
- 在佈局中
<com.ycbjie.slide.SlideAnimLayout android:id="@+id/slideDetailsLayout" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:default_panel="front" app:duration="200" app:percent="0.1"> <!--商品佈局--> <FrameLayout android:id="@+id/fl_shop_main2" android:layout_width="match_parent" android:layout_height="match_parent"/> <!--上拉載入動畫布局--> <LinearLayout android:id="@+id/ll_page_more" android:orientation="vertical" android:background="@color/colorAccent" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_more_img" android:layout_width="40dp" android:layout_height="40dp" android:rotation="180" android:layout_gravity="center_horizontal" android:src="@mipmap/icon_details_page_down_loading" /> <TextView android:id="@+id/tv_more_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="25dp" android:gravity="center" android:text="測試動畫,繼續上拉,檢視圖文詳情" android:textSize="13sp" /> </LinearLayout> <!--分頁詳情webView佈局--> <include layout="@layout/include_shop_detail"/> </com.ycbjie.slide.SlideAnimLayout>
- 在程式碼中
mSlideDetailsLayout.setScrollStatusListener(new SlideAnimLayout.onScrollStatusListener() { @Override public void onStatusChanged(SlideAnimLayout.Status mNowStatus, boolean isHalf) { if(mNowStatus== SlideAnimLayout.Status.CLOSE){ //開啟 if(isHalf){ mTvMoreText.setText("釋放,檢視圖文詳情"); mIvMoreImg.animate().rotation(0); LoggerUtils.i("onStatusChanged---CLOSE---釋放"+isHalf); }else{//關閉 mTvMoreText.setText("繼續上拉,檢視圖文詳情"); mIvMoreImg.animate().rotation(180); LoggerUtils.i("onStatusChanged---CLOSE---繼續上拉"+isHalf); } }else{ //開啟 if(isHalf){ mTvMoreText.setText("下拉回到商品詳情"); mIvMoreImg.animate().rotation(0); LoggerUtils.i("onStatusChanged---OPEN---下拉回到商品詳情"+isHalf); }else{//關閉 mTvMoreText.setText("釋放回到商品詳情"); mIvMoreImg.animate().rotation(180); LoggerUtils.i("onStatusChanged---OPEN---釋放回到商品詳情"+isHalf); } } } }); //關閉商詳頁 mSlideDetailsLayout.smoothClose(true); //開啟詳情頁 mSlideDetailsLayout.smoothOpen(true);
04.注意要點
- 針對SlideDetailsLayout僅獲取子節點中的前兩個View
- 其中第一個作為Front,即商品頁;第二個作為Behind,即圖文詳情WebView頁面。具體看程式碼:
@Override protected void onFinishInflate() { super.onFinishInflate(); final int childCount = getChildCount(); if (1 >= childCount) { throw new RuntimeException("SlideDetailsLayout only accept child more than 1!!"); } mFrontView = getChildAt(0); mBehindView = getChildAt(1); if(mDefaultPanel == 1){ post(new Runnable() { @Override public void run() { //預設是關閉狀態的 smoothOpen(false); } }); } }
- 針對SlideAnimLayout僅獲取子節點中三個View,且第二個為動畫節點View
- 其中第一個作為Front,即商品頁;第二個作為anim,即上拉動畫view。第三個作為Behind,即圖文詳情WebView頁面。具體看程式碼:
@Override protected void onFinishInflate() { super.onFinishInflate(); final int childCount = getChildCount(); if (1 >= childCount) { throw new RuntimeException("SlideDetailsLayout only accept childs more than 1!!"); } mFrontView = getChildAt(0); mAnimView = getChildAt(1); mBehindView = getChildAt(2); mAnimView.post(new Runnable() { @Override public void run() { animHeight = mAnimView.getHeight(); LoggerUtils.i("獲取控制元件高度"+animHeight); } }); if(mDefaultPanel == 1){ post(new Runnable() { @Override public void run() { //預設是關閉狀態的 smoothOpen(false); } }); } }
05.優化問題
- 異常情況儲存狀態
@Override protected Parcelable onSaveInstanceState() { SavedState ss = new SavedState(super.onSaveInstanceState()); ss.offset = mSlideOffset; ss.status = mStatus.ordinal(); return ss; } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); mSlideOffset = ss.offset; mStatus = Status.valueOf(ss.status); if (mStatus == Status.OPEN) { mBehindView.setVisibility(VISIBLE); } requestLayout(); }
- 當頁面銷燬的時候,移除listener監聽,移除動畫資源
@Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); setScrollStatusListener(null); setOnSlideStatusListener(null); if (animator!=null){ animator.cancel(); animator = null; } }
06.部分程式碼邏輯
6.1 如何實現ScrollView在最頂部或者最底部的時候,不消費事件
- 具體邏輯在dispatchTouchEvent分發事件中,當滑動到頂部或者底部的時候,則直接讓父View消費事件。其他情況是自己是將事件會向上返還給View的父節點。
@Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downX = ev.getX(); downY = ev.getY(); //如果滑動到了最底部,就允許繼續向上滑動載入下一頁,否者不允許 //如果子節點不希望父程序攔截觸控事件,則為true。 getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: float dx = ev.getX() - downX; float dy = ev.getY() - downY; boolean allowParentTouchEvent; if (Math.abs(dy) > Math.abs(dx)) { if (dy > 0) { //位於頂部時下拉,讓父View消費事件 allowParentTouchEvent = isTop(); } else { //位於底部時上拉,讓父View消費事件 allowParentTouchEvent = isBottom(); } } else { //水平方向滑動 allowParentTouchEvent = true; } getParent().requestDisallowInterceptTouchEvent(!allowParentTouchEvent); break; default: break; } return super.dispatchTouchEvent(ev); }
6.2 如何實現商品頁和詳情頁之間的滑動,如何處理上拉載入控制元件的動畫效果
- SlideAnimLayout有三個子ChildView:一個是商品頁layout,一個是上拉載入動畫layout,一個是詳情頁layout
- 通過onInterceptTouchEvent進行事件攔截後,在onTouchEvent方法中對觸控資訊做進一步處理可以實現豎直方向的滑動
- 當商品頁ScrollView滑動到底部時,則直接讓父View消費事件,該父View也就是SlideAnimLayout
- 在onInterceptTouchEvent中,當開啟詳情頁後(也就是CLOSE狀態),向下拉動,當y軸滑動位移絕對值大於觸控移動的畫素距離,並且當y軸滑動位移大於0,則攔截事件分發自己消費事件
- 在onInterceptTouchEvent中,當關閉詳情頁後(也就是OPEN狀態),向上拉動,當y軸滑動位移絕對值大於觸控移動的畫素距離,並且當y軸滑動位移小於0,則攔截事件分發自己消費事件
- 當處在商品頁時,向上拉動;或者處於詳情頁時,向下拉動,在拉動過程中去改變mSlideOffset值,並且呼叫requestLayout()方法去繪製
- 在螢幕區域滑動兩個面板只需要改變兩個面板在y軸方向的位移(有正負方向)即可。滑動的標尺是控制元件相對於Top的移動,且所有的位移計算都是基於該標尺。在切換面板時只需要知道對應的offset值即可……
- 如何處理上拉載入控制元件的動畫效果
- 新增一個listener監聽,可以監聽到狀態,以及是否達到一半距離,主要是和offset比較,當到達一半距離的時候,這個時候用屬性動畫將箭頭view旋轉180度即可實現。
- 既然要監聽滑動距離,則首先要獲取該載入控制元件的高度animHeight,那麼在哪裡獲取比較合適呢?可以在onFinishInflate()方法中,用post形式獲取控制元件高度。
- 那麼如何使滑動生效,並且看上去比較連貫
- 自定義佈局中有非常重要的兩個環節onMeasure(測量)和onLayout(佈局)。測量決定了View的所佔的大小,佈局決定了View所處的位置。實現滑動的關鍵思路就在這裡,我們在onLayout方法中根據通過onInterceptTouchEvent、onTouchEvent得到的滑動資訊進行計算而得到佈局的位置資訊,並把這個位置資訊設定到子View上面即可實現滑動。
- 滑動後鬆開手指如何實現滾動效果
- 也就是說,當處在商品頁時,向上拉動,拉動位移大於一半時,鬆開手指,則直接滑動到下一頁詳情頁頁面
- 具體邏輯在finishTouchEvent方法中,它主要是記錄offset值,以及close或open狀態下檢視的高度,還有是否發生切換變化
- 最後開啟動畫,在動畫過程中新增動畫update的監聽,在該方法中去requestLayout()控制元件,這樣就達到滾動效果了。動畫滾動結束後,如果是open狀態並且是第一次顯示,則設定詳情頁控制元件可見。
- 如何使滾動效果比較自然,或者如何調整滾動時長
- 可以自定義設定時間,直接在佈局中設定……
07.參考案例
- 感謝下面大佬的開源案例
- https://github.com/jeasonlzy/VerticalSlideView
- https://github.com/hexianqiao3755/GoodsInfoPage
- https://github.com/cnbleu/SlideDetailsLayout
08.其他更多
01.關於部落格彙總連結
02.關於我的部落格
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 簡書:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:[email protected]
- 阿里雲部落格:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e