
前面写了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);
这种写法好像挺特别的,不同的状态设置不同的监听对象,类似于设计模式里的状态模式,值得学习。