Android ScrollView滑动监听

姜泰宁
2023-12-01

     因为项目里用到了ScrollView, 并需要实现类似于ListView的滑动监听回调,所以自定义了一套实现方式支持这些事件, 基本满足了业务需求;

public interface OnMyScrollListener {
    int SCROLL_STATE_FLING = 2;   //手指滑动后松开,自动滑动
    int SCROLL_STATE_IDLE = 0;   //不滑动
    int SCROLL_STATE_TOUCH_SCROLL = 1;   //手指按着屏幕滑动

    void onScrollStateChanged(MyScrollView view, int state);

    void onScroll(MyScrollView view, int y); //滑动距离

    void onScrollToTop();   //滑到顶部

    void onScrollToBottom(); //滑到底部
  }


原理:

  1、判断fling?  ScrollView的fling都会执行ScrollView.fling函数, 所以覆盖该函数并在函数体执行回调, 表示已开始fling;

 2、判断drag? ScrollView有个成员变量mIsBeingDragged,  覆盖onTouch函数判断MOVE事件时反射读取该参数;

    /**
     * True if the user is currently dragging this ScrollView around. This is
     * not the same as 'is being flinged', which can be checked by
     * mScroller.isFinished() (flinging begins when the user lifts his finger).
     */
    private boolean mIsBeingDragged = false;

3、判断idle? 网络上有2个方法: 1、使用延迟消息,判断scroll位置是否发生变化(比较靠谱); 2、反射读取OverScroller的isFinished函数(不靠谱)。 我是用onDraw函数实现的, 滑动结束时会执行多次onDraw函数;

4、判断滑到顶部? 判断scrollY等于0, 即滑动距离为0;

5、判断滑到底部? ScrollView本身高度+上下间距+滑动距离  等于 子View 高度;

日志:

07-08 07:24:49.540 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动状态:IDLE
07-08 07:24:49.847 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动底部
07-08 07:24:51.894 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动状态:DRAG
07-08 07:24:51.931 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动状态:FLING
07-08 07:24:52.975 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动状态:IDLE
    滑到顶部
07-08 07:24:54.757 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动状态:DRAG
07-08 07:24:54.812 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动状态:FLING
07-08 07:24:55.219 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动状态:IDLE
07-08 07:24:55.526 8388-8388/com.byrcegao.myscrollviewtest D/brycegao: 滑动底部

完整代码:

public class MainActivity extends Activity {
  private MyScrollView scrollView;
  private MyScrollView.OnMyScrollListener listener;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    scrollView = (MyScrollView) findViewById(R.id.myscrollview);
    listener = new MyScrollView.OnMyScrollListener() {
      @Override public void onScrollStateChanged(MyScrollView view, int state) {
        String str;
        if (state == 0) {
          str = "IDLE";
        } else if (state == 1) {
          str = "DRAG";
        } else if (state == 2) {
          str = "FLING";
        } else {
          str = "ERROR";
        }
        Log.d("brycegao", "滑动状态:" + str);
      }

      @Override public void onScroll(MyScrollView view, int y) {

      }

      @Override public void onScrollToTop() {
        Log.d("brycegao", "滑到顶部");
      }

      @Override public void onScrollToBottom() {
        Log.d("brycegao", "滑动底部");
      }
    };
  }

  @Override protected void onResume() {
    super.onResume();
    scrollView.addOnScrollListner(listener);
  }

  @Override public void onLocalVoiceInteractionStopped() {
    super.onLocalVoiceInteractionStopped();
    scrollView.removeOnScrollListener(listener);
  }
}


package com.byrcegao.myscrollviewtest;

import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * 自定义ScrollView滑动事件
 * @author brycegao
 */
public class MyScrollView extends ScrollView {
  private ArrayList<OnMyScrollListener> listeners;

  private int currentState;
  private boolean isLastBottom;  //上次刷新是否底部
  private int lastDrawPos;     //上次刷新的纵向位移

  public interface OnMyScrollListener {
    int SCROLL_STATE_FLING = 2;   //手指滑动后松开,自动滑动
    int SCROLL_STATE_IDLE = 0;   //不滑动
    int SCROLL_STATE_TOUCH_SCROLL = 1;   //手指按着屏幕滑动

    void onScrollStateChanged(MyScrollView view, int state);

    void onScroll(MyScrollView view, int y); //滑动距离

    void onScrollToTop();   //滑到顶部

    void onScrollToBottom(); //滑到底部
  }

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

  public MyScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
    listeners = new ArrayList<>();
  }

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

  @Override public boolean onTouchEvent(MotionEvent ev) {
    boolean lastDragState = getDragState();

    boolean ret = super.onTouchEvent(ev);
    if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
      for (OnMyScrollListener listener:listeners) {
        listener.onScroll(this, getScrollY());
      }
    }

    if ((ev.getActionMasked() == MotionEvent.ACTION_UP
        || ev.getActionMasked() == MotionEvent.ACTION_CANCEL)
        && currentState == OnMyScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
      //取消滑动
      currentState = OnMyScrollListener.SCROLL_STATE_IDLE;
      for (OnMyScrollListener listener:listeners) {
        listener.onScrollStateChanged(this, OnMyScrollListener.SCROLL_STATE_IDLE);
      }

    }
    boolean curDragState = getDragState();

    //拖动
    if (!lastDragState && curDragState) {
      currentState = OnMyScrollListener.SCROLL_STATE_TOUCH_SCROLL;
      for (OnMyScrollListener listener:listeners) {
        listener.onScrollStateChanged(this, OnMyScrollListener.SCROLL_STATE_TOUCH_SCROLL);
      }
    }
    return ret;
  }

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (lastDrawPos == getScrollY()
        && getScrollY() > 0
        && currentState == OnMyScrollListener.SCROLL_STATE_FLING ) {
      currentState = OnMyScrollListener.SCROLL_STATE_IDLE;  //设置状态为空闲
      for (OnMyScrollListener listener:listeners) {
        listener.onScrollStateChanged(this, OnMyScrollListener.SCROLL_STATE_IDLE);
      }
    }

    if (getScrollY() == 0 && lastDrawPos == 0
        && currentState != OnMyScrollListener.SCROLL_STATE_IDLE) {
      currentState = OnMyScrollListener.SCROLL_STATE_IDLE;  //设置状态为空闲
      for (OnMyScrollListener listener:listeners) {
        listener.onScrollStateChanged(this, OnMyScrollListener.SCROLL_STATE_IDLE);
        listener.onScrollToTop();
      }
    }

    lastDrawPos = getScrollY();

    if (isCurrentBottom()
        && !isLastBottom) {
      for (OnMyScrollListener listener:listeners) {
        listener.onScrollToBottom();
      }
    }
    isLastBottom = isCurrentBottom();
  }

  @Override public void fling(int velocityY) {
    super.fling(velocityY);

    if (getChildCount() > 0) {
      currentState = OnMyScrollListener.SCROLL_STATE_FLING;
      for (OnMyScrollListener listener:listeners) {
        listener.onScrollStateChanged(this, OnMyScrollListener.SCROLL_STATE_FLING);
      }
    }
  }

  //判断是否滑到底部
  private boolean isCurrentBottom() {
    boolean ret = false;

    int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
    int heightPadding;  //scrollview上下padding之和
    View child = getChildAt(0);
    final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (targetSdkVersion >= Build.VERSION_CODES.M) {
      heightPadding = getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin;
    } else {
      heightPadding = getPaddingTop() + getPaddingBottom();
    }

    if (getMeasuredHeight() - heightPadding + getScrollY() == child.getMeasuredHeight()) {
      ret = true;
    }
    return ret;
  }

  //反射查询mIsBeingDragged
  private boolean getDragState() {
    boolean state = false;
    try {
      Class clz = ScrollView.class;
      Field field = clz.getDeclaredField("mIsBeingDragged");
      field.setAccessible(true);
      state = field.getBoolean(this);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    return state;
  }

  //滑动加速器是否停止
  private boolean isfinishScroll() {
    boolean isfinish=false;
    Class scrollview=ScrollView.class;
    try {
      Field scrollField=scrollview.getDeclaredField("mScroller");
      scrollField.setAccessible(true);
      Object scroller=scrollField.get(this);
      Class overscroller= scrollField.getType();
      Method finishField=overscroller.getMethod("isFinished");
      finishField.setAccessible(true);
      isfinish= (boolean) finishField.invoke(scroller);
    } catch (NoSuchFieldException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (NoSuchMethodException e) {

    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
    return isfinish;
  }

  /**
   * 添加滑动监听
   * @param listener
   */
  public void addOnScrollListner(OnMyScrollListener listener) {
    if (listener == null) {
      throw new IllegalArgumentException("invalid listener");
    }

    if (!listeners.contains(listener)) {
      listeners.add(listener);
    }
  }

  public void removeOnScrollListener(OnMyScrollListener listener) {
    if (listener == null) {
      throw new IllegalArgumentException("invalid listener");
    }

    listeners.remove(listener);
  }
}

 类似资料: