View的滑動沖突場景
常見的滑動沖突可以簡單分為如下三種
- 場景1:外部滑動方向和內(nèi)部滑動方向不一致
- 場景2:外部滑動方向和內(nèi)部滑動方向一致
-
場景3:上面兩種情況的嵌套
View的滑動沖突解決方式
- 外部攔截法:在onInterceptTouchEvvent方法中若债,首先在ACTION_DOWN這個(gè)事件以躯,父容器必須放回false报嵌,即不攔截ACTION_DOWN事件这难,這是因?yàn)橐坏└溉萜鲾r截了ACTION_DOWN,那么后續(xù)的ACTION_MOVE和ACTION_UP事件直接傳遞給父容器處理,沒法傳遞給子元素副瀑。
- 內(nèi)部攔截法:父容器不攔截任何事件,所有的事情都傳遞給子元素恋谭。如果子元素需要此事件就直接消耗掉糠睡,否則就交由父容器進(jìn)行處理。如下代碼是內(nèi)部攔截法的典型代碼疚颊,除了子元素需要做處理以外狈孔,父元素也要默認(rèn)攔截除了ACTION_DOWN以外的其他事件。
場景1的沖突材义,利用外部攔截法解決
1. 外部攔截法常規(guī)解決思路均抽,重寫onInterceptTouchEvent方法。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
intercept = false;
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
if(父容器需要當(dāng)前點(diǎn)擊事件){
intercept = true;
}else{
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
break;
}
return intercept;
}
2. 場景1沖突實(shí)例
一個(gè)可以水平滑動的HorizontalScrollView和一個(gè)垂直滑動的ListView就會產(chǎn)生滑動沖突其掂。其實(shí)在ViewPager+ListView是不會有滑動沖突的油挥,因?yàn)閂iewPager內(nèi)部已經(jīng)解決。
思路:判斷水平滑動還是垂直滑動款熬,可以通過在水平方向上deltaX和垂直方向上deltaY的絕對比較就可以明白用戶想要水平還是垂直滑動喘漏。
自定義MyViewPager代碼
public class MyViewPager extends HorizontalScrollView {
private boolean intercept;
private float lastX,lastY,x,y;
private float deltaX,deltaY;
private float totalX = 0;
private static final String TAG = "MyViewPager";
private LinearLayout mContentLL;
public MyViewPager(Context context) {
this(context,null);
}
public MyViewPager(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContentLL = findViewById(R.id.contentLayout);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent: ");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
intercept = false;
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercept = false;
lastX = ev.getX();
lastY = ev.getY();
Log.i(TAG, "onInterceptTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
x = ev.getX();
y = ev.getY();
deltaX = x - lastX;
deltaY = y - lastY;
Log.i(TAG, "onInterceptTouchEvent: ACTION_MOVE: deltX = " + deltaX + " , deltY = " + deltaY);
if(Math.abs(deltaX) > Math.abs(deltaY)){//水平滑動 , 當(dāng)前父ViewGroup攔截后交給onTouchEvent去處理具體的操作
intercept = true;
}else{
intercept = false;
}
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
intercept = false;
Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
break;
}
return intercept;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = ev.getX();
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
x = ev.getX();
deltaX = x - lastX;
totalX += deltaX;
Log.i(TAG, "onTouchEvent: totalX = " + totalX + " , deltaX = " + deltaX);
if(mContentLL != null){
mContentLL.scrollBy((int) -deltaX,0);
}
lastX = x;
break;
}
return super.onTouchEvent(ev);
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.chenpeng.julyapplication.SlidingConflict.MyViewPager
android:layout_width="1500dp"
android:layout_height="match_parent"
>
<LinearLayout
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ListView
android:id="@+id/listView1"
android:layout_width="500dp"
android:layout_height="match_parent"/>
<ImageView
android:layout_width="500dp"
android:layout_height="match_parent"
android:background="@mipmap/bg"/>
<ListView
android:id="@+id/listView2"
android:layout_width="500dp"
android:layout_height="match_parent"/>
</LinearLayout>
</com.example.chenpeng.julyapplication.SlidingConflict.MyViewPager>
</android.support.constraint.ConstraintLayout>
完美解決沖突
場景2的沖突,利用內(nèi)部攔截法解決
1. 內(nèi)部攔截法解決思路华烟,重寫子元素的dispatchTouchEvent方法,和父元素的onInterceptTouchEvent方法持灰。
父元素的onInterceptTouchEvent盔夜。ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)記位的控制,所以一旦父容器攔截ACTION_DOWN事件堤魁,那么所有的事件都無法傳遞到子元素中去喂链,這樣內(nèi)部攔截就無法起作用。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if(action == MotionEvent.ACTION_DOWN){
return false;
}else {
return true;
}
}
子元素的onInterceptTouchEvent妥泉。內(nèi)部攔截法的典型代碼椭微,面對不同的滑動策略時(shí)只需要修改里面的條件即可。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(父容器需要此類事件){//事件處理交個(gè)父容器
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
2. 場景2沖突實(shí)例
一個(gè)可以垂直滑動的MyVerticalViewGroup盲链,內(nèi)部包含一個(gè)可以垂直滑動的MyRecyclerView蝇率,兩者都可以垂直滑動,需要根據(jù)自己需求判斷垂直滑動時(shí)到底滑動哪一個(gè)控件刽沾。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.chenpeng.julyapplication.SlidingConflict.MyVerticalViewGroup
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="1000dp"
android:orientation="vertical">
<ImageView
android:id="@+id/imageView"
android:layout_marginTop="-200dp"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@mipmap/bg"/>
<View
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#789933"/>
<com.example.chenpeng.julyapplication.SlidingConflict.MyRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="800dp"/>
</com.example.chenpeng.julyapplication.SlidingConflict.MyVerticalViewGroup>
</android.support.constraint.ConstraintLayout>
自定義MyVerticalViewGroup
public class MyVerticalViewGroup extends LinearLayout {
private LinearLayout mContentll;
private float y,lastY,deltaY;
private int mImageViewHeight;
private static final String TAG = "SlidingView03";
public MyVerticalViewGroup(Context context) {
this(context,null);
}
public MyVerticalViewGroup(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyVerticalViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContentll = findViewById(R.id.contentLayout);
mImageViewHeight = (int) (200 * getResources().getDisplayMetrics().density+0.5);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if(action == MotionEvent.ACTION_DOWN){
lastY = ev.getY();
return false;
}else {
lastY = ev.getY();
return true;
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {//父容器的事件處理
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
lastY = ev.getY();
Log.i(TAG, "parent onTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
y = ev.getY();
deltaY = y - lastY;
if(mContentll != null){
mContentll.scrollBy(0, -(int) deltaY);
}
lastY = y;
Log.i(TAG, "parent onTouchEvent: ACTION_MOVE ACTION_UP");
break;
}
return super.onTouchEvent(ev);
}
@Override
public void scrollTo(int x, int y) {
//邊界控制
if(y > 0 ){
y = 0;
}
if( y < 0 && Math.abs(y) > mImageViewHeight){
y = -mImageViewHeight;
}
super.scrollTo(x, y);
}
}
自定義MyRecyclerView
public class MyRecyclerView extends RecyclerView {
private static final String TAG = "SlidingView03";
private float y,lastY,deltaY;
public MyRecyclerView(Context context) {
this(context,null);
}
public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
lastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
y = ev.getY();
deltaY = y - lastY;//deltaY > 0 表示向下滑動 本慕; deltaY < 0 表示向上滑動
lastY = y;
boolean isRecyclerViewOnTop = isTop();
Log.i(TAG, "child dispatchTouchEvent: deltaY = " + deltaY + " ,isTop() = " + isRecyclerViewOnTop + ", ViewCompat.canScrollVertically(this, 1) = " + ViewCompat.canScrollVertically(this, 1));
if(( isRecyclerViewOnTop && deltaY > 0 ) || ( deltaY < 0 && !ViewCompat.canScrollVertically(this, 1))){//事件處理交個(gè)父容器 -1判讀是否可以下滑 1判斷是否可以上滑
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
// Log.i(TAG, "onTouchEvent: ACTION_DOWN");
lastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
y = ev.getY();
deltaY = y - lastY;
lastY = y;
// Log.i(TAG, "onTouchEvent: ACTION_MOVE totalY = " + totalY + " , y = " + y + " ,lastY = " + lastY);
break;
case MotionEvent.ACTION_UP:
// Log.i(TAG, "onTouchEvent: ACTION_UP");
lastY = ev.getY();
break;
}
return super.onTouchEvent(ev);
}
public boolean isTop(){
LinearLayoutManager layoutManager = (LinearLayoutManager) this.getLayoutManager();
int position = layoutManager.findFirstVisibleItemPosition();
View firstVisibleChildView = layoutManager.findViewByPosition(position);
Log.i(TAG, "isTop: position = 0" +" , firstVisibleChildView.getTop() = " + firstVisibleChildView.getTop() + ",RecyclerView " + getScrollY() );
if(position == 0 && firstVisibleChildView.getTop() == 0){
return true;
}else{
return false;
}
}
}
實(shí)例演示
總結(jié)
- 場景3的情況就是場景1和場景2的結(jié)合,方式一樣就是復(fù)雜一些侧漓。
- 個(gè)人喜歡用外部攔截法锅尘,感覺方便一些
- 場景2中會有一個(gè)問題,就是一旦事件交個(gè)子元素或者父容器處理布蔗,他們會一管到底藤违。知道ACTION_UP為止浪腐。利用Android自帶嵌套滑動控件解決滑動沖突(NestingScroll,CoordinatorLayout與Behavior)可以很好的解決。
如何成為自定義高手(一)繪制
如何成為自定義高手(二)動畫
如何成為自定義高手(三)布局
如何成為自定義高手(四)觸摸反饋顿乒,事件分發(fā)機(jī)制
如何成為自定義高手(五)多點(diǎn)觸摸
如何成為自定義高手(六)滑動和拖拽
如何成為自定義高手(七)滑動沖突
利用Android自帶嵌套滑動控件解決滑動沖突(NestingScroll,CoordinatorLayout與Behavior)