分析整个分屏的流程-2
分屏总览
1.分屏模式是通过长按最近任务列表(RecetsActivity
)的任一个历史应用(TaskTiew)进入的,如果该应用不支持分屏就提示用户,如果可以分屏就显示可以分屏的区域,之后拖拽想要分屏的TaskView
,在拖拽的过程中判断touch事件移动的位置是否进入了分屏区域,如果没有继续处理touch事件,如果进入了分屏区域,就会更新屏幕区域分屏,此时结束拖拽。
2.调用AMS为分屏区域创建stack,根据屏幕尺寸计算stack的尺寸,然后对stack中task再重新计算尺寸,最后启动分屏应用。
请求分屏
最近任务列表是由一个个的TaskView组成,当我们选择要分屏的TaskVIew,长按就进入了TaskVIew.java的onLongClick函数中,请求对其分屏。
SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskView.java
/**** View.OnLongClickListener Implementation ****/
@Override
public boolean onLongClick(View v) {
SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
boolean inBounds = false;
Rect clipBounds = new Rect(mViewBounds.getClipBounds());
if (!clipBounds.isEmpty()) {
// If we are clipping the view to the bounds, manually do the hit test.
clipBounds.scale(getScaleX());
inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
} else {
// Otherwise just make sure we're within the view's bounds.
inBounds = mDownTouchPos.x <= getWidth() && mDownTouchPos.y <= getHeight();
}
if (v == this && inBounds && !ssp.hasDockedTask()) {
// Start listening for drag events
setClipViewInStack(false);
mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
return true;
}
return false;
}
TaskView发生长按事件后,获取TaskView的边界值,进行判断mDownTouchPos
的x,y值是否在TaskView的范围之内,该值就是触摸事件上报的值,也就是手指按下的位置,在onInterceptTouchEvent
函数中获取。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY()));
}
return super.onInterceptTouchEvent(ev);
}
只有点击的View与当前的对象一致,并且手指按下的位置在TaskView的范围之内,并且此时并不在分屏模式下才会监听拖拽事件,注册EventBus事件,并且发送DragStartEvent事件,将当前的Task,TaskView,以及手指按下的点封装进DragStartEvent对象中。
显示可分屏区域
在TaskView中发送DragStartEvent事件,首先在RecentsViewTouchHandler.java的onEventBus函数接收到该事件。
/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java
/**** Events ****/
//SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java
public final void onBusEvent(DragStartEvent event) {
SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
mRv.getParent().requestDisallowInterceptTouchEvent(true);
mDragRequested = true;
// We defer starting the actual drag handling until the user moves past the drag slop
mIsDragging = false;
mDragTask = event.task;
mTaskView = event.taskView;
if (ActivityTaskManager.supportsMultiWindow(mRv.getContext()) && !ssp.hasDockedTask()
&& mDividerSnapAlgorithm.isSplitScreenFeasible()) {
if (!event.task.isDockable) { // 如果应用不支持分屏
EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
} else {
// 根据当前屏幕方向获取分屏的状态值
DockState[] dockStates = LegacyRecentsImpl.getConfiguration()
.getDockStatesForCurrentOrientation();
for (DockState dockState : dockStates) {
// 将分屏状态记录在mDropTargets列表中
registerDropTargetForCurrentDrag(dockState);
mVisibleDockStates.add(dockState); //将dockState记录在mVisibleDockStates列表中
}
}
// Request other drop targets to register themselves
EventBus.getDefault().send(new DragStartInitializeDropTargetsEvent(event.task,
event.taskView, this));
}
在RecentsViewTouchHandler中首先清理之前的信息,如果系统支持多窗口,并且此时不处于分屏模式,就会判断当前的task是否支持分屏,如果不支持分屏,就会提示用户该应用不支持分屏,如果支持分屏,根据当前系统的方向来获取分屏的状态。最后将获取的分屏状态记录在mVisibleDockStates和mDropTargets列表中。
SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsConfiguration.java
/**
* Returns the preferred dock states for the current orientation.
* @return a list of dock states for device and its orientation
*/
public DockState[] getDockStatesForCurrentOrientation() {
boolean isLandscape = mAppContext.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
if (config.isLargeScreen) {
return isLandscape ? DockRegion.TABLET_LANDSCAPE : DockRegion.TABLET_PORTRAIT;
} else {
return isLandscape ? DockRegion.PHONE_LANDSCAPE : DockRegion.PHONE_PORTRAIT;
}
}
在getDockStatesForCurrentOrientation函数中根据Configuration判断是平板电脑还是手机,还是横屏竖屏。
手机设备在横屏状态只允许左右分屏,在竖屏状态只允许上下分屏,由于设备太小了。
平板电脑在横屏状态可以往左边也可以往右边分屏,而在竖屏状态就和手机一样只能往上边分。
总之,接收到事件后从event中获取对应的Task以及TaskView,清理mDropTargets与mVisibleDockStates列表内容。只有系统支持多窗口模式,并且此时不处于分屏模式,并且分屏后的窗口大小要大于等于最小的窗口尺寸才可以进行分屏。如果应用不支持分屏就会发送ShowIncompatibleAppOverlayEvent事件,提示用户应用不支持分屏。如果支持分屏就调用getDockStatesForCurrentOrientation函数获取当前的设备是平板还是手机,是要上下分屏还是左右分屏。
最后将获得分屏状态保存在mDropTargets与mVisibleDockStates列表中。RecentsViewTouchHandler处理完DragStartEvent事件后,分发给RecentsView.java,由RecentsView进行处理。
/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java
//SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java
public final void onBusEvent(DragStartEvent event) {
updateVisibleDockRegions(LegacyRecentsImpl.getConfiguration().getDockStatesForCurrentOrientation(),
true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
DockState.NONE.viewState.hintTextAlpha,
true /* animateAlpha */, false /* animateBounds */);
// Temporarily hide the stack action button without changing visibility
if (mStackActionButton != null) {
mStackActionButton.animate()
.alpha(0f)
.setDuration(HIDE_STACK_ACTION_BUTTON_DURATION)
.setInterpolator(Interpolators.ALPHA_OUT)
.start();
}
}
首先调用updateVisibleDockRegions来更新可见的分屏区域,根据当前屏幕方向获取分屏状态(左右,上下分屏),此时为默认分屏状态,dockAreaAlpha为80背景区域为灰白色,hintTextAlpha为255字体不透明。
/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java
//SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java
/**
* Updates the dock region to match the specified dock state.
*/
private void updateVisibleDockRegions(DockState[] newDockStates,
boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
boolean animateAlpha, boolean animateBounds) {
ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
new ArraySet<DockState>());
// 获取前面得到的mVisibleDockState列表
ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
DockState dockState = visDockStates.get(i);
DockState.ViewState viewState = dockState.viewState;
if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
// This is no longer visible, so hide it
viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN, animateAlpha, animateBounds);
} else {
// This state is now visible, update the bounds and show it
int areaAlpha = overrideAreaAlpha != -1
? overrideAreaAlpha
: viewState.dockAreaAlpha;
int hintAlpha = overrideHintAlpha != -1
? overrideHintAlpha
: viewState.hintTextAlpha;
Rect bounds = isDefaultDockState // 获取提示用户拖拽的区域边界
? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
mSystemInsets)
: dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
mDividerSize, mSystemInsets, getResources());
if (viewState.dockAreaOverlay.getCallback() != this) {
viewState.dockAreaOverlay.setCallback(this);
viewState.dockAreaOverlay.setBounds(bounds);
}
viewState.startAnimation(bounds, areaAlpha, hintAlpha,
TaskStackView.SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
animateAlpha, animateBounds); // 启动动画显示分屏区域
}
}
}
将newDockStates转变为ArraySet,获取mVisibleDockStates对象,此时mVisibleDockStates有两个对象与newDockStates中的对象一致。遍历mVisibleDockStates中两个对象,获得areaAlpha为80,hintAlpha为255,下面主要计算bounds的值。
由于isDefaultDockState的值为true,所以调用getPreDockedBounds函数,获得准备分屏模式的边界区。
//SystemUI/legacy/recents/src/com/android/systemui/recents/views/DockState.java
/**
* Returns the docked task bounds with the given {@param width} and {@param height}.
*/
public Rect getPreDockedBounds(int width, int height, Rect insets) {
getMappedRect(dockArea, width, height, mTmpRect);
return updateBoundsWithSystemInsets(mTmpRect, insets);
}
/**
* Returns the expanded bounds in certain dock sides such that the bounds account for the
* system insets (namely the vertical nav bar). This call modifies and returns the given
* {@param bounds}.
*/
private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
if (dockSide == DOCKED_LEFT) {
bounds.right += insets.left;
} else if (dockSide == DOCKED_RIGHT) {
bounds.left -= insets.right;
}
return bounds;
}
获取bounds后使用动画显示对应区域
拖拽分屏
当屏幕显示出来分屏区域后,抬起手指停止拖拽TaskView,在RecentsView的onTouchEvent函数中接收touch事件,之后调用RecentsViewTouchHandler的handleTouchEvent处理触摸事件。最后调用handleTouchEvent处理。
@Overrid
public boolean onTouchEvent(MotionEvent ev){
return mTouchHandler.onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent ev){
handleTouchEvent(ev);
return mDragRequested;
}
在手指移动过程中获取坐标点evX,evY,在进行分屏时mLastDropTarget的值为null,并且currentDropTarget也为null,所以遍历mDropTargets列表,根据前面DragStartEvent事件可以知道mDropTargets列表中是左右分屏或者上下分屏的DockState对象,就会调用DockState的acceptDrop函数来判断手指移动的位置是否可以进行分屏。