Android · 2015年3月4日 0

Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果

转帖请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming),请尊重他人的辛勤劳动成果,谢谢!

随着移动互联网的快速发展,它已经和我们的生活息息相关了,在公交地铁里面都能看到很多人的人低头看着自己的手机屏幕,从此“低头族”一词就产生了,作为一名移动行业的开发人员,我自己也是一名“低头族”,上下班时间在公交地铁上看看新闻来打发下时间,有时候也会看看那些受欢迎的App的一些界面效果,为什么人家的app那么受欢迎?跟用户体验跟UI设计也有直接的关系,最近在美团和大众点评的App看到如下效果,我感觉用户好,很人性化,所以自己也尝试着实现了下,接下来就讲解下实现思路!

如上图(2)我们看到了,当立即抢购布局向上滑动到导航栏布局的时候,立即抢购布局就贴在导航栏布局下面,下面的其他的布局还是可以滑动,当我们向下滑动的时候,立即抢购的布局又随着往下滑动了,看似有点复杂,但是一说思路可能你就顿时恍然大悟了。

当我们向上滑动过程中,我们判断立即抢购的布局是否滑到导航栏布局下面,如果立即抢购的上面顶到了导航栏,我们新建一个立即抢购的悬浮框来显示在导航栏下面,这样子就实现了立即抢购贴在导航栏下面的效果啦,而当我们向下滑动的时候,当立即抢购布局的下面刚好到了刚刚新建的立即抢购悬浮框的下面的时候,我们就移除立即抢购悬浮框,可能说的有点拗口,既然知道了思路,接下来我们就来实现效果。

新建一个Android项目,取名MeiTuanDemo,先看立即抢购(buy_layout.xml)的布局,这里为了方便我直接从美团上面截去了图片

  1. <?xml version=“1.0” encoding=“UTF-8”?>
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:orientation=“horizontal”
  4.     android:layout_width=“fill_parent”
  5.     android:layout_height=“wrap_content” >
  6.     <ImageView
  7.         android:id=“@+id/buy_layout”
  8.         android:layout_width=“fill_parent”
  9.         android:layout_height=“wrap_content”
  10.         android:background=“@drawable/buy” />
  11. </LinearLayout>

立即抢购的布局实现了,接下来实现主界面的布局,上面是导航栏布局,为了方便还是直接从美团截取的图片,然后下面的ViewPager布局,立即抢购布局,其他布局 放在ScrollView里面,界面还是很简单的

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  2.     xmlns:tools=“http://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.         </LinearLayout>
  45.     </com.example.meituandemo.MyScrollView>
  46. </LinearLayout>

你会发现上面的主界面布局中并不是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.  * 博客地址:http://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.  * 博客地址:http://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中加入<uses-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW” />
我们运行下项目看下效果吧

好了,今天的讲解到此结束,有疑问的朋友请在下面留言

项目源码,点击下载

PS:大家有兴趣的话可以看看Android 仿美团网,大众点评购买框悬浮效果之修改版

 

Share this: