淺談Android View滑動(dòng)沖突的解決方法
引言
這一篇文章我們就通過介紹滑動(dòng)沖突的規(guī)則和一個(gè)實(shí)例來更加深入的學(xué)習(xí)View的事件分發(fā)機(jī)制。
1、外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向不一致
考慮這樣一種場(chǎng)景,開發(fā)中我們經(jīng)常使用ViewPager和Fragment配合使用所組成的頁面滑動(dòng)效果,很多主流的應(yīng)用都會(huì)使用這樣的效果。在這種效果中,可以使用左右滑動(dòng)來切換界面,而每一個(gè)界面里面往往又都是ListView這樣的控件。本來這種情況是存在滑動(dòng)沖突的,只是ViewPager內(nèi)部處理了這種滑動(dòng)沖突。如果我們不使用ViewPager而是使用ScrollView,那么滑動(dòng)沖突就需要我們自己來處理,否者造成的后果就是內(nèi)外兩層只有一層能滑動(dòng)。
情況1的解決思路
對(duì)于第一種情況的解決思路是這樣的:當(dāng)用戶左右滑動(dòng)時(shí),需要讓外層的View攔截點(diǎn)擊事件。當(dāng)用戶上下滑動(dòng)時(shí),需要讓內(nèi)部的View攔截點(diǎn)擊事件(外層的View不攔截點(diǎn)擊事件),這時(shí)候我們就可以根據(jù)它們的特性來解決滑動(dòng)沖突。在這里我們可以根據(jù)滑動(dòng)時(shí)水平滑動(dòng)還是垂直滑動(dòng)來判斷誰來攔截點(diǎn)擊事件。下面先介紹一種通用的解決滑動(dòng)沖突的方法。
外部攔截法
外部攔截法是指:點(diǎn)擊事件都經(jīng)過父容器的攔截處理,如果父容器需要處理此事件就進(jìn)行攔截,否者不攔截交給子View進(jìn)行處理。這種方法比較符合點(diǎn)擊事件的分發(fā)機(jī)制。外部攔截法需要重寫父容器的onInterceptTouchEvent方法,在內(nèi)部做相應(yīng)的攔截即可。這種方法的偽代碼如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int x=(int)ev.getX();
int y=(int)ev.getY();
boolean intercept=false;
switch (ev.getAction()){
//按下事件不要攔截,否則后續(xù)事件都會(huì)給ViewGroup處理
case MotionEvent.ACTION_DOWN:
intercept=false;
break;
case MotionEvent.ACTION_MOVE:
//如果是橫向移動(dòng)就進(jìn)行攔截,否則不攔截
int deltaX=x-mLastX;
int deltaY=y-mLastY;
if(父容器需要當(dāng)前點(diǎn)擊事件){
intercept=true;
}else {
intercept=false;
}
break;
case MotionEvent.ACTION_UP:
intercept=false;
break;
}
mLastX = x;
mLastY = y;
return intercept;
}
上面代碼是外部攔截法的典型邏輯,針對(duì)不同的滑動(dòng)沖突,只需要修改父容器需要當(dāng)前點(diǎn)擊事件的條件即可,其他均不需要修改。我們?cè)诿枋鱿拢涸趏nInterceptTouchEvent方法中,首先是ACTION_DOWN事件,父容器必須返回false,即不攔截ACTION_DOWN事件,這是因?yàn)橐坏└溉萜鲾r截ACTION_DOWN,那么后續(xù)的ACTION_MOVE和ACTION_UP都會(huì)直接交給父容器處理,這時(shí)候事件就沒法傳遞給子元素了;其次是ACTION_MOVE事件,這個(gè)事件可以根據(jù)需要來決定是否需要攔截。
下面來看一個(gè)具體的實(shí)例,這個(gè)實(shí)現(xiàn)模擬ViewPager的效果,我們定義一個(gè)全新的控件,名稱叫HorizontalScrollView。具體代碼如下:
1、我們先看Activity中的代碼:
public class MainActivity extends Activity{
private HorizontalScrollView mListContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
LayoutInflater inflater = getLayoutInflater();
mListContainer = (HorizontalScrollView) findViewById(R.id.container);
final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
for (int i = 0; i < 3; i++) {
ViewGroup layout = (ViewGroup) inflater.inflate(
R.layout.content_layout, mListContainer, false);
layout.getLayoutParams().width = screenWidth;
TextView textView = (TextView) layout.findViewById(R.id.title);
textView.setText("page " + (i + 1));
layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0));
createList(layout);
mListContainer.addView(layout);
}
}
private void createList(ViewGroup layout) {
ListView listView = (ListView) layout.findViewById(R.id.list);
ArrayList<String> datas = new ArrayList<>();
for (int i = 0; i < 50; i++) {
datas.add("name " + i);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.content_list_item, R.id.name, datas);
listView.setAdapter(adapter);
}
}
在這個(gè)代碼中,我們創(chuàng)建了3個(gè)ListView然后將其添加到我們自定義控件的。這里HorizontalScrollView是父容器,ListView是子View。下面我們就使用外部攔截法來實(shí)現(xiàn)HorizontalScrollView,代碼如下:
/**
* 橫向布局控件
* 模擬經(jīng)典滑動(dòng)沖突
* 我們此處使用ScrollView來模擬ViewPager,那么必須手動(dòng)處理滑動(dòng)沖突,否則內(nèi)外兩層只能有一層滑動(dòng),那就是滑動(dòng)沖突。另外內(nèi)部左右滑動(dòng),外部上下滑動(dòng)也同樣屬于該類
*/
public class HorizontalScrollView extends ViewGroup {
//記錄上次滑動(dòng)的坐標(biāo)
private int mLastX = 0;
private int mLastY = 0;
private WindowManager wm;
//子View的個(gè)數(shù)
private int mChildCount;
private int mScreenWidth;
//自定義控件橫向?qū)挾?
private int mMeasureWidth;
//滑動(dòng)加載下一個(gè)界面的閾值
private int mCrital;
//滑動(dòng)輔助類
private Scroller mScroller;
//當(dāng)前展示的子View的索引
private int showViewIndex;
public HorizontalScrollView(Context context){
this(context,null);
}
public HorizontalScrollView(Context context, AttributeSet attributeSet){
super(context,attributeSet);
init(context);
}
/**
* 初始化
* @param context
*/
public void init(Context context) {
//讀取屏幕相關(guān)的長(zhǎng)寬
wm = ((Activity)context).getWindowManager();
mScreenWidth = wm.getDefaultDisplay().getWidth();
mCrital=mScreenWidth/4;
mScroller=new Scroller(context);
showViewIndex=1;
}
/**
* 重新事件攔截機(jī)制
* 我們分析了view的事件分發(fā),我們知道點(diǎn)擊事件的分發(fā)順序是 通過父布局分發(fā),如果父布局沒有攔截,即onInterceptTouchEvent返回false,
* 才會(huì)傳遞給子View。所以我們就可以利用onInterceptTouchEvent()這個(gè)方法來進(jìn)行事件的攔截。來看一下代碼
* 此處使用外部攔截法
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int x=(int)ev.getX();
int y=(int)ev.getY();
boolean intercept=false;
switch (ev.getAction()){
//按下事件不要攔截,否則后續(xù)事件都會(huì)給ViewGroup處理
case MotionEvent.ACTION_DOWN:
intercept=false;
if(!mScroller.isFinished()){
mScroller.abortAnimation();
intercept=true;
}
break;
case MotionEvent.ACTION_MOVE:
//如果是橫向移動(dòng)就進(jìn)行攔截,否則不攔截
int deltaX=x-mLastX;
int deltaY=y-mLastY;
if(Math.abs(deltaX)>Math.abs(deltaY)){
intercept=true;
}else {
intercept=false;
}
break;
case MotionEvent.ACTION_UP:
intercept=false;
break;
}
mLastX = x;
mLastY = y;
return intercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
/**
* scrollX是指ViewGroup的左側(cè)邊框和當(dāng)前內(nèi)容左側(cè)邊框之間的距離
*/
int scrollX=getScrollX();
if(scrollX-deltaX>0
&& (scrollX-deltaX)<=(mMeasureWidth-mScreenWidth)) {
scrollBy(-deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
scrollX=getScrollX();
int dx;
//計(jì)算滑動(dòng)的差值,如果超過1/4就滑動(dòng)到下一頁
int subScrollX=scrollX-((showViewIndex-1)*mScreenWidth);
if(Math.abs(subScrollX)>=mCrital){
boolean next=scrollX>(showViewIndex-1)*mScreenWidth;
if(showViewIndex<3 && next) {
showViewIndex++;
}else {
showViewIndex--;
}
}
dx=(showViewIndex - 1) * mScreenWidth - scrollX;
smoothScrollByDx(dx);
break;
}
mLastX = x;
mLastY = y;
return true;
}
/**
* 緩慢滾動(dòng)到指定位置
* @param dx
*/
private void smoothScrollByDx(int dx) {
//在1000毫秒內(nèi)滑動(dòng)dx距離,效果就是慢慢滑動(dòng)
mScroller.startScroll(getScrollX(), 0, dx, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
}
從上面代碼中,我們看到我們只是很簡(jiǎn)單的采用橫向滑動(dòng)距離和垂直滑動(dòng)距離進(jìn)行比較來判斷滑動(dòng)方向。在滑動(dòng)過程中,當(dāng)水平方向的距離大時(shí)就判斷為水平滑動(dòng),否者就是垂直滑動(dòng)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android listview的滑動(dòng)沖突解決方法
- android多種滑動(dòng)沖突的解決方案
- Android下拉刷新與輪播圖滑動(dòng)沖突解決方案
- Android滑動(dòng)沖突的完美解決
- 淺談Android實(shí)踐之ScrollView中滑動(dòng)沖突處理解決方案
- Android中RecyclerView嵌套滑動(dòng)沖突解決的代碼片段
- android中view手勢(shì)滑動(dòng)沖突的解決方法
- Android滑動(dòng)沖突的完美解決方案
- Android App中ViewPager所帶來的滑動(dòng)沖突問題解決方法
- Android中DrawerLayout+ViewPager滑動(dòng)沖突的解決方法
相關(guān)文章
Android開發(fā)之全屏與非全屏的切換設(shè)置方法小結(jié)
這篇文章主要介紹了Android開發(fā)之全屏與非全屏的切換設(shè)置方法,結(jié)合實(shí)例形式分析了Android全屏切換靜態(tài)與動(dòng)態(tài)兩種實(shí)現(xiàn)方法,需要的朋友可以參考下2017-08-08
Android JNI c/c++調(diào)用java的實(shí)例
這篇文章主要介紹了Android JNI c/c++調(diào)用java的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-07-07
Python基礎(chǔ)教程學(xué)習(xí)筆記 第二章 列表和元組
這篇文章主要介紹了Python基礎(chǔ)教程學(xué)習(xí)筆記 第二章 列表和元組,需要的朋友可以參考下2015-03-03
Android手機(jī)衛(wèi)士之設(shè)置密碼對(duì)話框
這篇文章主要為大家詳細(xì)介紹了Android手機(jī)衛(wèi)士之設(shè)置密碼對(duì)話框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Android studio將Module打包成Jar的方法
這篇文章主要介紹了Android studio將Module打包成Jar的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10
深入學(xué)習(xí)Kotlin?枚舉的簡(jiǎn)潔又高效進(jìn)階用法
這篇文章主要為大家介紹了深入學(xué)習(xí)Kotlin?枚舉簡(jiǎn)潔又高效的進(jìn)階用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05

