商品詳情頁上拉查看詳情

商品詳情頁上拉查看詳情

目錄介紹

  • 01.該庫介紹
  • 02.效果展示
  • 03.如何使用
  • 04.注意要點
  • 05.優化問題
  • 06.部分代碼邏輯
  • 07.參考案例

01.該庫介紹

  • 模仿淘寶、京東、考拉等商品詳情頁分頁加載的UI效果。可以嵌套RecyclerView、WebView、ViewPager、ScrollView等等。
  • 項目地址:https://github.com/yangchong2...

02.效果展示

  • slide.gif

2.1 使用SlideLayout效果

  • image

2.2 使用SlideAnimLayout帶有加載動畫效果

  • image
  • image

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.參考案例

08.其他更多

01.關於博客彙總鏈接

02.關於我的博客

項目地址:https://github.com/yangchong2...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章