Android ScrollView滑動監聽
阿新 • • 發佈:2018-12-25
因為專案裡用到了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);
}
}