1. 程式人生 > >仿淘寶商品詳情頁面Android

仿淘寶商品詳情頁面Android

1、需求:

要實現一個類似淘寶、京東的商品詳情頁面。首先是在看一些前輩的思路,檢視之後,發現博主qifengdeqingchen的文章不錯,然後去下載下來檢視demo。

2、查閱資料

來看看前輩的思路圖。使用兩個scrollView,兩個scrollView 豎直排列,通過自定義viewGroup來控制兩個scrollView的豎直排列,以及滑動事件的處理。

實現思路

3、發現問題:

我在scrollView2中新增一個TabLayout+ViewPager,然後在新增兩個Fragment,在fragment中寫一個scrollview.這時候發現在Fragment中只可以向下滑動,當想要去向上滑動的時候,就滑動到了scrollView1中。

4、解決問題:

首先來看一下前輩的程式碼,如下:

public class PullUpToLoadMore extends ViewGroup {  
    public static String TAG = PullUpToLoadMore.class.getName();  

    MyScrollView topScrollView, bottomScrollView;  
    VelocityTracker velocityTracker = VelocityTracker.obtain();  
    Scroller scroller = new Scroller(getContext());  

    int
currPosition = 0; int position1Y; int lastY; public int scaledTouchSlop;//最小滑動距離 int speed = 200; boolean isIntercept; public boolean bottomScrollVIewIsInTop = false; public boolean topScrollViewIsBottom = false; public PullUpToLoadMore(Context context) { super
(context); init(); } public PullUpToLoadMore(Context context, AttributeSet attrs) { super(context, attrs); init(); } public PullUpToLoadMore(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { post(new Runnable() { @Override public void run() { topScrollView = (MyScrollView) getChildAt(0); bottomScrollView = (MyScrollView) getChildAt(1); topScrollView.setScrollListener(new MyScrollView.ScrollListener() { @Override public void onScrollToBottom() { topScrollViewIsBottom = true; } @Override public void onScrollToTop() { } @Override public void onScroll(int scrollY) { } @Override public void notBottom() { topScrollViewIsBottom = false; } }); bottomScrollView.setScrollListener(new MyScrollView.ScrollListener() { @Override public void onScrollToBottom() { } @Override public void onScrollToTop() { } @Override public void onScroll(int scrollY) { if (scrollY == 0) { bottomScrollVIewIsInTop = true; } else { bottomScrollVIewIsInTop = false; } } @Override public void notBottom() { } }); position1Y = topScrollView.getBottom(); scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } }); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { //防止子View禁止父view攔截事件 this.requestDisallowInterceptTouchEvent(false); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int y = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: lastY = y; break; case MotionEvent.ACTION_MOVE: //判斷是否已經滾動到了底部 if (topScrollViewIsBottom) { int dy = lastY - y; //判斷是否是向上滑動和是否在第一屏 if (dy > 0 && currPosition == 0) { if (dy >= scaledTouchSlop) { isIntercept = true;//攔截事件 lastY=y; } } } if (bottomScrollVIewIsInTop) { int dy = lastY - y; //判斷是否是向下滑動和是否在第二屏 if (dy < 0 && currPosition == 1) { if (Math.abs(dy) >= scaledTouchSlop) { isIntercept = true; } } } break; } return isIntercept; } @Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); velocityTracker.addMovement(event); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: int dy = lastY - y; if (getScrollY() + dy < 0) { dy = getScrollY() + dy + Math.abs(getScrollY() + dy); } if (getScrollY() + dy + getHeight() > bottomScrollView.getBottom()) { dy = dy - (getScrollY() + dy - (bottomScrollView.getBottom() - getHeight())); } scrollBy(0, dy); break; case MotionEvent.ACTION_UP: isIntercept = false; velocityTracker.computeCurrentVelocity(1000); float yVelocity = velocityTracker.getYVelocity(); if (currPosition == 0) { if (yVelocity < 0 && yVelocity < -speed) { smoothScroll(position1Y); currPosition = 1; } else { smoothScroll(0); } } else { if (yVelocity > 0 && yVelocity > speed) { smoothScroll(0); currPosition = 0; } else { smoothScroll(position1Y); } } break; } lastY = y; return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int childTop = t; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); child.layout(l, childTop, r, childTop + child.getMeasuredHeight()); childTop += child.getMeasuredHeight(); } } //通過Scroller實現彈性滑動 private void smoothScroll(int tartY) { int dy = tartY - getScrollY(); scroller.startScroll(getScrollX(), getScrollY(), 0, dy); invalidate(); } @Override public void computeScroll() { if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } } }

通過分析我們可以大概瞭解到,出現在個問題應該是在滑動事件的解決上。在自定義控制元件中的onInterceptTouchEvent方法中,可能攔截了滑動事件,才導致了問題的出現。於是我在onInterceptTouchEvent方法中做出了改動。當向下滑動時,判斷是否在ScrollView2中,分別處理滑動事件。

當頁面沒有在ScrollView2中,就要讓自定義viewGroup中的Scrollview頁面切換(這時候父控制元件攔截OnTouchEvent,不向子控制元件傳遞,讓父控制元件滑動)。當頁面在ScrollView2中,還要去判斷viewpager裡邊的scrollview是否滑動到了頂部,如果在頂部,要去切換viewgroup中的頁面(這時候父控制元件攔截OnTouchEvent,不向子控制元件傳遞,讓父控制元件滑動),如果沒有滑動到頂部,就要讓viewpager中的scrollview滑動(這時候父控制元件不攔截OnTouchEvent,向子控制元件傳遞,讓子控制元件滑動)。

5、詳細程式碼如下:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int y = (int) ev.getY();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:

                //判斷是否已經滾動到了底部
                if (topScrollViewIsBottom) {
                    int dy = lastY - y;
                    //判斷是否是向上滑動和是否在第一屏
                    if (dy > 0 && currPosition == 0) {
                        if (dy >= scaledTouchSlop) {
                            isIntercept = true;//攔截事件
                            lastY=y;
                        }
                    }
                }

                if (bottomScrollVIewIsInTop) {
                    int dy = lastY - y;
                    //判斷是否是向下滑動和是否在第二屏
                    if (dy < 0 && currPosition == 1) {
                        if (Math.abs(dy) >= scaledTouchSlop) {
                            if(PublicStaticClass.IsTop){//如果viewpager裡邊的scrollview在最頂部,,就讓外邊的scrollview獲取焦點,否則,讓最裡邊的scrollview獲取焦點
                                isIntercept = true;
                            }
                        }
                    }

                }else{
                    int dy = lastY - y;
                    //判斷是否是向上滑動和是否在第二屏   如果是在剛到第二屏的時候,向上滑動,也讓父控制元件獲取焦點
//                    在onInterceptTouchEvent()方法中,如果返回true,父控制元件攔截事件,如果返回false,則向下傳遞
                    if (dy < 0 && currPosition == 1) {
                        if (Math.abs(dy) >= scaledTouchSlop) {
                            if(PublicStaticClass.IsTop){
                            //PublicStaticClass.IsTop  判斷fragment中的scrollview時候滑動到了頂部。
                            //如果viewpager裡邊的scrollview在最頂部,,就讓外邊的scrollview獲取焦點,否則,讓最裡邊的scrollview獲取焦點
                                isIntercept = true;
                            }
                        }
                    }
                }
                break;
        }
        return isIntercept;
    }

來看一下ViewPager中的實現:

自定義控制元件:MyScrollView.java:

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;

/**
 * Created by baoyunlong on 16/6/8.
 */
public class MyScrollView extends ScrollView {
    private static String TAG=MyScrollView.class.getName();

    public void setScrollListener(ScrollListener scrollListener) {
        this.mScrollListener = scrollListener;
    }

    private ScrollListener mScrollListener;

    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        switch (ev.getAction()){
            case MotionEvent.ACTION_MOVE:

                if(mScrollListener!=null){
                    int contentHeight=getChildAt(0).getHeight();
                    int scrollHeight=getHeight();

                    int scrollY=getScrollY();
                    mScrollListener.onScroll(scrollY);

                    if(scrollY+scrollHeight>=contentHeight||contentHeight<=scrollHeight){
                        mScrollListener.onScrollToBottom();
                    }else {
                        mScrollListener.notBottom();
                    }

                    if(scrollY==0){
                        mScrollListener.onScrollToTop();
                    }

                }

                break;
        }
        boolean result=super.onTouchEvent(ev);
        requestDisallowInterceptTouchEvent(false);

        return result;
    }

    public interface ScrollListener{
        void onScrollToBottom();
        void onScrollToTop();
        void onScroll(int scrollY);
        void notBottom();
    }
}

xml:

<com.qfdqc.views.pulltoloadmoreview.utils.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
                                                       android:id="@+id/oneScrollview"
                                                       android:layout_width="match_parent"
                                                       android:layout_height="match_parent"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">


        <TextView
            android:layout_width="match_parent"
            android:layout_height="@dimen/px_300"
            android:text="我是第一個頁面,向上滑動一下試試1"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="@dimen/px_300"
            android:text="我是第一個頁面,向上滑動一下試試2"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="@dimen/px_300"
            android:text="我是第一個頁面,向上滑動一下試試3"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="@dimen/px_300"
            android:text="我是第一個頁面,向上滑動一下試試4"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="@dimen/px_300"
            android:text="我是第一個頁面,向上滑動一下試試5"/>
    </LinearLayout>
</com.qfdqc.views.pulltoloadmoreview.utils.MyScrollView>

Fragment.java:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.qfdqc.views.pulltoloadmoreview.R;
import com.qfdqc.views.pulltoloadmoreview.utils.MyScrollView;
import com.qfdqc.views.pulltoloadmoreview.utils.PublicStaticClass;

public class OneFragment extends Fragment {
    View mView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mView = inflater.inflate(R.layout.fragment_one, container, false);
        initView();
        return mView;
    }

    private void initView() {
        MyScrollView oneScrollView= (MyScrollView) mView.findViewById(R.id.oneScrollview);
        oneScrollView.setScrollListener(new MyScrollView.ScrollListener() {
            @Override
            public void onScrollToBottom() {

            }

            @Override
            public void onScrollToTop() {

            }

            @Override
            public void onScroll(int scrollY) {
                //判斷時候滑動到了頂部
                if (scrollY == 0) {
                    PublicStaticClass.IsTop = true;
                } else {
                    PublicStaticClass.IsTop = false;
                }
            }

            @Override
            public void notBottom() {

            }

        });
    }
}

6、最後來上效果圖:

效果圖

7、又發現問題:

在第二頁上,左右滑動時,在水平方向上如果出現一點變化,就會滑動到第一頁上。這裡在onInterceptTouchEvent()方法中做了修改,判斷滑動的距離。如果左右滑動距離大於上下滑動距離,我們就認為使用者在左右滑動,這時候我們要讓子控制元件獲取到事件,去切換fragment。相反,如果上下滑動距離大於左右滑動距離,我們就認為使用者在上下滑動,這時候讓父控制元件攔截事件。具體程式碼如下:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int y = (int) ev.getY();
        int x = (int) ev.getX();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastY = y;
                lastX = x;
                break;
            case MotionEvent.ACTION_MOVE:

                //判斷是否已經滾動到了底部
                if (topScrollViewIsBottom) {
                    int dy = lastY - y;
                    //判斷是否是向上滑動和是否在第一屏
                    if (dy > 0 && currPosition == 0) {
                        if (dy >= scaledTouchSlop) {
                            isIntercept = true;//攔截事件
                            lastY=y;
                            lastX=x;
                        }
                    }
                }

                if (bottomScrollVIewIsInTop) {
                    int dy = lastY - y;
                    //判斷是否是向下滑動和是否在第二屏
                    if (dy < 0 && currPosition == 1) {
                        if (Math.abs(dy) >= scaledTouchSlop) {
                            if(PublicStaticClass.IsTop){//如果viewpager裡邊的scrollview在最頂部,,就讓外邊的scrollview獲取焦點,否則,讓最裡邊的scrollview獲取焦點
                                isIntercept = true;
                            }
                        }
                    }

                }else{
                    int dy = lastY - y;//上下滑動的距離
                    int dx = lastX - x;//左右滑動的距離

                    //判斷是否是向上滑動和是否在第二屏   如果是在剛到第二屏的時候,向上滑動,也讓父控制元件獲取焦點
//                    在onInterceptTouchEvent()方法中,如果返回true,父控制元件攔截事件,如果返回false,則向下傳遞
                    if (dy < 0 && currPosition == 1) {
                        if (Math.abs(dy) >= scaledTouchSlop) {
                            if(PublicStaticClass.IsTop){//如果viewpager裡邊的scrollview在最頂部,,就讓外邊的scrollview獲取焦點,否則,讓最裡邊的scrollview獲取焦點

                                //這裡加一個判斷,如果左右滑動的距離小於上下滑動的距離,我們認為使用者在上下滑動
                                 //如果左右滑動的距離大於上下滑動的距離,我們認為使用者在左右滑動
                                //上下滑動時,讓父控制元件攔截事件
                                //左右滑動時,讓子控制元件攔截事件
                                if(Math.abs(dy)>Math.abs(dx)){//上下滑動
                                    isIntercept = true;
                                }else{//左右滑動
                                    isIntercept = false;
                                }
                            }
                        }
                    }
                }
                break;
        }
        return isIntercept;
    }

8、github已經更新,專案地址: