Android對ScrollView滾動監聽,實現美團、大眾點評的購買懸浮效果

轉帖請註明本文出自xiaanming的博客(https://blog.csdn.net/xiaanming),請尊重他人的辛勤勞動成果,謝謝!

隨著移動互聯網的快速發展,它已經和我們的生活息息相關瞭,在公交地鐵裡面都能看到很多人的人低頭看著自己的手機屏幕,從此“低頭族”一詞就產生瞭,作為一名移動行業的開發人員,我自己也是一名“低頭族”,上下班時間在公交地鐵上看看新聞來打發下時間,有時候也會看看那些受歡迎的App的一些界面效果,為什麼人傢的app那麼受歡迎?跟用戶體驗跟UI設計也有直接的關系,最近在美團和大眾點評的App看到如下效果,我感覺用戶好,很人性化,所以自己也嘗試著實現瞭下,接下來就講解下實現思路!

如上圖(2)我們看到瞭,當立即搶購佈局向上滑動到導航欄佈局的時候,立即搶購佈局就貼在導航欄佈局下面,下面的其他的佈局還是可以滑動,當我們向下滑動的時候,立即搶購的佈局又隨著往下滑動瞭,看似有點復雜,但是一說思路可能你就頓時恍然大悟瞭。

當我們向上滑動過程中,我們判斷立即搶購的佈局是否滑到導航欄佈局下面,如果立即搶購的上面頂到瞭導航欄,我們新建一個立即搶購的懸浮框來顯示在導航欄下面,這樣子就實現瞭立即搶購貼在導航欄下面的效果啦,而當我們向下滑動的時候,當立即搶購佈局的下面剛好到瞭剛剛新建的立即搶購懸浮框的下面的時候,我們就移除立即搶購懸浮框,可能說的有點拗口,既然知道瞭思路,接下來我們就來實現效果。

新建一個Android項目,取名MeiTuanDemo,先看立即搶購(buy_layout.xml)的佈局,這裡為瞭方便我直接從美團上面截去瞭圖片

  1. <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"

  2. android:orientation=”horizontal”

  3. android:layout_width=”fill_parent”

  4. android:layout_height=”wrap_content” >

  5. <ImageView

  6. android:id=”@+id/buy_layout”

  7. android:layout_width=”fill_parent”

  8. android:layout_height=”wrap_content”

  9. android:background=”@drawable/buy” />


  10. 立即搶購的佈局實現瞭,接下來實現主界面的佈局,上面是導航欄佈局,為瞭方便還是直接從美團截取的圖片,然後下面的ViewPager佈局,立即搶購佈局,其他佈局 放在ScrollView裡面,界面還是很簡單的

    1. <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"

    2. xmlns:tools=”https://schemas.android.com/tools”

    3. android:layout_width=”match_parent”

    4. android:layout_height=”match_parent”

    5. android:orientation=”vertical” >

    6. <ImageView

    7. android:id=”@+id/imageView1″

    8. android:scaleType=”centerCrop”

    9. android:layout_width=”match_parent”

    10. android:layout_height=”45dip”

    11. android:src=”@drawable/navigation_bar” />

    12. <com.example.meituandemo.MyScrollView

    13. android:id=”@+id/scrollView”

    14. android:layout_width=”fill_parent”

    15. android:layout_height=”fill_parent” >

    16. <LinearLayout

    17. android:layout_width=”match_parent”

    18. android:layout_height=”wrap_content”

    19. android:orientation=”vertical” >

    20. <ImageView

    21. android:id=”@+id/iamge”

    22. android:layout_width=”match_parent”

    23. android:layout_height=”wrap_content”

    24. android:background=”@drawable/pic”

    25. android:scaleType=”centerCrop” />

    26. <include

    27. android:id=”@+id/buy”

    28. layout=”@layout/buy_layout” />

    29. <ImageView

    30. android:layout_width=”match_parent”

    31. android:layout_height=”wrap_content”

    32. android:background=”@drawable/one”

    33. android:scaleType=”centerCrop” />

    34. <ImageView

    35. android:layout_width=”match_parent”

    36. android:layout_height=”wrap_content”

    37. android:background=”@drawable/one”

    38. android:scaleType=”centerCrop” />

    39. <ImageView

    40. android:layout_width=”match_parent”

    41. android:layout_height=”wrap_content”

    42. android:background=”@drawable/one”

    43. android:scaleType=”centerCrop” />

    44. 你會發現上面的主界面佈局中並不是ScrollView,而是自定義的一個MyScrollView,接下來就看看MyScrollView類中的代碼

      1. package com.example.meituandemo;

      2. import android.content.Context;

      3. import android.os.Handler;

      4. import android.util.AttributeSet;

      5. import android.view.MotionEvent;

      6. import android.widget.ScrollView;

      7. /**

      8. * 博客地址:https://blog.csdn.net/xiaanming

      9. *

      10. * @author xiaanming

      11. *

      12. */

      13. public class MyScrollView extends ScrollView {

      14. private OnScrollListener onScrollListener;

      15. /**

      16. * 主要是用在用戶手指離開MyScrollView,MyScrollView還在繼續滑動,我們用來保存Y的距離,然後做比較

      17. */

      18. private int lastScrollY;

      19. public MyScrollView(Context context) {

      20. this(context, null);

      21. }

      22. public MyScrollView(Context context, AttributeSet attrs) {

      23. this(context, attrs, 0);

      24. }

      25. public MyScrollView(Context context, AttributeSet attrs, int defStyle) {

      26. super(context, attrs, defStyle);

      27. }

      28. /**

      29. * 設置滾動接口

      30. * @param onScrollListener

      31. */

      32. public void setOnScrollListener(OnScrollListener onScrollListener) {

      33. this.onScrollListener = onScrollListener;

      34. }

      35. /**

      36. * 用於用戶手指離開MyScrollView的時候獲取MyScrollView滾動的Y距離,然後回調給onScroll方法中

      37. */

      38. private Handler handler = new Handler() {

      39. public void handleMessage(android.os.Message msg) {

      40. int scrollY = MyScrollView.this.getScrollY();

      41. //此時的距離和記錄下的距離不相等,在隔5毫秒給handler發送消息

      42. if(lastScrollY != scrollY){

      43. lastScrollY = scrollY;

      44. handler.sendMessageDelayed(handler.obtainMessage(), 5);

      45. }

      46. if(onScrollListener != null){

      47. onScrollListener.onScroll(scrollY);

      48. }

      49. };

      50. };

      51. /**

      52. * 重寫onTouchEvent, 當用戶的手在MyScrollView上面的時候,

      53. * 直接將MyScrollView滑動的Y方向距離回調給onScroll方法中,當用戶抬起手的時候,

      54. * MyScrollView可能還在滑動,所以當用戶抬起手我們隔5毫秒給handler發送消息,在handler處理

      55. * MyScrollView滑動的距離

      56. */

      57. @Override

      58. public boolean onTouchEvent(MotionEvent ev) {

      59. if(onScrollListener != null){

      60. onScrollListener.onScroll(lastScrollY = this.getScrollY());

      61. }

      62. switch(ev.getAction()){

      63. case MotionEvent.ACTION_UP:

      64. handler.sendMessageDelayed(handler.obtainMessage(), 5);

      65. break;

      66. }

      67. return super.onTouchEvent(ev);

      68. }

      69. /**

      70. *

      71. * 滾動的回調接口

      72. *

      73. * @author xiaanming

      74. *

      75. */

      76. public interface OnScrollListener{

      77. /**

      78. * 回調方法, 返回MyScrollView滑動的Y方向距離

      79. * @param scrollY

      80. * 、

      81. */

      82. public void onScroll(int scrollY);

      83. }

      84. }

        一看代碼你也許明白瞭,就是對ScrollView的滾動Y值進行監聽,我們知道ScrollView並沒有實現滾動監聽,所以我們必須自行實現對ScrollView的監聽,我們很自然的想到在onTouchEvent()方法中實現對滾動Y軸進行監聽,可是你會發現,我們在滑動ScrollView的時候,當我們手指離開ScrollView。它可能還會繼續滑動一段距離,所以我們選擇在用戶手指離開的時候每隔5毫秒來判斷ScrollView是否停止滑動,並將ScrollView的滾動Y值回調給OnScrollListener接口的onScroll(int
        scrollY)方法中,我們隻需要對ScrollView調用我們隻需要對ScrollView調用setOnScrollListener方法就能監聽到滾動的Y值。

        實現瞭對ScrollView滾動的Y值進行監聽,接下來就簡單瞭,我們隻需要顯示立即搶購懸浮框和移除懸浮框瞭,接下來看看主界面Activity的代碼編寫

        1. package com.example.meituandemo;

        2. import android.app.Activity;

        3. import android.content.Context;

        4. import android.graphics.PixelFormat;

        5. import android.os.Bundle;

        6. import android.view.Gravity;

        7. import android.view.LayoutInflater;

        8. import android.view.View;

        9. import android.view.WindowManager;

        10. import android.view.WindowManager.LayoutParams;

        11. import android.widget.LinearLayout;

        12. import com.example.meituandemo.MyScrollView.OnScrollListener;

        13. /**

        14. * 博客地址:https://blog.csdn.net/xiaanming

        15. *

        16. * @author xiaanming

        17. *

        18. */

        19. public class MainActivity extends Activity implements OnScrollListener{

        20. private MyScrollView myScrollView;

        21. private LinearLayout mBuyLayout;

        22. private WindowManager mWindowManager;

        23. /**

        24. * 手機屏幕寬度

        25. */

        26. private int screenWidth;

        27. /**

        28. * 懸浮框View

        29. */

        30. private static View suspendView;

        31. /**

        32. * 懸浮框的參數

        33. */

        34. private static WindowManager.LayoutParams suspendLayoutParams;

        35. /**

        36. * 購買佈局的高度

        37. */

        38. private int buyLayoutHeight;

        39. /**

        40. * myScrollView與其父類佈局的頂部距離

        41. */

        42. private int myScrollViewTop;

        43. /**

        44. * 購買佈局與其父類佈局的頂部距離

        45. */

        46. private int buyLayoutTop;

        47. @Override

        48. protected void onCreate(Bundle savedInstanceState) {

        49. super.onCreate(savedInstanceState);

        50. setContentView(R.layout.activity_main);

        51. myScrollView = (MyScrollView) findViewById(R.id.scrollView);

        52. mBuyLayout = (LinearLayout) findViewById(R.id.buy);

        53. myScrollView.setOnScrollListener(this);

        54. mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

        55. screenWidth = mWindowManager.getDefaultDisplay().getWidth();

        56. }

        57. /**

        58. * 窗口有焦點的時候,即所有的佈局繪制完畢的時候,我們來獲取購買佈局的高度和myScrollView距離父類佈局的頂部位置

        59. */

        60. @Override

        61. public void onWindowFocusChanged(boolean hasFocus) {

        62. super.onWindowFocusChanged(hasFocus);

        63. if(hasFocus){

        64. buyLayoutHeight = mBuyLayout.getHeight();

        65. buyLayoutTop = mBuyLayout.getTop();

        66. myScrollViewTop = myScrollView.getTop();

        67. }

        68. }

        69. /**

        70. * 滾動的回調方法,當滾動的Y距離大於或者等於 購買佈局距離父類佈局頂部的位置,就顯示購買的懸浮框

        71. * 當滾動的Y的距離小於 購買佈局距離父類佈局頂部的位置加上購買佈局的高度就移除購買的懸浮框

        72. *

        73. */

        74. @Override

        75. public void onScroll(int scrollY) {

        76. if(scrollY >= buyLayoutTop){

        77. if(suspendView == null){

        78. showSuspend();

        79. }

        80. }else if(scrollY <= buyLayoutTop + buyLayoutHeight){

        81. if(suspendView != null){

        82. removeSuspend();

        83. }

        84. }

        85. }

        86. /**

        87. * 顯示購買的懸浮框

        88. */

        89. private void showSuspend(){

        90. if(suspendView == null){

        91. suspendView = LayoutInflater.from(this).inflate(R.layout.buy_layout, null);

        92. if(suspendLayoutParams == null){

        93. suspendLayoutParams = new LayoutParams();

        94. suspendLayoutParams.type = LayoutParams.TYPE_PHONE; //懸浮窗的類型,一般設為2002,表示在所有應用程序之上,但在狀態欄之下

        95. suspendLayoutParams.format = PixelFormat.RGBA_8888;

        96. suspendLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL

        97. | LayoutParams.FLAG_NOT_FOCUSABLE; //懸浮窗的行為,比如說不可聚焦,非模態對話框等等

        98. suspendLayoutParams.gravity = Gravity.TOP; //懸浮窗的對齊方式

        99. suspendLayoutParams.width = screenWidth;

        100. suspendLayoutParams.height = buyLayoutHeight;

        101. suspendLayoutParams.x = 0; //懸浮窗X的位置

        102. suspendLayoutParams.y = myScrollViewTop; ////懸浮窗Y的位置

        103. }

        104. }

        105. mWindowManager.addView(suspendView, suspendLayoutParams);

        106. }

        107. /**

        108. * 移除購買的懸浮框

        109. */

        110. private void removeSuspend(){

        111. if(suspendView != null){

        112. mWindowManager.removeView(suspendView);

        113. suspendView = null;

        114. }

        115. }

        116. }


          上面的代碼比較簡單,根據ScrollView滑動的距離來判斷顯示和移除懸浮框,懸浮框的實現主要是通過WindowManager這個類來實現的,調用這個類的addView方法用於添加一個懸浮框,removeView用於移除懸浮框。
          通過上述代碼就實現瞭美團,大眾點評的這種效果,在運行項目之前我們必須在AndroidManifest.xml中加入


          我們運行下項目看下效果吧

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。