最近安卓电视开发中有这样的需求:
1.全部清空移至多任务列表左侧
2.加一个垃圾桶图标
3.手势划出app时,全部清空的图标会暂时隐藏
如下图:
需求明确了,那就开始干活吧
SystemUI的源码实在Android/frameworks/base/packages/SystemUI/
1.首先要先找到全部清除按钮:
xml:SystemUI/res/layout/recents_stack_action_button.xml
对象:com.android.systemui.recents.views.RecentsView中的mStackActionButton
2.全部清除移到移至多任务列表左侧
RecentsView是继承FrameLayout的,在其构造方法中找到mStackActionButton的对象获取:
if (RecentsDebugFlags.Static.EnableStackActionButton) {
mStackActionButton = (TextView) inflater.inflate(R.layout.recents_stack_action_button,
this, false);
mStackActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().send(new DismissAllTaskViewsEvent());
}
});
addView(mStackActionButton);
}
上上面代码可以看出,mStackActionButton是通过addView直接添加到recentView的,但是没有看到坐标位置相关信息,继续找,于是在onLayout方法里找到如下代码:
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Layout the stack action button such that its drawable is start-aligned with the
// stack, vertically centered in the available space above the stack
Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
buttonBounds.bottom);
}
从代码可以看出mStackActionButton是通过buttonBounds确定位置的,继续跟踪getStackActionButtonBoundsFromStackLayout:
/**
* @return the bounds of the stack action button.
*/
private Rect getStackActionButtonBoundsFromStackLayout() {
Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
int left = isLayoutRtl()
? actionButtonRect.left - mStackActionButton.getPaddingLeft()
: actionButtonRect.right + mStackActionButton.getPaddingRight()
- mStackActionButton.getMeasuredWidth();
int top = actionButtonRect.top +
(actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
top + mStackActionButton.getMeasuredHeight());
return actionButtonRect;
}
从这里可以看到最终返回actionButtonRect的left、top、right、bottom的来源,根据UI给的坐标,于是我修改如下:
/**
* @return the bounds of the stack action button.
*/
private Rect getStackActionButtonBoundsFromStackLayout() {
Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
int left = 261;
int top = getMeasuredHeight()/2 - mStackActionButton.getMeasuredHeight() / 2;
actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
top + mStackActionButton.getMeasuredHeight());
return actionButtonRect;
}
left:靠左向右261px
top:是RecentView高度的1/2减去mStackActionButton高度的1/2,也就是垂直居中
right和bottom保持不变
于是需求1搞定,下一个
3.加一个垃圾桶图标
这个就简单了,打开SystemUI/res/layout/recents_stack_action_button.xml修改如下:
文字右侧添加图标android:drawableRight="@drawable/icon_delete_l",及间距android:drawablePadding="15px",搞定,mm编译push再reboot跑起来看看,效果如下:
好了,剩下随后一个需求了
4.手势划出app时,全部清空的图标会暂时隐藏
原生的mStackActionButton在任务列表往上滑的时候会隐藏,往下滑到顶部时再显示出来。而我们的需求是,左右滑动某个app时或者清除app时把它隐藏,没有这些操作时再显示出来。
首先要先找到mStackActionButton隐藏和显示的地方
首先在RecentsView中找到显示mStackActionButton的地方
public final void onBusEvent(ShowStackActionButtonEvent event) {
if (!RecentsDebugFlags.Static.EnableStackActionButton) {
return;
}
showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
}
然后在RecentsView中找到隐藏mStackActionButton的地方
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Hide the stack action button
hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
}
animateBackgroundScrim(0f, taskViewExitToHomeDuration);
}
public final void onBusEvent(AllTaskViewsDismissedEvent event) {
hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
}
public final void onBusEvent(HideStackActionButtonEvent event) {
if (!RecentsDebugFlags.Static.EnableStackActionButton) {
return;
}
hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
}
showStackActionButton和hideStackActionButton大家可以去看看,主要是一些动画的实现
ShowStackActionButtonEvent类是继承EventBus.Event的,EventBus继承BroadcastReceiver
搜索ShowStackActionButtonEvent在哪里使用
在TaskStackView中有如下代码:
@Override
public void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
mUIDozeTrigger.poke();
if (animation != null) {
relayoutTaskViewsOnNextFrame(animation);
}
// In grid layout, the stack action button always remains visible.
if (mEnterAnimationComplete && !useGridLayout()) {
if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
mStack.getTaskCount() > 0) {
EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */));
} else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
EventBus.getDefault().send(new HideStackActionButtonEvent());
}
}
}
private void updateStackActionButtonVisibility() {
// Always show the button in grid layout.
if (useGridLayout() ||
(mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
mStack.getTaskCount() > 0)) {
EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
} else {
EventBus.getDefault().send(new HideStackActionButtonEvent());
}
}
最终走
EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
继续跟踪 EventBus
public void send(Event event) {
......
queueEvent(event);
}
继续跟踪queueEvent
private void queueEvent(final Event event) {
ArrayList eventHandlers = mEventTypeMap.get(event.getClass());
if (eventHandlers == null) {
// This is just an optimization to return early if there are no handlers. However, we
// should still ensure that we call pre/post dispatch callbacks so that AnimatedEvents
// are still cleaned up correctly if a listener has not been registered to handle them
event.onPreDispatch();
event.onPostDispatch();
return;
}
// Prepare this event
boolean hasPostedEvent = false;
event.onPreDispatch();
// We need to clone the list in case a subscriber unregisters itself during traversal
// TODO: Investigate whether we can skip the object creation here
eventHandlers = (ArrayList) eventHandlers.clone();
int eventHandlerCount = eventHandlers.size();
for (int i = 0; i < eventHandlerCount; i++) {
final EventHandler eventHandler = eventHandlers.get(i);
if (eventHandler.subscriber.getReference() != null) {
if (event.requiresPost) {
mHandler.post(new Runnable() {
@Override
public void run() {
processEvent(eventHandler, event);
}
});
hasPostedEvent = true;
} else {
processEvent(eventHandler, event);
}
}
}
// Clean up after this event, deferring until all subscribers have been called
if (hasPostedEvent) {
mHandler.post(new Runnable() {
@Override
public void run() {
event.onPostDispatch();
}
});
} else {
event.onPostDispatch();
}
}
这里最终走的是processEvent,继续跟踪
private void processEvent(final EventHandler eventHandler, final Event event) {
// Skip if the event was already cancelled
if (event.cancelled) {
if (event.trace || DEBUG_TRACE_ALL) {
logWithPid("Event dispatch cancelled");
}
return;
}
try {
if (event.trace || DEBUG_TRACE_ALL) {
logWithPid(" -> " + eventHandler.toString());
}
Object sub = eventHandler.subscriber.getReference();
if (sub != null) {
long t1 = 0;
if (DEBUG_TRACE_ALL) {
t1 = SystemClock.currentTimeMicro();
}
eventHandler.method.invoke(sub, event);
if (DEBUG_TRACE_ALL) {
long duration = (SystemClock.currentTimeMicro() - t1);
mCallDurationMicros += duration;
mCallCount++;
logWithPid(eventHandler.method.toString() + " duration: " + duration +
" microseconds, avg: " + (mCallDurationMicros / mCallCount));
}
} else {
Log.e(TAG, "Failed to deliver event to null subscriber");
}
} catch (IllegalAccessException e) {
Log.e(TAG, "Failed to invoke method", e.getCause());
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
这里最终走的是eventHandler.method.invoke(sub, event);通过反射调用
sub是com.android.systemui.recents.views.RecentsView
event是com.android.systemui.recents.events.activity.ShowStackActionButtonEvent
最后执行RecentsView的onBusEvent(ShowStackActionButtonEvent event)方法
com.android.systemui.recents.views.RecentsView-> onBusEvent();
最后调用showStackActionButton和hideStackActionButton方法控制全部清除按钮的显示与隐藏
onStackScrollChanged是TaskStackView上下滑动的回调,我们的需求是上下滑动不用改变全部清除按钮的的状态,我们可以这么改:
@Override
public void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
mUIDozeTrigger.poke();
if (animation != null) {
relayoutTaskViewsOnNextFrame(animation);
}
// In grid layout, the stack action button always remains visible.
// if (mEnterAnimationComplete && !useGridLayout()) {
// if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
// curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
// mStack.getTaskCount() > 0) {
// EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */));
// } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
// curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
// EventBus.getDefault().send(new HideStackActionButtonEvent());
// }
// }
}
updateStackActionButtonVisibility可以这么改,一直显示全部清除按钮:
private void updateStackActionButtonVisibility() {
// Always show the button in grid layout.
if (useGridLayout() ||
(mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
mStack.getTaskCount() > 0)) {
EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
} else {
//EventBus.getDefault().send(new HideStackActionButtonEvent());
EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
}
}
接下来去找左右滑动
com.android.systemui.recents.views.TaskStackViewTouchHandler
1.->onBeginDrag 开始拖拽的时候隐藏
@Override
public void onBeginDrag(View v) {
......
EventBus.getDefault().send(new HideStackActionButtonEvent());
......
}
2.->onChildDismissed 清除任务后显示
@Override
public void onChildDismissed(View v) {
......
EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
......
}
3.->onChildSnappedBack 拖拽放开回弹后显示
@Override
public void onChildSnappedBack(View v, float targetLeft) {
......
EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
......
}