前面寫了IncallUI的顯示過程,只分析到了IncallActivity
的起點。
IncallActivity
包含了多個Fragment
構成,
包含了InCallFragment
,InCallButtonGridFragment
,SurfaceViewVideoCallFragment
,AnswerFragment
,VideoCallFragment
,等Fragment.
他們的控制顯示,則是在各自的Presenter裏,通過通話狀態來調整。
在這個普通通話界面,包含的是InCallFragment
與InCallButtonGridFragment
,分別顯示上部分的聯繫人信息通話狀態,以及下半部分的通話按鍵部分。
InCallButtonGridFragment是控制顯示通話狀態中的Button部分的。
這裏的Button都用了代理模式的方式,放在了ButtonController裏。
ButtonController的初始化是在InCallFragment
裏初始化的。這是因爲其實剛開始最初顯示的時候,InCallButtonGridFragment
裏面這些button還沒有初始化,這些都是空的view,只有InCallFragment
在根據狀態更新Button的狀態時,這些button纔會顯示出來,所以這些ButtonController
都是在InCallFragment
裏初始化。而且InCallFragment
內部持有了InCallButtonGridFragment
對象。
看看InCallFragment
的onViewCreated
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle bundle) {
...
buttonControllers.add(new ButtonController.MuteButtonController(inCallButtonUiDelegate));
buttonControllers.add(new ButtonController.SpeakerButtonController(inCallButtonUiDelegate));
buttonControllers.add(new ButtonController.DialpadButtonController(inCallButtonUiDelegate));
buttonControllers.add(new ButtonController.HoldButtonController(inCallButtonUiDelegate));
buttonControllers.add(new ButtonController.AddCallButtonController(inCallButtonUiDelegate));
buttonControllers.add(new ButtonController.SwapButtonController(inCallButtonUiDelegate));
buttonControllers.add(new ButtonController.MergeButtonController(inCallButtonUiDelegate));
...
}
其中SpeakerButtonController有個特別的地方,在系統檢測到藍牙耳機輸出的時候,系統會有三個輸出聲音方式,耳機,外發,和藍牙,於是就要顯示Audio音頻選擇器界面,而不是自動選擇。
SpeakerButtonController
裏,有兩個監聽
@Override
public void onClick(View v) {
delegate.showAudioRouteSelector();
}
@Override
public void onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked) {
checkableLabeledButton.setContentDescription(
isChecked ? checkedContentDescription : uncheckedContentDescription);
delegate.toggleSpeakerphone();
}
一個是當點擊View時執行showAudioRouteSelector
,去開啓音頻選擇界面,一個是onCheckedChanged
去切換外音和喇叭。
這裏有個疑問,因爲系統代碼裏沒有地方調用showAudioRouteSelector
,而要顯示音頻選擇器就一定要調用這個方法,那麼就必須是這裏的onClick
監聽回調調用,可是這裏又有個onCheckedChanged
,要如何區分點擊的是onClick
還是onCheckedChanged
呢?那就要看設置的監聽器是誰了。
在SpeakerButtonController
的setButton
設置監聽的時候有判斷checkable
@Override
public void setButton(CheckableLabeledButton button) {
this.button = button;
if (button != null) {
button.setEnabled(isEnabled && isAllowed);
button.setVisibility(View.VISIBLE);
button.setChecked(isChecked);
button.setOnClickListener(checkable ? null : this);
button.setOnCheckedChangeListener(checkable ? this : null);
button.setLabelText(label);
button.setIconDrawable(icon);
button.setContentDescription(
isChecked ? checkedContentDescription : uncheckedContentDescription);
button.setShouldShowMoreIndicator(!checkable);
}
}
原來在設置監聽的時候,區分了
button.setOnClickListener(checkable ? null : this);
button.setOnCheckedChangeListener(checkable ? this : null);
關鍵在於checkable ,而checkable 這個值也只有在setAudioState
的時候有設置,
public void setAudioState(CallAudioState audioState) {
SpeakerButtonInfo info = new SpeakerButtonInfo(audioState, IconSize.SIZE_36_DP);
checkable = info.checkable;
isChecked = info.isChecked;
label = info.label;
icon = info.icon;
@StringRes int contentDescriptionResId = info.contentDescription;
contentDescription = delegate.getContext().getText(contentDescriptionResId);
checkedContentDescription =
TextUtils.concat(
contentDescription,
delegate.getContext().getText(R.string.incall_talkback_speaker_on));
uncheckedContentDescription =
TextUtils.concat(
contentDescription,
delegate.getContext().getText(R.string.incall_talkback_speaker_off));
setButton(button);
}
SpeakerButtonInfo
public SpeakerButtonInfo(CallAudioState audioState, @IconSize int iconSize) {
if ((audioState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH)
== CallAudioState.ROUTE_BLUETOOTH) {
checkable = false;
isChecked = false;
label = R.string.incall_label_audio;
....
在SpeakerButtonInfo裏,當檢測到audio支持Bluetooth的時候,就把checkable設爲false,然後setButton的時候就分別設置是
ClickListener還是CheckedChangeListener了。
setAudioState
的地方是CallButtonPresenter的監聽,來自AudioModeListener的回調
@Override
public void onAudioStateChanged(CallAudioState audioState) {
if (mInCallButtonUi != null) {
mInCallButtonUi.setAudioState(audioState);
}
}
而setButton
的地方在InCallButtonGridFragment
的updateButtonStates
最後。
public int updateButtonStates(
List<ButtonController> buttonControllers,
@Nullable ButtonChooser buttonChooser,
int voiceNetworkType,
int phoneType) {
Set<Integer> allowedButtons = new ArraySet<>();
Set<Integer> disabledButtons = new ArraySet<>();
for (ButtonController controller : buttonControllers) {
if (controller.isAllowed()) {
allowedButtons.add(controller.getInCallButtonId());
if (!controller.isEnabled()) {
disabledButtons.add(controller.getInCallButtonId());
}
}
}
for (ButtonController controller : buttonControllers) {
controller.setButton(null);
}
if (buttonChooser == null) {
buttonChooser =
ButtonChooserFactory.newButtonChooser(voiceNetworkType, false /* isWiFi */, phoneType);
}
int numVisibleButtons = getResources().getInteger(R.integer.incall_num_rows) * BUTTONS_PER_ROW;
List<Integer> buttonsToPlace =
buttonChooser.getButtonPlacement(numVisibleButtons, allowedButtons, disabledButtons);
for (int i = 0; i < BUTTON_COUNT; ++i) {
if (i >= buttonsToPlace.size()) {
/// M: set to GONE
buttons[i].setVisibility(View.GONE);
continue;
}
@InCallButtonIds int button = buttonsToPlace.get(i);
buttonGridListener.getButtonController(button).setButton(buttons[i]);
}
return numVisibleButtons;
}
這裏的buttonGridListener
實際對象就是InCallFragment
總結:
button.setOnClickListener(checkable ? null : this);
button.setOnCheckedChangeListener(checkable ? this : null);
這種寫法好像挺特別的,不同的狀態設置不同的監聽對象,類似於設計模式裏的狀態模式,值得學習。