android-----滑動衝突解決案例
之前的幾篇部落格,我測試了View事件分發機制中的一些知識點,我們理解事件分發機制的目的就是為了能夠更好了知道View中事件的傳遞過程進而能夠對於滑動衝突有針對性的解決措施,今天我們通過一個翻頁例項來學習下滑動處理的方式之一-----外部攔截法;
因為要用到翻頁,那麼不可避免的要用到Scroller類,其實拿scrollBy和scrollTo也能做到翻頁的效果,但不足是兩者都是在瞬間完成對View內容的移動,使用者體驗度不好,注意這裡是View內容的移動而不是View本身的移動,而Scroll類卻能夠進行平滑的移動,原因在於他將大的滑動根據設定的時間段分割成多個小的滑動,這樣漸進式的移動體驗度明顯好於前者;
在正式講解案例之前,我們有必要稍微瞭解下Scroller類:
我們平常使用Scroller的過程如下:
(1)建立Scroller例項;
(2)通過Scroller例項呼叫startScroll進行滑動事件開始之前的一些設定;
(3)呼叫startScroll之後需要呼叫invalidate來進行View重繪,而View重繪中的draw方法就會呼叫computeScroll方法,這個方法在View中是一個空實現,也就是說我們想要實現彈性滑動就需要重寫computeScroll方法,在這個方法裡面我們可以通過Scroller例項獲取到當前滑動到的位置的scrollX以及scrollY,接著呼叫scrollTo來移動到這個位置即可,在scrollTo呼叫結束之後我們同樣需要呼叫invalidate或者postInvalidate來進行View的重繪,原因很簡單,因為你總不能移動一次就結束吧,後面的移動也需要重繪View的呢,那麼很多人就在想你這裡不也是使用的scrollT麼?為什麼你就能實現彈性滑動呢?第(4)點解釋原因;
(4)其實在上面第(3)點中少說了一個判斷條件,那就是當computeScrollOffset返回true的時候我們才會通過Scroller例項去獲得scrollX以及scrollY,computeScrollOffset返回true表示我們的滑動還沒有停止,還需要繼續滑動,返回false表示滑動已經結束了,這時候當然不再需要呼叫invalidate或者postInvalidate來進行View的重繪了;
通過上面的講述我們知道了在Scroller中最關鍵的兩個方法是startScroll和computeScrollOffset以及View中沒有實現的computeScroll方法,下面從原始碼角度分析下前兩個方法,之後進入例項:
Scroller$startScroll
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / mDuration;
}
startScroll的四個引數startX和startY表示滑動起點,dx和dy表示滑動的距離,duration表示滑動的時間,可以看到我們把滑動模式設定為是SCROLL_MODE,設定mDuration這個事件預設是250ms,設定開始滑動時間mStartTime,並且將滑動起點、距離等進行相應的賦值操作;
Scroller$computeScrollOffset
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished. loc will be altered to provide the
* new location.
*/
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE[index];
final float d_sup = SPLINE[index + 1];
final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf);
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
if (mCurrX == mFinalX && mCurrY == mFinalY) {
mFinished = true;
}
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
剛開始判斷滑動是否結束了,結束的話執行第8行返回false,這也就驗證說明computeScrollOffset返回false的話表示滑動停止,否則的話會執行第13行,判斷滑動事件是否在mDuration時間範圍內,因為我們設定的mMode值是SCROLL_MODE,執行16--25行,設定mCurrX以及mCurrY,有了這兩個值之後我們就可以在computeScroll中呼叫getCurrX以及getCurrY來獲取到這兩個值,並且通過scrollTo來移動到這個位置了;
好了,我們該開始例項部分了:
先來看看效果:
我們採用三個Fragment實現了三個切換的頁面,其中第1個Fragment包含有一個ListView,因為ListView本身是上下滑動的,而我們這裡引入了左右滑動,那麼這種情況下可能就會出現滑動衝突了;第2和3個Fragment分別是顯示一張圖片,你叫簡單,下面退出來各自的佈局檔案:
fragment1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
就僅僅是包含一個ListView而已;
fragment2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/paoche1">
</ImageView>
</LinearLayout>
僅僅包含一個ImageView;
fragment3.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/paoche2">
</ImageView>
</LinearLayout>
同樣僅僅包含一個ImageView;
接下來是三個Fragment的程式碼:
Fragment1.java
public class Fragment1 extends Fragment{
public ListView mListView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String[] strArray = new String[20];
for(int i = 0;i < 20;i++)
strArray[i] = "Item"+i;
MyListAdapter adapter = new MyListAdapter(strArray, this.getActivity());
View view = inflater.inflate(R.layout.fragment1, container,false);
mListView = (ListView) view.findViewById(R.id.listView);
mListView.setAdapter(adapter);
return view;
}
}
因為我們的Fragment1裡面有ListView,所以我們需要在他上面繫結資料,這些繫結操作是在Fragment1的onCreate方法裡面進行的;
Fragment2.java
public class Fragment2 extends Fragment{
public ImageView mImageView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment2, container,false);
return view;
}
}
就是簡單的返回fragment2對應的View;
Fragment3.kava
public class Fragment3 extends Fragment{
public ImageView mImageView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment3, container,false);
return view;
}
}
同樣也只是簡單的返回fragment3對應的View;主介面的佈局activity_main.xml:
<com.hzw.dealslideconflict.ScrollLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/fragment1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.hzw.dealslideconflict.Fragment1"/>
<fragment
android:id="@+id/fragment2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.hzw.dealslideconflict.Fragment2"/>
<fragment
android:id="@+id/fragment3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.hzw.dealslideconflict.Fragment3"/>
</com.hzw.dealslideconflict.ScrollLayout>
可以看到主介面將三個Fragment新增進來,為了到達三個Fragment分別是三個頁面的效果,每個Fragment的layout_width以及layout_height都設定成了match_parent,注意到最外面的佈局是我們自己定義的,定義程式碼如下:
public class ScrollLayout extends ViewGroup{
//左邊界值
public int leftBoard;
//右邊界值
public int rightBoard;
//DOWN事件時x的位置
public float mXDown;
//上一次MOVE事件的x座標
public float mXLastMove;
//當前MOVE事件的x座標
public float mXMove;
//被認為是滑動的最短距離
public int mTouchSlop;
//獲取彈性滑動物件
public Scroller mScroller;
public ScrollLayout(Context context) {
super(context);
}
public ScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//獲取被認為是最短的滑動距離,也就是說滑動超過mTouchSlop才算是滑動
ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
mScroller = new Scroller(getContext());
}
public ScrollLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取子View的個數
int childCount = getChildCount();
//對每個子View進行measure操作
for(int i = 0;i < childCount;i++)
{
View childView = getChildAt(i);
//測量每個子View
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//如果發生變化,則對他進行重新佈局
if(changed)
{
int childCount = getChildCount();
for(int i = 0;i < childCount;i++)
{
View childView = getChildAt(i);
childView.layout(i*childView.getMeasuredWidth(), 0, (i+1)*childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
leftBoard = getChildAt(0).getLeft();//獲取左邊界值
rightBoard = getChildAt(childCount-1).getRight();//獲取右邊界值
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mXDown = ev.getRawX();//獲取DOWN事件下相對於螢幕的x座標
mXLastMove = mXDown;//初始化上一次MOVE事件的x座標
break;
case MotionEvent.ACTION_MOVE:
mXMove = ev.getRawX();
float slideDistance = Math.abs(mXMove-mXLastMove);
mXLastMove = mXMove;
if(slideDistance > mTouchSlop)
{
//表示是翻頁操作,攔截事件,攔截事件之後接下來的MOVE和UP事件都將由該View來處理
return true;
}
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
//實現具體的移動操作
mXMove = event.getRawX();
int xScrolled = (int)(mXLastMove - mXMove);
System.out.println(getScrollX()+xScrolled+getWidth());
//這裡的getScrollX的值等於View的左邊緣和View內容左邊緣的距離
if(getScrollX()+xScrolled < leftBoard)
{
//表示超出了左邊界,那麼我們需要讓其直接滑到左邊界
scrollTo(leftBoard, 0);
return true;
}else if(getScrollX()+xScrolled+getWidth() > rightBoard)
{
//表示超出了右邊界,那麼我們需要讓其直接滑到右邊界
scrollTo(rightBoard-getWidth(), 0);
return true;
}
mXLastMove = mXMove;
scrollBy(xScrolled,0);
break;
case MotionEvent.ACTION_UP:
//在UP事件中要判斷我們現在已經滑動到的位置,如果已經滑動超過一半的螢幕,那麼應該翻到下一頁,不到一半的話應該回退滑動
int index = (getScrollX()+getWidth()/2) / getWidth();
int distance = index*getWidth() - getScrollX();
mScroller.startScroll(getScrollX(), 0, distance, 0);
invalidate();//重新繪製
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
//這個方法會在draw方法中呼叫
//判斷滑動是否完成,返回true表示滑動沒有完成
if(mScroller.computeScrollOffset())
{
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
}
來解釋下這段程式碼的實現:
首先在第22行的建構函式中建立了Scroller例項同時給mTouchSlop進行賦值,這裡的mTouchSlop指的是系統認為的被認為是滑動的最小距離,獲方法就是25/26行程式碼;
接著我們重寫了onMeasure方法,該方法是用來測量View的,第44行呼叫ViewGroup的measureChild方法進行測量;
接著重寫onLayout方法來對View進行佈局,因為我們的翻頁操作是在水平方向進行的,所以可以看到第56行layout引數只有水平方向的左右有值;第58行和59行獲取左右邊界值,為了防止我們已經滑到最左邊或者最右邊了繼續滑動還會出現頁面的情況;
接著onInterceptTouchEvent的DOWN事件中主要進行的是mXDown以及mXLastMove的初始化操作;在MOVE事件中,首先獲取到當前滑動到的位置mXMove,計算出當前位置與上次mXLastMove的絕對值差,第74行判斷這個值是否大於預設的認為是滑動的最短距離,如果大於的話會直接返回true來攔截MOVE事件,這樣MOVE事件就不會傳遞到當前View的子View上了,也就是說這時候進行的是水平滑動了,不再會進行ListView的上下滑動;
在onInterceptTouchEvent攔截MOVE事件後就會執行當前View的onTouchEvent方法,我們來看看MOVE操作部分,首先當然是獲取到當前位置,接著第97行是在判斷有沒有超出左邊界,超出的話執行100行直接呼叫scrollTo讓View滑到左邊界處;第102行判斷有沒有超出右邊界,有的話執行第105行直接執行scrollTo將當前View滑動到右邊界處;如果沒有超出左邊界或者右邊界的話,執行第109行,注意這裡是scrollBy,因為我們這裡的移動是相對於上一次的移動,當然你可以使用scrollTo,但是每次你需要計算出絕對移動的座標;
在我們結束滑動時會執行第111行的UP方法,該方法裡面首先判斷我們在釋放之後要滑動到哪個Fragment裡面,這裡的滑動就用到了Scroller類了,因為如果手釋放之後一下子滑動到某一個位置,這樣使用者體驗度一點都不好,這裡的index就是我們將要滑到的Fragment編號,接著計算出滑動距離,呼叫Scroller的startScroll開始滑動,呼叫invalidate進行重繪,invalidate轉而會去執行draw方法,而draw會去執行computeScroll,也就是我們這裡重寫的computeScroll,該方法裡面首先通過computeScrollOffset判斷滑動沒有停止的話,獲取當前位置,呼叫scrollTo進行滑動,隨後同樣呼叫invalidate,這樣間接的在遞迴呼叫computeScroll,直到滑動結束,達到了彈性滑動的效果;
注意一點的是:在onTouchEvent的DOWN下面要加return true這行程式碼;也就是第90行程式碼,原因在於,如果你的Fragment裡面是ImageView或者TextView的話,預設情況下是沒有為他們設定clickable以及longclickable屬性的,這回導致當事件傳遞到他們上面的時候他們的onTouchEvent返回false,ScrollLayout作為他們的父View,他的onTouchEvent方法預設返回false,這會導致隨後到來的MOVE事件將不再會由當前Fragment的父View執行,也就是你會發現你的程式將不再能滑動翻頁;
最後就是MainActivity的程式碼了,他繼承自FragmentActivity:
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
這樣,這個例子就講解結束了;上面我們採用的解決滑動衝突的方法是外部攔截法,也就是說因為事件首先傳遞給父View,那麼我們首先在父View上判斷需不需要攔截這個事件,我們的這個例子很簡單,只要我們在水平方向MOVE的距離大於了系統預設認為滑動的距離,我們就攔截事件,當然實際中可以通過速度、水平方向移動距離大於豎直方向等等來進行判斷,我們例項中的ListView是上下移動的,而我們的頁面切換是水平移動的,當出現水平移動的時候就讓當前父View攔截了事件,而不會將他傳遞給ListView了,這也就解決了滑動衝突啦!
相關推薦
android-----滑動衝突解決案例
之前的幾篇部落格,我測試了View事件分發機制中的一些知識點,我們理解事件分發機制的目的就是為了能夠更好了知道View中事件的傳遞過程進而能夠對於滑動衝突有針對性的解決措施,今天我們通過一個翻頁例項來學習下滑動處理的方式之一-----外部攔截法;
Android滑動衝突解決方法
敘述 滑動衝突可以說是日常開發中比較常見的一類問題,也是比較讓人頭疼的一類問題,尤其是在使用第三方框架的時候,兩個原本完美的控制元件,組合在一起之後,忽然發現整個世界都不好了。 關於滑動衝突 滑動衝突分類### 滑動衝突,總的來說就是兩類。 同方向滑動衝突 比
Android滑動衝突解決方式(下拉重新整理上拉載入更多,適配RecyclerView/ListView/ScrollView)
@Override public boolean judgeIntercept(float curInterceptY, float lastInterceptY, boolean isHeaderShow, boolean isFooterShow, boolean allowLoadM
Android PtrFrameLayout 與RecyclerView滑動衝突解決方法
原文地址:https://blog.csdn.net/Simon_Crystin/article/details/80926795 1.背景 2.主要程式碼邏輯 2.1 父View程式碼(PtrFrameLayout) 2.2 子View關鍵程式碼(RecyclerView)
Android ScrollView巢狀Banner於RecyclerView滑動衝突解決
首先是佈局 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http:/
Android 滑動衝突的解決方法
一、常見的滑動衝突場景 場景1——外部滑動方向和內部滑動方向不一致,如:ViewPager中有多個fragment,而fragment中有ListView,這時ViewPager可以左右滑動,而ListView可以上下滑動,這就造成了滑動衝突。注意:這只是舉個例子說明一下場
webview輪播圖與Android滑動衝突的解決辦法
在Android中我們經常需要左右滑動,其中內嵌的web頁面也有滑動動作。輪播圖舉例:需要左右翻頁時,會觸發Android中的ViewPager導致翻頁到另一個頁面輪播圖翻頁翻不動,或失效。但是由於安卓是父,而Web是子。在Web中不論你做什麼處理,都管不了父視窗的事情,所以
Android控制元件-ScrollView 和WebView之見滑動衝突解決
需求: 最近在做一個webView載入網頁的頁面,最外層是一個scrollView,因為還有標題等其他資料是需要單獨獲取載入,所以scrollview中是包含一個其他資訊的頭部佈局和一個載入網頁資訊的WebView,當滑動的時候,頭部和We
android滑動衝突的解決方案
一、前言 Android 中解決滑動的方案有2種:外部攔截法 和內部攔截法。 滑動衝突也存在2種場景: 橫豎滑動衝突、同向滑動衝突。 所以我就寫了4個例子來學習如何解決滑動衝突的,這四個例子分別為: 外部攔截法解決橫豎衝突、外部攔截法解決同向衝突、內部
android-Ultra-Pull-To-Refresh重新整理框架與viewpager滑動衝突解決方案
文章概述: 問題描述: liaohuqiu 開源的 android-Ultra-Pull-To-Refresh 下拉重新整理框架,在使用時,會經常遇到巢狀banner的使用場景,即:子ViewGroup巢狀ViewPager使用,例如: <c
android ListView/GridView與ScrollView巢狀的滑動衝突解決
首先說一下思路,主要就是去掉子ListView/GridView的內容全部顯示出來,使其不需要滑動。然後用ScrollView將其包裹在其中,接管滑動事件,達到整個佈局的滑動效果。 實際做法需要將ListView/GridView 與 ScrollView 覆
android-Ultra-Pull-To-Refresh/SwipeRefreshLayout巢狀ViewPager/ScrollView滑動衝突解決
前戲 每次必不可少的前戲又來了。發文時Android-PullToRefresh這個框架已經停止維護3年了,很多人在關心我們現在用什麼框架好,這裡給大家推薦兩個。一個是可愛可親起可恨的Google官方v4包自帶的SwipeRefreshLa
android ViewPager與ScrollView滑動衝突解決
內部解決法: 重寫ViewPager的dispatchTouchEvent方法判斷滑動傾向 import android.con
【朝花夕拾】Android自定義View篇之(七)Android事件分發機制(下)滑動衝突解決方案總結
前言 轉載請宣告,轉自【https://www.cnblogs.com/andy-songwei/p/11072989.html】,謝謝! 前面兩篇文章,花了很大篇幅講解了Android的事件分發機制的
ScrollView與ListView(ExpandableListView)的滑動衝突解決方法
在Android開發中,如果外層使用ScrollView巢狀ListView(ExpandableListView),以下統一稱為ListView,會導致ListView的顯示高度變窄,甚至不能實現螢幕外內容的括展,那麼滑動衝突就出現了。 解決思路: 思路一: 在XML中將高度固定
解決SwipeRefreshLayout+RecyclerView滑動衝突解決
sf_brand.setOnChildScrollUpCallback(new SwipeRefreshLayout.OnChildScrollUpCallback() { @
滑動衝突解決
外層可上下滑動,內層也可上下滑動情況: a.外層onInterceptTouchEvent中,down事件不攔截,move事件根據實際情況進行攔截(returen true),在onTouchEvent的move事件中按條件進行處理即可; b.外層不做攔截,內層dispatchTouchEv
android 滑動衝突
在android開發中會經常遇到各種問題,比較常見之一就有滑動衝突問題,只要出現滑動衝突,demo或者專案就無法正常工作, 那麼問題是滑動衝突時怎麼產生的,我們又應該如何解決這個滑動衝突問題那,其實在介面中只要內外兩層同時可以滑動,這個時候就會產生滑動衝突,我們如何解決這個
橫豎橫ScrollView巢狀時滑動衝突解決
以前其實解決過類似的問題,當時是ViewPager巢狀的衝突問題,沒有做記錄,所以這次又費力研究半天,想想還是把程式碼和思路記錄下來方便以後參考。 首先是最外層的HorizontalScrollView(後面簡稱HS)中的控制,當內部有一個豎向的Scrol
ViewPager 常見滑動衝突解決方法
情況1:ViewPager中的第一個child View 中的子元素要求可以水平滑動. 在實際中遇到的問題是ViewPager中的第一個child View 中的子元素一件拿到了Touch(acti