主頁 > 移動端開發 > Android事件分發-基礎原理和場景分析

Android事件分發-基礎原理和場景分析

2023-04-22 08:06:41 移動端開發

作者:京東零售 郭旭鋒

1 為什么需要事件分發

和其他平臺類似,Android 中 View 的布局是一個樹形結構,各個 ViewGroup 和 View 是按樹形結構嵌套布局的,從而會出現用戶觸摸的位置坐標可能會落在多個 View 的范圍內,這樣就不知道哪個 View 來回應這個事件,為了解決這一問題,就出現了事件分發機制,

2 事件分發的關鍵方法

Android 中事件分發是從 Activity 開始的,可以看看各組件中事件分發的關鍵方法

Activity:沒有 onInterceptTouchEvent 方法,因為如果 Activity 攔截事件,將導致整個頁面都沒有回應,而 Activity 是系統應用和用戶互動的媒介,不能回應事件顯然不是系統想要的結果,所以 Activity 不需要攔截事件,

ViewGroup:三個方法都有,Android 中 ViewGroup 是一個布局容器,可以嵌套多個 ViewGroup 和 View,事件傳遞和攔截都由 ViewGroup 完成,

View:事件傳遞的最末端,要么消費事件,要么不消費把事件傳遞給父容器,所以也不需要攔截事件,

3 事件分發流程分析

3.1 事件分發流程概覽

Activity 并不是一個 View,那么 Activity 是如何將事件分發到頁面的 ViewGroup 和 View 的呢,我們先看看原始碼

# Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // 呼叫 Window 物件的方法,開始事件分發
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    // 如果事件分發回傳 false,也即事件沒被消費,則呼叫自己的 onTouchEvent 方法
    return onTouchEvent(ev);
}

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}



可以看到,Activity 中的事件分發方法 dispatchTouchEvent 呼叫了 getWindow().superDispatchTouchEvent(ev) 方法,而這里的 WIndow 實際上是 PhoneWindow,

簡單來說,Window 是一個抽象類,是所有視圖的最頂層容器,視圖的外觀和行為都歸他管,無論是背景顯示、標題欄還是事件處理都是他管理的范疇,而 PhoneWindow 作為 Window的唯一親兒子(唯一實作類),自然就是 View 界的皇帝了,

下來看看 PhoneWindow 的代碼

# PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}



PhoneWindow 中又呼叫了 mDecor.superDispatchTouchEvent(event) 方法,mDecor 是 DecorView 物件,再看看 DecorView 的代碼

# DecorView
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
}

# FrameLayout
public class FrameLayout extends ViewGroup {
}

# ViewGroup
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ......
    }
}

# View
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ......
    }
}



可以看到,DecorView 實際上就是 ViewGroup,事件分發方法最終呼叫到了 ViewGroup 的 dispatchTouchEvent(MotionEvent ev) 方法,

DecorView 是 PhoneWindow 的一個物件,其職位就是跟在 PhoneWindow 身邊專業為 PhoneWindow 服務的,除了自己要干活之外,也負責訊息的傳遞,PhoneWindow 的指示通過 DecorView 傳遞給下面的 View,而下面 View 的資訊也通過 DecorView 回傳給 PhoneWindow,

Android 中的事件分發是責任鏈模式的一種變形,事件由上往下傳遞,如果事件沒有被消費則繼續傳遞到下一層,如果事件被消費則停止傳遞,如果到最下層事件則沒有被消費,則事件會層層傳遞給上一層處理,我們都知道事件分發的源頭在 Activity 中的 dispatchTouchEvent 方法中,事件從這里開始,分發到布局中的各個 View 中,不斷遞回呼叫 ViewGroup/View 的 dispatchTouchEvent 方法,通過上面分析可以看到,Activity 在接受到上層派發來的事件后,會把事件傳遞到自己的 dispatchTouchEvent 方法中,然后Activity 會把觸摸、點擊事件傳遞給自己的 mWindow 物件,最終傳遞給 DecorView 的 dispatchTouchEvent 方法,實際呼叫的是 ViewGroup 的 dispatchTouchEvent 方法,

3.2 事件分發原始碼分析

經過分析,可以知道 Android 中事件分發的關鍵方法就是 ViewGroup 和 View 中的相關方法,如下

# View
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

    public boolean dispatchTouchEvent(MotionEvent event) {
        // ... 省略部分代碼
        boolean result = false;
        // ... 省略部分代碼
        if (onFilterTouchEventForSecurity(event)) {
            // ... 省略部分代碼
            // 1. 主要呼叫 onTouchEvent 方法,回傳 true 說明事件被消費,否則沒被消費
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        // ... 省略部分代碼
        return result;
    }
    
    public boolean onTouchEvent(MotionEvent event) {
        // ... 省略部分代碼
        // 2. 默認可點擊則回傳 true,也就是消費事件,Button 或設定過 OnClickListener,則 View 可點擊
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    // ... 省略部分代碼
                    break;
                case MotionEvent.ACTION_DOWN:
                    // ... 省略部分代碼
                    break;
                case MotionEvent.ACTION_CANCEL:
                    // ... 省略部分代碼
                    break;
                case MotionEvent.ACTION_MOVE:
                    // ... 省略部分代碼
                    break;
            }
            return true;
        }

        return false;
    }
}



View 中的方法邏輯比較簡單,如備注 1 所示,dispatchTouchEvent 主要就是做一些安全檢查,檢查通過后會呼叫 onTouchEvent 方法,而 onTouchEvent 方法中邏輯如備注 2 所示,如果 View 是可點擊的,則默認會認為消費事件,否則不消費,一般 Button 控制元件,或設定過 OnClickListener 的控制元件,View 會被默認設定為可點擊,

下面看看 ViewGroup 代碼

# ViewGroup
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    public boolean dispatchTouchEvent(MotionEvent ev) {
        // ... 省略部分代碼
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 1. 如果是 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();
            }

            final boolean intercepted;
            // 2. 如果是 DOWN 事件,會判斷當前 ViewGroup 是否要攔截事件,這里受兩個因素影響:
            //    一是 FLAG_DISALLOW_INTERCEPT,如果設定不攔截,則不會呼叫 onInterceptTouchEvent,直接設定為不攔截
            //    二是沒設定 FLAG_DISALLOW_INTERCEPT 標志,默認允許攔截,會呼叫 onInterceptTouchEvent 方法
            // 3. 如果不是 DOWN 事件,可能是 MOVE 或 UP 事件,mFirstTouchTarget 是記錄需要繼續進行事件分發的下一級子 View,包括ViewGroup 或 View,這里也分為兩種情況
            //    如果 mFirstTouchTarget 不為空,說明需要繼續向下一級子 View/ViewGroup 分發事件,這時說明上次 DOWN 事件找到了下級有消費事件的子 View,且無攔截事件
            //    如果 mFirstTouchTarget 為空,說明沒找到要消費事件的子 View,或事件被攔截了
            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;
            }
            // ... 省略部分代碼
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            // 4. 下面邏輯主要就是遍歷尋找能消費事件的 View,如果事件被攔截,則不需要再尋找
            if (!canceled && !intercepted) {
                // ... 省略部分代碼
                // 5. 只有 DOWN 事件才需要尋找,其他事件時已經確定是否找到,都不需要再找消費事件的 View 了
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    // ... 省略部分代碼
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        // ... 省略部分代碼
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            // ... 省略部分代碼
                            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                            // 6. 這個方法是關鍵
                            //    如果 child 不為空,則會再呼叫 child.dispatchTouchEvent 方法,達到層層遞回的效果
                            //    如果 child 為空,則會呼叫 super.dispatchTouchEvent 方法,super 是 View,實際上呼叫了 onTouchEvent 方法,自己判斷是否消費事件
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // ... 省略部分代碼
                                // 7. 回傳 true,說明找到了消費事件的 View,下面方法會給 mFirstTouchTarget 賦值,下面 mFirstTouchTarget 將不為空
                                //    注:mFirstTouchTarget 并不是最終消費事件的 View,而是下一級包含消費事件 View 的鏈表物件,或是直接消費事件的 View 的鏈表物件
                                //    每一個 ViewGourp 都會記錄一個 mFirstTouchTarget,mFirstTouchTarget.child 記錄了下一層消費事件的 ViewGroup 或 View
                                //    同時,alreadyDispatchedToNewTouchTarget 變數會設定為 true
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                            // ... 省略部分代碼
                        }
                        // ... 省略部分代碼
                    }
                    // ... 省略部分代碼
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // 8. 當沒有找到消費事件的 View,或事件被攔截,mFirstTouchTarget 都不會被賦值,這里 child 為空,會呼叫自己的 onTouchEvent 方法
                handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    // 9. 說明找到了消費事件的 View,并且已經分發,直接設定為已處理
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                        // 10. 此方法和備注 6 和 8 都一樣,這里多了 cancel 的處理邏輯,如果事件被攔截,需要給原來消費事件的 View 發一個 CANCEL 事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
            // ... 省略部分代碼
        }
        // ... 省略部分代碼
        return handled;
    }
    
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        // 默認不攔截
        return false;
    }
    
    // 沒有覆寫這個方法,實際呼叫的是 View 的 onTouchEvent 方法
    public boolean onTouchEvent(MotionEvent event) {
    }
        
}



可以看到,ViewGroup 中的事件分發邏輯還是比較復雜,但抓住關鍵點后則很容易能看清它的本來面貌

(1)分發的事件包括 DOWN、MOVE、UP、CANCEL 幾種,用戶一個完整的動作就是由這幾個事件組合而成的

(2)只有 DOWN 事件中會尋找消費事件的目標 View,其他事件不會再尋找

(3)DOWN 事件尋找到目標 View 后,后續其他事件都會直接分發至目標 View

(4)事件可以被攔截,攔截后原目標 View 會收到 CANCEL 事件,后續將不會再收到任何事件(這也是這套機制不支持豐富的嵌套滑動的原因)

3.3 事件分發情景分析

3.3.1 分發程序沒有任何 View 攔截和消費

(1)事件回傳時,為了簡化理解,dispatchTouchEvent 直接指向了父 View 的 onTouchEvent ,實際上它僅僅是回傳給父 View 的 dispatchTouchEvent 一個 false 值(影響了 mFirstTouchTarget 的值),父 View 根據回傳值來呼叫自身的onTouchEvent 方法

(2)ViewGroup 是根據 onInterceptTouchEvent 的回傳值(影響了 mFirstTouchTarget 的值)確定是呼叫子 View 的 dispatchTouchEvent 還是自身的 onTouchEvent 方法

(3)如果所有 View 都沒有消費 DOWN 事件,后續 MOVE 和 UP 不會再往下傳遞,會直接傳遞給 Activity 的 onTouchEvent 方法

3.3.2 最底層View消費事件,且上層View沒有攔截事件

(1)若沒有 ViewGroup 對事件進行攔截,而最底層 View 消費了此事件,也就是接收到 DOWN 事件時 View 的 onTouchEvent 回傳 true,事件將不會再向上傳遞給各個 ViewGroup 的 onTouchEvent 方法,而是直接回傳,后續的 MOVE 和 UP 事件也將會直接交給 View 進行處理

3.3.3 最底層View沒有消費事件,ViewGroup2消費了事件,且上層View沒有攔截事件

(1)如果 View 沒有消費事件,在層層呼叫父布局的 onTouchEvent 方法時,有 View 消費此事件,如 ViewGroup2 消費此事件,后續 MOVE 和 UP 事件將會傳遞給 ViewGroup2 的 onTouchEvent 方法,而且不會再呼叫 ViewGroup2 的 onInterceptTouchEvent 方法

(2)原始碼 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {} 這個代碼中主要呼叫 onInterceptTouchEvent() 方法和處理是否攔截

第一次是 DOWN 事件會進行判斷,所以會呼叫 onInterceptTouchEvent 攔截方法

第二次非 DOWN 事件,不會再呼叫 onInterceptTouchEvent 方法,原因如下:

? 如果 DOWN 事件的時候進行過攔截,也就是 onInterceptTouchEvent() 方法回傳 true,則 mFirstTouchTarget 必定為 null,不會呼叫 onInterceptTouchEvent 方法,因為后面不會對這個值賦值,會往下走邏輯,直接呼叫到此 View 或 ViewGroup 的 onTouchEvent() 方法

? 如果 DOWN 事件沒有攔截,但子 View 的 onTouchEvent 都回傳 false,只有當前 ViewGroup 的 onTouchEvent 回傳 true,mFirstTouchTarget 也同樣為 null,也不會呼叫 onInterceptTouchEvent 方法,因為 mFirstTouchTarget 本質是找能接收事件的子 View,所有子 View 都不接收事件,mFirstTouchTarget 就必然為 null

3.3.4 ViewGroup2攔截了并消費了DOWN事件,其他View沒有攔截事件

(1)ViewGroup2 攔截 DOWN 事件后,View 不會接收到任何事件,ViewGroup2 消費事件后,后續 MOVE 和 UP 事件會交給 ViewGroup2 的 onTouchEvent 方法進行處理,且不會再呼叫 ViewGroup2 的onInterceptTouchEvent 方法

3.3.5 View消費了DOWN事件,ViewGroup2攔截且消費了MOVE事件,其他View沒有攔截事件

(1)View 中 DOWN 事件正常傳遞

(2)當 ViewGroup2 攔截 MOVE 事件后,當前 mFirstTouchTarget 不為空,首先 View 會收到轉換后的 CANCEL 事件,mFirstTouchTarget 會置為空,下次 MOVE 事件由于 mFirstTouchTarget 為空,會呼叫到自己的 onTouchEvent 方法

3.3.6 View消費 DOWN 事件,ViewGroup2攔截且消費了MOVE事件,一定條件后,ViewGroup1再次攔截和消費MOVE事件,其他View沒有攔截事件

3.4 事件分發總結

(1)整個分發程序中沒有任何攔截和消費,DOWN 事件會層層往下分發,并層層往上回傳 false,MOVE 和 UP 事件則會交給 Activity 的 onTouchEvent 方法進行處理,不再往下分發

(2)分發程序中沒有任何攔截但有消費,DOWN 事件會層層往下分發,并層層往上回傳false,直到有消費回傳 true,MOVE 和 UP 事件則會層層往下分發,最后直接交給消費事件的 View 進行處理,然后層層回傳 true

(3)分發程序中有攔截且攔截后消費,DOWN 事件會層層往下分發,直到有攔截后直接交給消費的 View 進行處理,MOVE 和 UP 事件則會層層往下分發,最后直接交給消費事件的 View 進行處理,然后層層回傳true

(4)分發程序中不攔截 DOWN 事件,但攔截 MOVE 事件且攔截后消費,第一次攔截,之前收到 DOWN 事件的子 View 會收到 CANCEL 事件,并層層回傳;后續 MOVE 和 UP 會層層往下分發,最后直接交給消費事件的 View 進行處理

(5)分發程序中不攔截 DOWN 事件,但攔截 MOVE 事件且攔截后不消費,第一次攔截,之前收到 DOWN 事件的子 View 會收到 CANCEL 事件,并層層回傳;后續 MOVE 和 UP 會層層往下分發,最后交給攔截的 View 進行處理,此時由于攔截的 View 沒有消費,會層層往上回傳 false,最后會交給 Activity 的 onTouchEvent 方法進行處理

以上,是個人的一些分析和經驗,歡迎有興趣的小伙伴一起學習和探討!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/550830.html

標籤:Android

上一篇:OpenHarmony SystemUI開發記錄

下一篇:返回列表

標籤雲
其他(157832) Python(38089) JavaScript(25381) Java(17985) C(15215) 區塊鏈(8256) C#(7972) AI(7469) 爪哇(7425) MySQL(7137) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4556) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1959) Web開發(1951) HtmlCss(1919) python-3.x(1918) 弹簧靴(1913) C++(1910) xml(1889) PostgreSQL(1872) .NETCore(1854) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • Android事件分發-基礎原理和場景分析

    和其他平臺類似,Android 中 View 的布局是一個樹形結構,各個 ViewGroup 和 View 是按樹形結構嵌套布局的,從而會出現用戶觸摸的位置坐標可能會落在多個 View 的范圍內,這樣就不知道哪個 View 來回應這個事件,為了解決這一問題,就出現了事件分發機制。 ......

    uj5u.com 2023-04-22 08:06:41 more
  • OpenHarmony SystemUI開發記錄

    背景介紹 最近學習OpenHarmony應用開發, SDK版本是3.2.9.2 Beta4,IDE版本是3.1.0.200。參考官方檔案,做了個Demo應用,除錯、運行非常順利。啟動應用后,狀態欄和導航欄占用的高度過高,顯得很奇怪,嘗試修改一下系統應用。 摸石頭過河 因為沒做過移動端開發,最初以為狀 ......

    uj5u.com 2023-04-21 09:01:49 more
  • 手機穿戴設備能力共享,提升豐富互動體驗

    HUAWEI Wear Engine面向手機和穿戴設備的應用與服務開發者,提供華為穿戴設備開放能力。 開發者通過呼叫Wear Engine開放能力,可以實作手機上的生態應用與服務給華為穿戴設備發訊息、發通知、傳輸資料,并獲取穿戴設備狀態、讀取傳感器資料等,也可以實作華為穿戴設備上的生態應用與服務給手 ......

    uj5u.com 2023-04-21 09:01:34 more
  • Cmder: 懶癌必備!從此告別記事本記命令的日子

    前言 平時開發中遇到這樣那樣的命令需要記下來,一般做法是這樣。 新建記事本 將需要記下的關鍵命令保存。 每次需要使用時,粘貼復制即可。 好像沒什么毛病!直到遇到了 Cmder。。。 當看到同事分析問題時在 Cmder 里命令快捷鍵刷刷一頓操作,覺得挺厲害滴。感覺這同事技術好牛,快捷鍵用這么好。后來才 ......

    uj5u.com 2023-04-21 08:56:04 more
  • 手機穿戴設備能力共享,提升豐富互動體驗

    HUAWEI Wear Engine面向手機和穿戴設備的應用與服務開發者,提供華為穿戴設備開放能力。 開發者通過呼叫Wear Engine開放能力,可以實作手機上的生態應用與服務給華為穿戴設備發訊息、發通知、傳輸資料,并獲取穿戴設備狀態、讀取傳感器資料等,也可以實作華為穿戴設備上的生態應用與服務給手 ......

    uj5u.com 2023-04-21 08:53:02 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more