*運項目痛點之ScrollView中嵌套百度地圖(BaiduMap)的解決方案,scrollviewbaidumap

來源:互聯網
上載者:User

*運項目痛點之ScrollView中嵌套百度地圖(BaiduMap)的解決方案,scrollviewbaidumap


由於產品的需求,有時候不得不在ScrollView中嵌套百度地圖(BaiduMap)。但是,嵌套之後會存在一些問題,兩個比較突出的問題是:1)ScrollView中事件處理與BaiduMap存在衝突。2)在BaiduMap隨著ScrollView拖動的時候,存在黑影問題。很多人遇到過這兩個問題,也比較棘手,所以希望百度能給出官方的解決方案。下面說說我的處理辦法。

1)ScrollView中事件處理與BaiduMap存在衝突

想要瞭解產生事件衝突的原因,就必須明白安卓的事件傳遞與處理機制。http://blog.csdn.net/theone10211024/article/details/43270455 這片文章是我見過講的最好的。還不明白的同學不妨移步這裡。這裡我只簡單說一下傳遞流程:

Events->Activity.dispatchTouchEvent()->(頂層)ViewGroup.dispatchTouchEvent()->(頂層)ViewGroup.onInterceptTouchEvent()->childView1.dispatchTouchEvent()->childView1.OnTouchListener.OnTouch(如果定義了)->childView1.onTouchEvent()->childView2.....->childView n.....->(頂層)ViewGroup.onTouchListener.onTouch()(如果定義了)->(頂層)ViewGroup.onTouchEvent()->Activity.onTouchEvent();

中間任何一步如果事件被消費了,就會停止傳遞。

下面,我們來看看ScrollView的原始碼

  @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        /*         * This method JUST determines whether we want to intercept the motion.         * If we return true, onMotionEvent will be called and we do the actual         * scrolling there.         */        /*        * Shortcut the most recurring case: the user is in the dragging        * state and he is moving his finger.  We want to intercept this        * motion.        */        final int action = ev.getAction();        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {            return true;        }        switch (action & MotionEvent.ACTION_MASK) {            case MotionEvent.ACTION_MOVE: {                /*                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check                 * whether the user has moved far enough from his original down touch.                 */                /*                * Locally do absolute value. mLastMotionY is set to the y value                * of the down event.                */                final int activePointerId = mActivePointerId;                if (activePointerId == INVALID_POINTER) {                    // If we don't have a valid id, the touch down wasn't on content.                    break;                }                final int pointerIndex = ev.findPointerIndex(activePointerId);                final float y = ev.getY(pointerIndex);                final int yDiff = (int) Math.abs(y - mLastMotionY);                if (yDiff > mTouchSlop) {                    mIsBeingDragged = true;                    mLastMotionY = y;                    initVelocityTrackerIfNotExists();                    mVelocityTracker.addMovement(ev);                    if (mScrollStrictSpan == null) {                        mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");                    }                }                break;            }            case MotionEvent.ACTION_DOWN: {                final float y = ev.getY();                if (!inChild((int) ev.getX(), (int) y)) {                    mIsBeingDragged = false;                    recycleVelocityTracker();                    break;                }                /*                 * Remember location of down touch.                 * ACTION_DOWN always refers to pointer index 0.                 */                mLastMotionY = y;                mActivePointerId = ev.getPointerId(0);                initOrResetVelocityTracker();                mVelocityTracker.addMovement(ev);                /*                * If being flinged and user touches the screen, initiate drag;                * otherwise don't.  mScroller.isFinished should be false when                * being flinged.                */                mIsBeingDragged = !mScroller.isFinished();                if (mIsBeingDragged && mScrollStrictSpan == null) {                    mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");                }                break;            }            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                /* Release the drag */                mIsBeingDragged = false;                mActivePointerId = INVALID_POINTER;                recycleVelocityTracker();                if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {                    invalidate();                }                break;            case MotionEvent.ACTION_POINTER_UP:                onSecondaryPointerUp(ev);                break;        }        /*        * The only time we want to intercept motion events is if we are in the        * drag mode.        */        return mIsBeingDragged;    }

從該函數中可以看出,當處於拖動中(BeingDragged)的時候,ScrollView.onInterceptTouchEvent()返回true。事件就被父View即ScrollView消費了。ScrollView中的BaiduMap(實際上是其中的MapView)就不會再接收到該事件了。所以,解決該問題的核心思路就是:當ScrollView處於拖動事件中,且拖動地區在BaiduMap中的時候,讓事件不要被父View消費,而是交給BaiduMap處理。

解決方案:

1)重寫ScrollView.onInterceptTouchEvent()函數。當發現手指在BaiduMap中時,返回false(即未被消費)其他時候交由super.onInterceptTouchEvent()處理。我沒有採用這類解決方案,原因之一是不好控制手指是否在BaiduMap中,大家可以試一試

2)我們發現android給view提供了一個函數requestDisallowInterceptTouchEvent().它的定義是這樣的

Called when a child does not want this parent and its ancestors to intercept touch events with ViewGroup.onInterceptTouchEvent(MotionEvent). This parent should pass this call onto its parents. This parent must obey this request for the duration of the touch (that is, only clear the flag after this parent has received an up or a cancel.
意思是當child View不想它的父view消費事件,而是傳遞給自己的時候,可以調用該函數說“你不要把事件消費了,傳給我再處理吧”。然後,我就按照http://blog.csdn.net/catoop/article/details/14233419這個部落格寫的做了,具體代碼如下

// 重寫onTouch()事件,在事件裡通過requestDisallowInterceptTouchEvent(boolean)方法來設定父類的不可用,true表示父類的不可用//解決地圖的touch事件和scrollView的touch事件衝突問題mMapView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {if(event.getAction() == MotionEvent.ACTION_UP){scrollView.requestDisallowInterceptTouchEvent(false);}else{scrollView.requestDisallowInterceptTouchEvent(true);}return false;}});

但是,當我打了斷點調試的時候才發現,觸發事件的時候根本沒有走到MapView的onTouch()中,換句話說就是MapView.OnTouchListener.onTouch()根本沒有被調用。如果你熟悉事件的行程順序,這時候你就會猜到,MapView是繼承至ViewGroup,事件一定是被MapView中的某個childView消費了才不會傳遞給MapView。最後我找出了消費該事件的view。最後代碼改進成了這樣

 重寫onTouch()事件,在事件裡通過requestDisallowInterceptTouchEvent(boolean)方法來設定父類的不可用,true表示父類的不可用//解決地圖的touch事件和scrollView的touch事件衝突問題
                View v = mMapView.getChildAt(0);//這個view實際上就是我們看見的繪製在表面的地圖圖層
v.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {if(event.getAction() == MotionEvent.ACTION_UP){scrollView.requestDisallowInterceptTouchEvent(false);}else{scrollView.requestDisallowInterceptTouchEvent(true);}return false;}});
改進之後,順利達到效果。

其實,當你一頭霧水的時候,發現問題並解決是比較難的。當時我就反編譯了百度的jar包,在半寫半蒙中才找到了正確答案。

2)在BaiduMap隨著ScrollView拖動的時候,存在黑影問題

據我分析,由於百度地圖是用openGl繪製的,黑影可能是在拖動過程中不斷重繪才導致的。其實,百度工程師是不建議在ScrollView中使用百度地圖,除非你逼不得已。

這個如果非得用動態百度map,那解決可能還得等百度工程師的佳音了

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.