編輯:關於Android編程
一、前言
Android 中解決滑動的方案有2種:外部攔截法 和內部攔截法。
滑動沖突也存在2種場景: 橫豎滑動沖突、同向滑動沖突。
所以我就寫了4個例子來學習如何解決滑動沖突的,這四個例子分別為: 外部攔截法解決橫豎沖突、外部攔截法解決同向沖突、內部攔截法解決橫豎沖突、內部攔截法解決同向沖突。
先上效果圖:
二、實戰
1、外部攔截法,解決橫豎沖突
思路是,重寫父控件的onInterceptTouchEvent方法,然後根據具體的需求,來決定父控件是否攔截事件。如果攔截返回返回true,不攔截返回false。如果父控件攔截了事件,則在父控件的onTouchEvent進行相應的事件處理。
我的這個例子,是一個橫向滑動的ViewGroup裡面包含了3個豎向滑動的ListView。下面我附上代碼,HorizontalEx.Java:
/** * Created by blueberry on 2016/6/20. * * 解決交錯的滑動沖突 * * 外部攔截法 */ public class HorizontalEx extends ViewGroup { private static final String TAG = "HorizontalEx"; private boolean isFirstTouch = true; private int childIndex; private int childCount; private int lastXIntercept, lastYIntercept, lastX, lastY; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalEx(Context context) { super(context); init(); } public HorizontalEx(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalEx(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { width = childCount * getChildAt(0).getMeasuredWidth(); height = getChildAt(0).getMeasuredHeight(); setMeasuredDimension(width, height); } else if (widthMode == MeasureSpec.AT_MOST) { width = childCount * getChildAt(0).getMeasuredWidth(); setMeasuredDimension(width, height); } else { height = getChildAt(0).getMeasuredHeight(); setMeasuredDimension(width, height); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int left = 0; for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); child.layout(left + l, t, r + left, b); left += child.getMeasuredWidth(); } } /** * 攔截事件 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { /*如果攔截了Down事件,則子類不會拿到這個事件序列*/ case MotionEvent.ACTION_DOWN: lastXIntercept = x; lastYIntercept = y; intercepted = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; case MotionEvent.ACTION_MOVE: final int deltaX = x - lastXIntercept; final int deltaY = y - lastYIntercept; /*根據條件判斷是否攔截該事件*/ if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; } lastXIntercept = x; lastYIntercept = y; return intercepted; } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); mVelocityTracker.addMovement(event); ViewConfiguration configuration = ViewConfiguration.get(getContext()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; case MotionEvent.ACTION_MOVE: /*因為這裡父控件拿不到Down事件,所以使用一個布爾值, 當事件第一次來到父控件時,對lastX,lastY賦值*/ if (isFirstTouch) { lastX = x; lastY = y; isFirstTouch = false; } final int deltaX = x - lastX; scrollBy(-deltaX, 0); break; case MotionEvent.ACTION_UP: int scrollX = getScrollX(); final int childWidth = getChildAt(0).getWidth(); mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity()); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) > configuration.getScaledMinimumFlingVelocity()) { childIndex = xVelocity < 0 ? childIndex + 1 : childIndex - 1; } else { childIndex = (scrollX + childWidth / 2) / childWidth; } childIndex = Math.min(getChildCount() - 1, Math.max(childIndex, 0)); smoothScrollBy(childIndex * childWidth - scrollX, 0); mVelocityTracker.clear(); isFirstTouch = true; break; } lastX = x; lastY = y; return true; } void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mVelocityTracker.recycle(); } }
調用代碼:
@Override public void showOutHVData(List<String> data1, List<String> data2, List<String> data3) { ListView listView1 = new ListView(getContext()); ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data1); listView1.setAdapter(adapter1); ListView listView2 = new ListView(getContext()); ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data2); listView2.setAdapter(adapter2); ListView listView3 = new ListView(getContext()); ArrayAdapter<String> adapter3 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data3); listView3.setAdapter(adapter3); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mHorizontalEx.addView(listView1, params); mHorizontalEx.addView(listView2, params); mHorizontalEx.addView(listView3, params); }
其實外部攔截的主要思想都在於對onInterceptTouchEvent的重寫。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { /*如果攔截了Down事件,則子類不會拿到這個事件序列*/ case MotionEvent.ACTION_DOWN: lastXIntercept = x; lastYIntercept = y; intercepted = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; case MotionEvent.ACTION_MOVE: final int deltaX = x - lastXIntercept; final int deltaY = y - lastYIntercept; /*根據條件判斷是否攔截該事件*/ if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; } lastXIntercept = x; lastYIntercept = y; return intercepted; }
這幾乎是一個實現外部攔截事件的模板,這裡一定不要在ACTION_DOWN 中返回 true,否則會讓子VIew沒有機會得到事件,因為如果在ACTION_DOWN的時候返回了 true,同一個事件序列ViewGroup的disPatchTouchEvent就不會在調用onInterceptTouchEvent方法了。
還有就是 在ACTION_UP中返回false,因為如果父控件攔截了ACTION_UP,那麼子View將得不到UP事件,那麼將會影響子View的 Onclick方法等。但這對父控件是沒有影響的,因為如果是父控件子ACITON_MOVE中 就攔截了事件,他們UP事件必定也會交給它處理,因為有那麼一條定律叫做:父控件一但攔截了事件,那麼同一個事件序列的所有事件都將交給他處理。這條結論在我的上一篇文章中已經分析過。
最後就是在 ACTION_MOVE中根據需求決定是否攔截。
2、內部攔截法,解決橫豎沖突
內部攔截主要依賴於父控件的 requestDisallowInterceptTouchEvent方法,關於這個方法我的上篇文章其實已經分析過。他設置父控件的一個標志(FLAG_DISALLOW_INTERCEPT)
這個標志可以決定父控件是否攔截事件,如果設置了這個標志則不攔截,如果沒設這個標志,它就會調用父控件的onInterceptTouchEvent()來詢問父控件是否攔截。但這個標志對Down事件無效。
可以參考一下源碼:
// Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); //清楚標志 resetTouchState(); } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //標志 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; }
那麼我們如果想使用 內部攔截法攔截事件。
第一步:
a、我們要重寫父控件的onInterceptTouchEvent,在ACTION_DOWN的時候返回false,負責的話子View調用requestDisallowInterceptTouchEvent也將無能為力。
b、還有就是其他事件的話都返回true,這樣就把能否攔截事件的權利交給了子View。
第二步:
在子View的dispatchTouchEvent中 來決定是否讓父控件攔截事件。
a. 先要在MotionEvent.ACTION_DOWN:的時候使用mHorizontalEx2.requestDisallowInterceptTouchEvent(true);,負責的話,下一個事件到來時,就交給父控件了。
b. 然後在MotionEvent.ACTION_MOVE: 根據業務邏輯決定是否調用mHorizontalEx2.requestDisallowInterceptTouchEvent(false);來決定父控件是否攔截事件。
上代碼HorizontalEx2.java:
/** * Created by blueberry on 2016/6/20. * 內部攔截 * 和 ListViewEx配合使用 */ public class HorizontalEx2 extends ViewGroup { private int lastX, lastY; private int childIndex; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalEx2(Context context) { super(context); init(); } public HorizontalEx2(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalEx2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { height = getChildAt(0).getMeasuredHeight(); width = childCount * getChildAt(0).getMeasuredWidth(); setMeasuredDimension(width, height); } else if (widthMode == MeasureSpec.AT_MOST) { width = childCount * getChildAt(0).getMeasuredWidth(); setMeasuredDimension(width, height); } else { height = getChildAt(0).getMeasuredHeight(); setMeasuredDimension(width, height); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int leftOffset = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(l + leftOffset, t, r + leftOffset, b); leftOffset += child.getMeasuredWidth(); } } /** * 不攔截Down事件,其他一律攔截 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { if (!mScroller.isFinished()) { mScroller.abortAnimation(); return true; } return false; } else { return true; } } private boolean isFirstTouch = true; @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); mVelocityTracker.addMovement(event); ViewConfiguration configuration = ViewConfiguration.get(getContext()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; case MotionEvent.ACTION_MOVE: if (isFirstTouch) { isFirstTouch = false; lastY = y; lastX = x; } final int deltaX = x - lastX; scrollBy(-deltaX, 0); break; case MotionEvent.ACTION_UP: isFirstTouch = true; int scrollX = getScrollX(); mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity()); float mVelocityX = mVelocityTracker.getXVelocity(); if (Math.abs(mVelocityX) > configuration.getScaledMinimumFlingVelocity()) { childIndex = mVelocityX < 0 ? childIndex + 1 : childIndex - 1; } else { childIndex = (scrollX + getChildAt(0).getWidth() / 2) / getChildAt(0).getWidth(); } childIndex = Math.min(getChildCount() - 1, Math.max(0, childIndex)); smoothScrollBy(childIndex*getChildAt(0).getWidth()-scrollX,0); mVelocityTracker.clear(); break; } lastX = x; lastY = y; return true; } private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,500); invalidate(); } @Override public void computeScroll() { if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mVelocityTracker.recycle(); } }
ListViewEx.java
/** * 內部攔截事件 */ public class ListViewEx extends ListView { private int lastXIntercepted, lastYIntercepted; private HorizontalEx2 mHorizontalEx2; public ListViewEx(Context context) { super(context); } public ListViewEx(Context context, AttributeSet attrs) { super(context, attrs); } public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public HorizontalEx2 getmHorizontalEx2() { return mHorizontalEx2; } public void setmHorizontalEx2(HorizontalEx2 mHorizontalEx2) { this.mHorizontalEx2 = mHorizontalEx2; } /** * 使用 outter.requestDisallowInterceptTouchEvent(); * 來決定父控件是否對事件進行攔截 * @param ev * @return */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mHorizontalEx2.requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: final int deltaX = x-lastYIntercepted; final int deltaY = y-lastYIntercepted; if(Math.abs(deltaX)>Math.abs(deltaY)){ mHorizontalEx2.requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; } lastXIntercepted = x; lastYIntercepted = y; return super.dispatchTouchEvent(ev); } }
調用代碼:
@Override public void showInnerHVData(List<String> data1, List<String> data2, List<String> data3) { ListViewEx listView1 = new ListViewEx(getContext()); ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data1); listView1.setAdapter(adapter1); listView1.setmHorizontalEx2(mHorizontalEx2); ListViewEx listView2 = new ListViewEx(getContext()); ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data2); listView2.setAdapter(adapter2); listView2.setmHorizontalEx2(mHorizontalEx2); ListViewEx listView3 = new ListViewEx(getContext()); ArrayAdapter<String> adapter3 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data3); listView3.setAdapter(adapter3); listView3.setmHorizontalEx2(mHorizontalEx2); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mHorizontalEx2.addView(listView1, params); mHorizontalEx2.addView(listView2, params); mHorizontalEx2.addView(listView3, params); }
至此,2種攔截方法已經學習完畢,下面我們來學習如何解決同向滑動沖突。
其實和上面的2個例子思路是一樣的,只是用來判斷是否攔截的那塊邏輯不同而已。
下面的例子,是一個下拉刷新的一個控件。
3、外部攔截 解決同向滑動沖突
RefreshLayoutBase.java
package com.blueberry.sample.widget.refresh; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ProgressBar; import android.widget.Scroller; import android.widget.TextView; import com.blueberry.sample.R; /** *外部攔截(同向) * */ public abstract class RefreshLayoutBase<T extends View> extends ViewGroup { private static final String TAG = "RefreshLayoutBase"; public static final int STATUS_LOADING = 1; public static final int STATUS_RELEASE_TO_REFRESH = 2; public static final int STATUS_PULL_TO_REFRESH = 3; public static final int STATUS_IDLE = 4; public static final int STATUS_LOAD_MORE =5; private static int SCROLL_DURATION =500; protected ViewGroup mHeadView; protected ViewGroup mFootView; private T contentView; private ProgressBar headProgressBar; private TextView headTv; private ProgressBar footProgressBar; private TextView footTv; private boolean isFistTouch = true; protected int currentStatus = STATUS_IDLE; private int mScreenWidth; private int mScreenHeight; private int mLastXIntercepted; private int mLastYIntercepted; private int mLastX; private int mLastY; protected int mInitScrollY = 0; private int mTouchSlop; protected Scroller mScoller; private OnRefreshListener mOnRefreshListener; public RefreshLayoutBase(Context context) { this(context, null); } public RefreshLayoutBase(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RefreshLayoutBase(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getScreenSize(); initView(); mScoller = new Scroller(context); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setPadding(0, 0, 0, 0); } public void setContentView(T view) { addView(view, 1); } public OnRefreshListener getOnRefreshListener() { return mOnRefreshListener; } public void setOnRefreshListener(OnRefreshListener mOnRefreshListener) { this.mOnRefreshListener = mOnRefreshListener; } private void initView() { setupHeadView(); setupFootView(); } private void getScreenSize() { WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(metrics); mScreenWidth = metrics.widthPixels; mScreenHeight = metrics.heightPixels; } private int dp2px(int dp) { WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(metrics); return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); } /** * 設置頭布局 */ private void setupHeadView() { mHeadView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_head_view, null); mHeadView.setBackgroundColor(Color.RED); headProgressBar = (ProgressBar) mHeadView.findViewById(R.id.head_progressbar); headTv = (TextView) mHeadView.findViewById(R.id.head_tv); /*設置 實際高度為 1/4 ,但內容區域只有 100dp*/ ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mScreenHeight / 4); mHeadView.setLayoutParams(layoutParams); mHeadView.setPadding(0, mScreenHeight / 4 - dp2px(100), 0, 0); addView(mHeadView); } /** * 設置尾布局 */ private void setupFootView() { mFootView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_foot_view, null); mFootView.setBackgroundColor(Color.BLUE); footProgressBar = (ProgressBar) mFootView.findViewById(R.id.fresh_foot_progressbar); footTv = (TextView) mFootView.findViewById(R.id.fresh_foot_tv); addView(mFootView); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int finalHeight = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); finalHeight += child.getMeasuredHeight(); } if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { widthSize = getChildAt(0).getMeasuredWidth(); setMeasuredDimension(widthSize, finalHeight); } else if (widthMode == MeasureSpec.AT_MOST) { widthSize = getChildAt(0).getMeasuredWidth(); setMeasuredDimension(widthSize, height); } else { setMeasuredDimension(widthSize, finalHeight); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int topOffset = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset); topOffset += child.getMeasuredHeight(); } mInitScrollY = mHeadView.getMeasuredHeight() + getPaddingTop(); scrollTo(0, mInitScrollY); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastXIntercepted = x; mLastYIntercepted = y; break; case MotionEvent.ACTION_MOVE: final int deltaY = x - mLastYIntercepted; if (isTop() && deltaY > 0 && Math.abs(deltaY) > mTouchSlop) { /*下拉*/ intercepted = true; } break; case MotionEvent.ACTION_UP: break; } mLastXIntercepted = x; mLastYIntercepted = y; return intercepted; } private void doRefresh() { Log.i(TAG, "doRefresh: "); if (currentStatus == STATUS_RELEASE_TO_REFRESH) { mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); currentStatus = STATUS_IDLE; } else if (currentStatus == STATUS_PULL_TO_REFRESH) { mScoller.startScroll(0,getScrollY(),0,0-getScrollY(),SCROLL_DURATION); if (null != mOnRefreshListener) { currentStatus = STATUS_LOADING; mOnRefreshListener.refresh(); } } invalidate(); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScoller.isFinished()) { mScoller.abortAnimation(); } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_MOVE: if (isFistTouch) { isFistTouch = false; mLastX = x; mLastY = y; } final int deltaY = y - mLastY; if (currentStatus != STATUS_LOADING) { changeScrollY(deltaY); } break; case MotionEvent.ACTION_UP: isFistTouch = true; doRefresh(); break; } mLastX = x; mLastY = y; return true; } private void changeScrollY(int deltaY) { Log.i(TAG, "changeScrollY: "); int curY = getScrollY(); if (deltaY > 0) { /*下拉*/ if (curY - deltaY > getPaddingTop()) { scrollBy(0, -deltaY); } } else { /*上拉*/ if (curY - deltaY <= mInitScrollY) { scrollBy(0, -deltaY); } } curY = getScrollY(); int slop = mInitScrollY / 2; if (curY > 0 && curY <=slop) { currentStatus = STATUS_PULL_TO_REFRESH; } else if (curY > 0 && curY >= slop) { currentStatus = STATUS_RELEASE_TO_REFRESH; } } @Override public void computeScroll() { if (mScoller.computeScrollOffset()) { scrollTo(mScoller.getCurrX(), mScoller.getCurrY()); postInvalidate(); } } /** * 加載完成調用這個方法 */ public void refreshComplete() { mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); currentStatus = STATUS_IDLE; invalidate(); } /** * 顯示 Footer */ public void showFooter() { if(currentStatus==STATUS_LOAD_MORE) return ; currentStatus = STATUS_LOAD_MORE ; mScoller.startScroll(0, getScrollY(), 0, mFootView.getMeasuredHeight() , SCROLL_DURATION); invalidate(); } /** * loadMore完成之後調用 */ public void footerComplete() { mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); invalidate(); currentStatus = STATUS_IDLE; } public interface OnRefreshListener { void refresh(); } abstract boolean isTop(); abstract boolean isBottom(); }
它是一個抽象類,需要編寫子類繼承isTop()和 isBottom()方法。下面給出它的一個實現類:
package com.blueberry.sample.widget.refresh; import android.content.Context; import android.util.AttributeSet; import android.widget.AbsListView; import android.widget.ListView; /** * Created by blueberry on 2016/6/21. * * RefreshLayoutBase 的一個實現類 */ public class RefreshListView extends RefreshLayoutBase<ListView> { private static final String TAG = "RefreshListView"; private ListView listView; private OnLoadListener loadListener; public RefreshListView(Context context) { super(context); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); } public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public ListView getListView() { return listView; } public void setListView(final ListView listView) { this.listView = listView; setContentView(listView); this.listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { /*這裡存在一個bug: 當listView滑動到底部的時候,如果下拉也會出現footer * 這是因為,暫時還沒有想到如何判斷是下拉還是上拉。 * 如果要解決此問題,我覺得應該重寫listView 的onTouchEvent來判斷手勢方向 * 次模塊主要解決豎向滑動沖突,故現將此問題放下。 * */ if (currentStatus == STATUS_IDLE && getScrollY() <= mInitScrollY && isBottom() ) { showFooter(); if (null != loadListener) { loadListener.onLoadMore(); } } } }); } public OnLoadListener getLoadListener() { return loadListener; } public void setLoadListener(OnLoadListener loadListener) { this.loadListener = loadListener; } @Override boolean isTop() { return listView.getFirstVisiblePosition() == 0 && getScrollY() <= mHeadView.getMeasuredHeight(); } @Override boolean isBottom() { return listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1; } public interface OnLoadListener { void onLoadMore(); } }
4、內部攔截法解決同向滑動
同樣是一個下拉刷新組件,因為實現原理都一樣,所以這個寫的比較隨意些。主要還是如果解決滑動沖突。
RefreshLayoutBase2.java
package com.blueberry.sample.widget.refresh; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Scroller; import com.blueberry.sample.R; import java.util.ArrayList; import java.util.List; /** * Created by blueberry on 2016/6/22. * 結合內部類 ListVieEx * 內部攔截法,同向 */ public class RefreshLayoutBase2 extends ViewGroup { private static final String TAG = "RefreshLayoutBase2"; private static List<String> datas; static { datas = new ArrayList<>(); for (int i = 0; i < 40; i++) { datas.add("數據—" + i); } } private ViewGroup headView; private ListViewEx lv; private int lastY; public int mInitScrollY; private Scroller mScroller; public RefreshLayoutBase2(Context context) { this(context, null); } public RefreshLayoutBase2(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RefreshLayoutBase2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScroller = new Scroller(context); setupHeadView(context); setupContentView(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int finalHeight = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); finalHeight += child.getMeasuredHeight(); } if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { widthSize = getChildAt(0).getMeasuredWidth(); setMeasuredDimension(widthSize, finalHeight); } else if (widthMode == MeasureSpec.AT_MOST) { widthSize = getChildAt(0).getMeasuredWidth(); setMeasuredDimension(widthSize, height); } else { setMeasuredDimension(widthSize, finalHeight); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int topOffset = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset); topOffset += child.getMeasuredHeight(); } mInitScrollY = headView.getMeasuredHeight() + getPaddingTop(); scrollTo(0, mInitScrollY); } /** * 不攔截Down 其他一律攔截 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) return false; return true; } @Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: final int deltaY = y-lastY; Log.i(TAG, "onTouchEvent: deltaY: "+deltaY); if (deltaY >= 0 && lv.isTop() && getScrollY() - deltaY >=getPaddingTop()) { scrollBy(0, -deltaY); } break; case MotionEvent.ACTION_UP: this.postDelayed(new Runnable() { @Override public void run() { mScroller.startScroll(0,getScrollY(),0,mInitScrollY-getScrollY()); invalidate(); } },2000); break; } lastY = y ; return true; } private void setupHeadView(Context context) { headView = (ViewGroup) View.inflate(context, R.layout.fresh_head_view, null); headView.setBackgroundColor(Color.RED); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); addView(headView, params); } public void setupContentView(Context context) { lv = new ListViewEx(context, this); lv.setBackgroundColor(Color.BLUE); ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, datas); lv.setAdapter(adapter); addView(lv, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } @Override public void computeScroll() { if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); } } public static class ListViewEx extends ListView { private RefreshLayoutBase2 outter; public ListViewEx(Context context, RefreshLayoutBase2 outter) { super(context); this.outter = outter; } public ListViewEx(Context context, AttributeSet attrs) { super(context, attrs); } public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 使用 outter.requestDisallowInterceptTouchEvent(); * 來決定父控件是否對事件進行攔截 * @param ev * @return */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: outter.requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: if ( isTop() && outter.getScrollY() <= outter.mInitScrollY) { outter.requestDisallowInterceptTouchEvent(false); } break; } return super.dispatchTouchEvent(ev); } public boolean isTop() { return getFirstVisiblePosition() ==0; } } }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
安卓系統近日正在測試一項新功能,在設備儲存空間不足的情況下會自動尋找並給出刪除建議,安卓系統自定義刪除空間功能怎麼用?下載吧小編就一起來參考一下。 安卓系
ListView與GridView是Android開發中的常用控件,它們和Adapter配合使用能夠實現很多界面效果。下面分別以實例
模版方法模式(Template Method):模版方法模式是類的行為模式。提供一個抽象類,把一部分的邏輯以具體方法或構造子形式實現,然後聲明一些抽象方法,迫使子類實現剩
項目中經常需要底部彈出框,這裡我整理一下其中我用的比較順手的一個方式(底部彈出一個橫向滿屏的dialog)。效果圖如下所示(只顯示關鍵部分):步驟如下所示:1.定義一個d