Android N 來電界面

本流程圖基於MTK平臺 Android 7.0,普通來電,本流程只作爲溝通學習使用

通過前面 Android 7.0 Phone_MT來電流程 的流程分析中我們可以發現,最後是將來電的信息和狀態傳送到了 dialer 的 incallUI 裏面,在 PhoneStatusBar.java 的addNotification方法中通過判斷 isHeadsUped 的值來確定是顯示 HeadsUp 還是全屏的AnswerFragment ,這裏我們重點介紹一下 AnswerFragment 的相關知識。

相關類圖

這裏寫圖片描述

說明:

  • AnswerFragment是一個基類,它只提供了公共的接口和一些方法,沒有界面顯示
  • GlowPadAnswerFragment和AccessibleAnswerFragment是它的子類,裏面包含具體的界面顯示和部分邏輯
  • GlowPadAnswerFragment是我們通常意義上看到的AnswerFragment(GlowPadView)
  • AccessibleAnswerFragment當我們打開了Accessibility裏面的TalkBack Services後會啓用這個界面
  • AnswerFragment界面是CallCardFragment界面的一部分
  • InCallPresenter實例化了InCallActivity和AnswerPresenter,它們兩個一起控制着AnswerFragment的顯示和隱藏

GlowPadAnswerFragment

類圖說明

這裏寫圖片描述

說明:

  • GlowPadAnswerFragment 加載了 answer_fragment 這個佈局
  • answer_fragment 裏面只定義了一個自定義view, GlowPadWrapper
  • GlowPadWrapper 繼承自 GlowPadView
  • GlowPadView 繼承自 View

首先看看啓動流程圖

這裏寫圖片描述

再看看界面

這裏寫圖片描述

說明

  • 紅色方框中的界面就是我們的GlowPadAnswerFragment
  • 這裏顯示了三種狀態下的界面,1.未觸摸中心點的phone圖標、2.觸摸了並向右移動到接聽圖標、3.觸摸了但沒有移動

通過上面類圖的介紹,我們可以知道這個界面最終是由 GlowPadView 這個自定義view顯示的,下面我們就來大致瞭解一下上面類圖中各個類的內容和作用:

GlowPadAnswerFragment

這個類的代碼量很少,主要就是加載 answer_fragment 佈局,並通過 onShowAnswerUi 動態控制動畫的開始和停止,通過showTargets 方法動態調整整個 GlowPadView 的資源文件,包括:顯示幾個圖標,圖標的描述字符串的改變,幾個圖標的改變。

    @Override
    public void onShowAnswerUi(boolean shown) {
        Log.d(this, "Show answer UI: " + shown);
        if (shown) {
            mGlowpad.startPing();
        } else {
            mGlowpad.stopPing();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
    /**
     * Sets targets on the glowpad according to target set identified by the parameter.
     *
     * @param targetSet Integer identifying the set of targets to use.
     */
    @Override
    public void showTargets(int targetSet, int videoState) {
        Log.d(this, "showTargets  targetSet  2 == " + targetSet);
        final int targetResourceId;
        final int targetDescriptionsResourceId;
        final int directionDescriptionsResourceId;
        final int handleDrawableResourceId;
        mGlowpad.setVideoState(videoState);

        switch (targetSet) {
            case TARGET_SET_FOR_AUDIO_WITH_SMS:
                targetResourceId = R.array.incoming_call_widget_audio_with_sms_targets;
                targetDescriptionsResourceId =
                        R.array.incoming_call_widget_audio_with_sms_target_descriptions;
                directionDescriptionsResourceId =
                        R.array.incoming_call_widget_audio_with_sms_direction_descriptions;
                handleDrawableResourceId = R.drawable.ic_incall_audio_handle;
                break;
            case TARGET_SET_FOR_VIDEO_WITHOUT_SMS:
                targetResourceId = R.array.incoming_call_widget_video_without_sms_targets;
                targetDescriptionsResourceId =
                        R.array.incoming_call_widget_video_without_sms_target_descriptions;
                directionDescriptionsResourceId =
                        R.array.incoming_call_widget_video_without_sms_direction_descriptions;
                handleDrawableResourceId = R.drawable.ic_incall_video_handle;
                break;
                .............省略部分代碼;
                }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

answer_fragment

<com.android.incallui.GlowPadWrapper
        xmlns:android="http://schemas.android.com/apk/res/android" //設置android原生的命名空間
        xmlns:dc="http://schemas.android.com/apk/res-auto"  //設置dc特殊的命名空間,這樣後面dc:屬性纔可以識別
        android:id="@+id/glow_pad_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true"
        android:layout_centerHorizontal="true"
        android:gravity="center"
        android:background="@color/glowpad_background_color"  //黑色
        android:layout_marginBottom="@dimen/glowpadview_margin_bottom"

        dc:targetDrawables="@array/incoming_call_widget_audio_with_sms_targets"  //顯示各個方向上的圖標
        dc:targetDescriptions="@array/incoming_call_widget_audio_with_sms_target_descriptions" //各個方向圖標描述字符串
        dc:directionDescriptions="@array/incoming_call_widget_audio_with_sms_direction_descriptions" //各個方向的描述信息,指明這個方向可以做什麼事
        dc:handleDrawable="@drawable/ic_incall_audio_handle"  //接聽圖標
        dc:outerRingDrawable="@drawable/ic_lockscreen_outerring"  //最外層的圓圈
        dc:outerRadius="@dimen/glowpadview_target_placement_radius"  //外圓半徑
        dc:innerRadius="@dimen/glowpadview_inner_radius"  //內圓半徑
        dc:snapMargin="@dimen/glowpadview_snap_margin"  //滑動時距離圖標多遠就算已達到圖標,並且圖標會變換
        dc:feedbackCount="1"  //動畫播放的次數,GlowPadWrapper會重複播放動畫,GlowPadView中也有默認值爲3,代碼邏輯判斷只要這個值大於0就會播放動畫,所以設不設置都會重複播放動畫,知道touch事件主動調用停止
        dc:vibrationDuration="20"  //震動時長毫秒
        dc:glowRadius="@dimen/glowpadview_glow_radius" //以前版本的光暈半徑,也就是當我們按下並移動時,跟隨我們手指移動的那一小團小白點,但是現在的版本沒有跟着手指移動的小白點了
        dc:pointDrawable="@drawable/ic_lockscreen_glowdot" //小白點資源
        dc:allowScaling="true"  //是否允許縮放這個圓 
        />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

參考:http://blog.csdn.net/yihongyuelan/article/details/14000363 這個鏈接是4.2的版本有點老了,但是大致一樣,有些細節不一樣,不一樣的地方上面的代碼註釋也有說明,可以對比查看。

GlowPadWrapper

這個類其實就是 GlowPadView 的包裝,它起到一箇中間層的作用,GlowPadAnswerFragment通過它來控制 GlowPadView 的動畫啓停,GlowPadView 通過它來反饋用戶的 touch 和 select 事件給 AnswerFragment ,做對應的邏輯處理。

    private final Handler mPingHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case PING_MESSAGE_WHAT: //重複播放動畫的msg
                    triggerPing();
                    break;
            }
        }
    };


    private void triggerPing() {
        Log.d(this, "triggerPing(): " + mPingEnabled + " " + this);
        if (mPingEnabled && !mPingHandler.hasMessages(PING_MESSAGE_WHAT)) {
            ping(); //開始動畫

            if (ENABLE_PING_AUTO_REPEAT) { //一直是true 所以動畫會一直播放
                mPingHandler.sendEmptyMessageDelayed(PING_MESSAGE_WHAT, PING_REPEAT_DELAY_MS);
            }
        }


    @Override //GlowPadView 中當用戶滑動到一個圖標後觸發的方法
    public void onTrigger(View v, int target) {
        Log.d(this, "onTrigger() view=" + v + " target=" + target);
        final int resId = getResourceIdForTarget(target);
        if (resId == R.drawable.ic_lockscreen_answer) {
            mAnswerFragment.onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext()); //接聽電話
            mTargetTriggered = true;
        } else if (resId == R.drawable.ic_lockscreen_decline) {
            mAnswerFragment.onDecline(getContext()); //拒絕電話
            mTargetTriggered = true;
        } else if (resId == R.drawable.ic_lockscreen_text) {
            mAnswerFragment.onText(); //文本消息回覆
            mTargetTriggered = true;
        } else if (resId == R.drawable.ic_videocam || resId == R.drawable.ic_lockscreen_answer_video) {
            mAnswerFragment.onAnswer(mVideoState, getContext()); //video狀態下的接聽
            mTargetTriggered = true;
        } else if (resId == R.drawable.ic_lockscreen_decline_video) {
            mAnswerFragment.onDeclineUpgradeRequest(getContext()); //拒絕升級請求
            mTargetTriggered = true;
        } else {
            // Code should never reach here.
            Log.e(this, "Trigger detected on unhandled resource. Skipping.");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

GlowPadView

具體的 GlowPadView 的實現類,裏面包含了view的繪畫和動畫的定義,還有一些監聽和回調,這部分內容後面再做詳細的分析。

LOG信息

01-11 10:29:48.872 D/InCall  ( 5589): AnswerPresenter - onIncomingCall: com.android.incallui.AnswerPresenter@95a864f
01-11 10:29:49.895 D/InCall  ( 5589): AnswerPresenter - Showing incoming for call id: Call_0 com.android.incallui.AnswerPresenter@95a864f
//界面的啓動和初始化
01-11 10:29:49.962 D/InCall  ( 5589): GlowPadWrapper - class created com.android.incallui.GlowPadWrapper{ebc6975 VFED..... ......I. 0,0-0,0 #7f0a0094 app:id/glow_pad_view}
01-11 10:29:49.962 D/InCall  ( 5589): GlowPadWrapper - onFinishInflate()
01-11 10:29:49.967 D/InCall  ( 5589): GlowPadAnswerFragment - Creating view for answer fragment GlowPadAnswerFragment{f709f47 #2 id=0x7f0a00c4 tag_answer_fragment}
01-11 10:29:49.967 D/InCall  ( 5589): GlowPadAnswerFragment - Created from activitycom.android.incallui.InCallActivity@494e8d4
//界面顯示和動畫
01-11 10:29:49.968 D/InCall  ( 5589): GlowPadAnswerFragment - Show answer UI: true
01-11 10:29:49.968 D/InCall  ( 5589): GlowPadWrapper - startPing
01-11 10:29:49.968 D/InCall  ( 5589): GlowPadWrapper - triggerPing(): true com.android.incallui.GlowPadWrapper{ebc6975 VFED..... ......I. 0,0-0,0 #7f0a0094 app:id/glow_pad_view}
01-11 10:29:49.973 D/InCall  ( 5589): GlowPadAnswerFragment - showTargets  targetSet  1 == 1
01-11 10:29:49.973 D/InCall  ( 5589): GlowPadAnswerFragment - showTargets  targetSet  2 == 1
01-11 10:29:49.974 D/InCall  ( 5589): AnswerPresenter - getVideoUpgradeRequestCall call =null
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

AccessibleAnswerFragment

這個界面是Android 7.0 新加入的界面,主要是給有生理障礙的人士使用,在 InCallActivity 裏面通過判斷是否啓動無障礙功能裏面的 TalkBack 服務,來決定是否顯示 AccessibleAnswerFragment

            if (AccessibilityUtil.isTalkBackEnabled(this)) { //如果開啓就啓動AccessibleAnswerFragment 界面
                mAnswerFragment = new AccessibleAnswerFragment();
            } else {//否則啓動 GlowPadAnswerFragment 界面
                mAnswerFragment = new GlowPadAnswerFragment();
            }
  • 1
  • 2
  • 3
  • 4
  • 5

界面圖片

這裏寫圖片描述

可以看到這個界面相對於 GlowPadAnswerFragment 界面就簡單得多了,只有三個 imageview 沒有複雜的動畫在裏面。

AccessibleAnswerFragment

這個類的主要作用就是監聽並處理用戶的點擊和滑動事件,從而調用 AnswerFragment 裏面的接聽、掛斷、短信回覆這些方法。相關的方法如下:

//設置點擊事件,通過三個view來觸發不同的應答邏輯
        mAnswer = group.findViewById(R.id.accessible_answer_fragment_answer);
        mAnswer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "Answer Button Clicked");
                onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext());
            }
        });
        mDecline = group.findViewById(R.id.accessible_answer_fragment_decline);
        mDecline.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "Decline Button Clicked");
                onDecline(getContext());
            }
        });

        mText = group.findViewById(R.id.accessible_answer_fragment_text);
        mText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "Text Button Clicked");
                onText();
            }
        });

//設置滑動事件的監聽,通過滑動方向來確定應答模式
    private boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
        if (hasPendingDialogs()) {
            return false;
        }

        float diffY = e2.getY() - e1.getY();
        float diffX = e2.getX() - e1.getX();
        if (Math.abs(diffX) > Math.abs(diffY)) {
            if (Math.abs(diffX) > SWIPE_THRESHOLD) {
                if (diffX > 0) {
                    onSwipeRight();
                } else {
                    onSwipeLeft();
                }
            }
            return true;
        } else if (Math.abs(diffY) > SWIPE_THRESHOLD) {
            if (diffY > 0) {
                onSwipeDown();
            } else {
                onSwipeUp();
            }
            return true;
        }

        return false;
    }

//設置touch監聽爲全屏事件,即整個 InCallActivity 界面
    @Override
    public void onResume() {
        super.onResume();
        // Intercept all touch events for full screen swiping gesture.
        InCallActivity activity = (InCallActivity) getActivity();
        activity.setDispatchTouchEventListener(mTouchListener);
    }

//touch監聽事件即爲我們的 滑動事件監聽,所以我們可以在全屏任何地方滑動觸發相關邏輯代碼
    private class TouchListener implements View.OnTouchListener {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return mGestureDetector.onTouchEvent(event);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

accessible_answer_fragment

這個文件是 GlowPadAnswerFragment 的佈局文件,裏面內容也很簡單,通過相對佈局和線性佈局來得到我們上圖下半部分的來電界面.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:background="@color/glowpad_background_color">
    <RelativeLayout
        android:id="@+id/accessible_answer_fragment_answer"
        android:orientation="vertical"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:clickable="true"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp">
        <ImageView
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:src="@drawable/ic_lockscreen_answer_activated_layer"
            android:layout_centerInParent="true">
        </ImageView>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/description_target_answer"
            android:textSize="12sp"
            android:textColor="@color/accessible_answer_hint_text_color"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="8dp"/>
    </RelativeLayout>

    .....省略部分代碼
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

總結

  1. 來電界面有兩個,一個是正常的 GlowPadAnswerFragment 一個是無障礙服務開啓後顯示的 AccessibleAnswerFragment (這個是7.0 新加的 fragment 通過判讀 TalkBack 服務是否開啓來決定是否顯示這個fragment)
  2. GlowPadAnswerFragment 顯示的其實就是 GlowPadView 這個自定義view
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章