Android自定義LinearLayout實現淘寶詳情頁

1.簡單說明

淘寶詳情頁就不用我一一介紹了,昨天逛淘寶看到這個效果時,讓我想起了去年剛學習Android只會使用現成的時候,當時在網上找了一個這種效果的使用了,並不懂怎麼實現的。現在就看到一種效果就想自己實現一下,我想這就是剛接觸某個知識時的好奇心吧

說走咱就走啊,本文只是介紹一種實現思路,網上也已經有了很多種實現方式,有問題請指正

效果圖(我有很用心的找美女圖的)

2.實現思路

繼承LinearLayout,設置方向爲垂直
控件中有兩個ScrollView,至於爲什麼要使用ScrollView,主要是因爲內容超過一頁時省去自己處理滑動
關鍵是事件分發處理。監聽兩個ScrollView的滑動事件,當第一頁滑動到底部時,再向上拖動時,攔截事件,判斷距離,超過設定值時,滑動到第二頁,否則回彈;同理,當第二頁滑動到頂部時,再向下拖動時,攔截事件,判斷距離,超過設定值時,滑動到第一頁,否則回彈(還有很多細節需要結合代碼講解)
關於回彈和滑動換頁使用的是Scroller,對於Scroller的使用,本文不做過多解釋

3.實現

3.1重寫ScrollView

根據實現思路,我們需要監聽ScrollView是否滑動到頂部和底部,但是ScrollView的setOnScrollChangeListener()方法在api23才添加。主要是重寫ScrollViewonScrollChanged(int l, int t, int oldl, int oldt)方法。

l:當前水平方向滾動值,和getScrollX()相等
t:當前豎直方向滾動值,和getScrollY()相等
oldl:上一次水平滾動值
oldt:上一次豎直滾動值

監聽接口:

1
2
3
4
5
public interface OnScrollEndListener {
  void scrollToBottom(View view);
  void scrollToTop(View view);
  void scrollToMiddle(View view);
  }

onScrollChanged方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void onScrollChanged( int l, int t, int oldl, int oldt) {
super .onScrollChanged(l, t, oldl, oldt);
if (t == 0 ){
  if (mOnScrollBottomListener != null ) {
  mOnScrollBottomListener.scrollToTop( this );
  }
} else if (t + getMeasuredHeight() >= getChildAt( 0 ).getMeasuredHeight()){
  if (mOnScrollBottomListener != null ) {
  mOnScrollBottomListener.scrollToBottom( this );
  }
} else {
  if (mOnScrollBottomListener != null ) {
  mOnScrollBottomListener.scrollToMiddle( this );
  }
}
}

3.2重寫onMeasure方法、page的獲取與設置

顯示調用第二個自孩子的測量方法,不然尺寸有可能爲0

1
2
3
4
5
6
7
8
9
10
11
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
super .onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
  * 顯示調用第二個自孩子的測量方法,不然尺寸有可能爲0
  */
View child2 = getChildAt( 1 );
if (child2 != null ) {
  child2.measure(widthMeasureSpec, heightMeasureSpec);
}
}

在onFinishInflate中初始化兩個頁面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  protected void onFinishInflate() {
  super .onFinishInflate();
  if (getChildCount() == 2 ){
   View child1 = getChildAt( 0 );
   if (child1 instanceof ScrollEndScrollView){
   scrollView1 = (ScrollEndScrollView) child1;
   }
   View child2 = getChildAt( 1 );
   if (child2 instanceof ScrollEndScrollView){
   scrollView2 = (ScrollEndScrollView) child2;
   }
  }
 
  initEvent();
  }

爲兩個頁面設置滑動監聽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private ScrollEndScrollView.OnScrollEndListener scrollEndListener = new ScrollEndScrollView.OnScrollEndListener() {
  public void scrollToBottom(View view) {
   if (view == scrollView1){
   isToBotttom = true ;
   }
  }
 
  public void scrollToTop(View view) {
   if (view == scrollView2){
   isToTop = true ;
   }
  }
 
  @Override
  public void scrollToMiddle(View view) {
   if (view == scrollView1){
   isToBotttom = false ;
   }
   if (view == scrollView2){
   isToTop = false ;
   }
  }
  };

3.3Scroller使用的幾步

Scroller的英文解釋是:
This class encapsulates scrolling. You can use scrollers (Scroller or OverScroller) to collect the data you need to produce a scrolling animation—for example, in response to a fling gesture. Scrollers track scroll offsets for you over time, but they don't automatically apply those positions to your view. It's your responsibility to get and apply new coordinates at a rate that will make the scrolling animation look smooth.

此類封裝滾動。您可以使用滾動條(滾輪或OverScroller)收集你需要製作一個滾動的動畫,例如,響應一扔手勢的數據。滾動條爲您跟蹤滾動偏移量隨着時間的推移,但他們不會自動將新的位置設置到View中。你的任務是獲取並使用一個合適的速度,使滾動動畫看起來更平滑。

簡而言之,有關滑動的你都可以使用這個實現。

需要重寫的方法

1
2
3
4
5
6
7
8
9
10
11
@Override
  public void computeScroll() {
  super .computeScroll();
  //先判斷mScroller滾動是否完成
  if (mScroller.computeScrollOffset()) {
   //這裏調用View的scrollTo()完成實際的滾動
   scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
   //必須調用該方法,否則不一定能看到滾動效果
   postInvalidate();
  }
  }

輔助方法

1
2
3
4
5
6
7
8
9
10
11
12
13
//調用此方法設置滾動的相對偏移
  public void smoothScrollBy( int dx, int dy) {
  //設置mScroller的滾動偏移量
  mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, Math.max( 300 , Math.abs(dy)));
  invalidate(); //這裏必須調用invalidate()才能保證computeScroll()會被調用,否則不一定會刷新界面,看不到滾動效果
  }
 
//調用此方法滾動到目標位置
  public void smoothScrollTo( int fx, int fy) {
  int dx = fx - mScroller.getFinalX();
  int dy = fy - mScroller.getFinalY();
  smoothScrollBy(dx, dy);
  }

3.4事件分發

最關鍵的部分,邏輯稍複雜,細節處理較多。這裏重寫dispatchTouchEvent。

顯示第一頁時
未滑動到底部,事件由scrollView1自己處理
滑動到底部時,如果繼續向上拖動,攔截事件,父控件處理滑動;繼續向下拖動時,如果父控件(即該控件)當前滾動最後位置(mScroller.getFinalY())不爲0, 如果父控件繼續滾動不會出現負值時(出現負值時會導致頭部空白,因爲這時是父控件控制,scrollView1不可滑動),不攔截事件,父控件處理滑動,否則,強制滑動到0位置,並把事件下發給子控件

顯示第二頁時
未滑動到最頂部時,事件由scrollView2自己處理
滑動到頂部時,如果繼續向下拖動,攔截事件,父控件處理滑動;繼續向上拖動時,如果父控件當前滾動位置小於第一頁高度,攔截事件,父控件處理滑動,否則,滑動到第二頁起始位置,並把事件下發給子控件
ACTION_MOVE中進行事件分發,ACTION_UP中進行切換頁面、回彈
關於使用scroller滑動,實現彈性效果,簡單實現請看這裏簡單的彈性實現代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
  int action = ev.getAction();
  int yPosition = ( int ) ev.getY();
  switch (action) {
   case MotionEvent.ACTION_DOWN:
   mScroller.abortAnimation();
   mLastY = yPosition;
   mMoveY = 0 ;
   break ;
   case MotionEvent.ACTION_MOVE:
   mMoveY = (mLastY - yPosition);
   mLastY = yPosition;
   if (isToBotttom){
    if (mMoveY > 0 ){
    //向上
    smoothScrollBy( 0 , mMoveY);
    return true ;
    } else {
    //向下
    if (mScroller.getFinalY() != 0 ){
     //這是出於第一頁和第二頁顯示連接處
     if (getScrollY() + mMoveY > 0 ){
     smoothScrollBy( 0 , mMoveY);
     return true ;
     } else {
     smoothScrollTo( 0 , 0 );
     return super .dispatchTouchEvent(ev);
     }
    }
    }
   }
   else if (isToTop){
    if (mMoveY < 0 ){
    //向下
    smoothScrollBy( 0 , mMoveY);
    return true ;
    } else {
    //向上
    if (mScroller.getFinalY() < scrollView1.getHeight()){
     //這是出於第一頁和第二頁顯示連接處
     smoothScrollBy( 0 , mMoveY);
     return true ;
    } else {
     smoothScrollTo( 0 , scrollView1.getHeight());
     return super .dispatchTouchEvent(ev);
    }
    }
   }
 
   //處理快速滑動時兩頁覆蓋問題
   if (pageIndex == 0 ){
    smoothScrollTo( 0 , 0 );
   } else if (pageIndex == 1 ){
    smoothScrollTo( 0 , scrollView1.getHeight());
   }
 
   break ;
 
   case MotionEvent.ACTION_UP:
   case MotionEvent.ACTION_CANCEL:
   if (isToBotttom){
    if (Math.abs(getScrollY()) > TO_NEXT_PAGE_HEIGHT){
    //移動到第二頁
    pageIndex = 1 ;
    smoothScrollTo( 0 , scrollView1.getHeight());
    isToBotttom = false ;
    isToTop = true ;
    } else {
    //回彈
    smoothScrollBy( 0 , -mScroller.getFinalY());
    }
   } else if (isToTop){
    if (scrollView1.getHeight() - getScrollY() > TO_NEXT_PAGE_HEIGHT){
    //移動到第一頁
    pageIndex = 0 ;
    smoothScrollTo( 0 , 0 );
    isToBotttom = true ;
    isToTop = false ;
    } else {
    //回彈
    smoothScrollTo( 0 , scrollView1.getHeight());
    }
   }
 
   break ;
   default :
   break ;
  }
 
  return super .dispatchTouchEvent(ev);
  }

4.總結

實現該控件,需要掌握的知識點主要是自定義控件的基本步驟、Scroller的基本使用和事件分發,當然這裏最關鍵的處理還是事件分發。開頭也說了,雖然這個有很多人實現過了,但還是想用自己的方式實現一遍。大笑三聲,哈哈哈,又實現一個自定義控件…博主還在自定義控件學習階段,請謹慎使用該控件到項目中。

5.下載

https://github.com/LineChen/TwoPageLayout

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

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