Simplifying app icon popup
> Using a single linearLayout instead of multiple nested views > Using clipToOutline for rounded corners instead of using canvas.saveLayer > Removing nested view elevations and overdraw > Using LayoutTransition for animating layout changes, instead of manually creating animators Change-Id: I8e57092f52ca5a032a2756594fdd39788acc5a0d
This commit is contained in:
parent
ea529083bd
commit
00ac920241
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2017 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?attr/popupColorTertiary" />
|
||||||
|
|
||||||
|
<item android:height="3dp" android:top="0dp">
|
||||||
|
<shape>
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="@android:color/transparent"
|
||||||
|
android:startColor="#33000000"
|
||||||
|
android:type="linear" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
|
@ -1,97 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (C) 2017 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<com.android.launcher3.notification.NotificationItemView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/notification_view"
|
|
||||||
android:layout_width="@dimen/bg_popup_item_width"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:theme="@style/PopupItem"
|
|
||||||
android:elevation="@dimen/deep_shortcuts_elevation">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:clipChildren="false">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/gutter_top"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:theme="@style/PopupGutter"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/header"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/notification_header_height"
|
|
||||||
android:paddingStart="@dimen/notification_padding_start"
|
|
||||||
android:paddingEnd="@dimen/notification_padding_end"
|
|
||||||
android:background="?attr/popupColorPrimary"
|
|
||||||
android:elevation="@dimen/notification_elevation"
|
|
||||||
android:layout_below="@id/gutter_top" >
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/notification_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="start"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:text="@string/notifications_header"
|
|
||||||
android:textSize="@dimen/notification_header_text_size"
|
|
||||||
android:textColor="?android:attr/textColorPrimary" />
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/notification_count"
|
|
||||||
android:layout_width="@dimen/notification_icon_size"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/notification_header_count_text_size"
|
|
||||||
android:fontFamily="sans-serif-medium"
|
|
||||||
android:textColor="?android:attr/textColorPrimary" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<include layout="@layout/notification_main"
|
|
||||||
android:id="@+id/main_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/notification_main_height"
|
|
||||||
android:layout_below="@id/header" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/popup_item_divider_height"
|
|
||||||
android:background="?attr/popupColorTertiary"
|
|
||||||
android:layout_below="@id/main_view"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<include layout="@layout/notification_footer"
|
|
||||||
android:id="@+id/footer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/notification_footer_height"
|
|
||||||
android:layout_below="@id/divider" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/gutter_bottom"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:theme="@style/PopupGutter"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_below="@id/footer" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</com.android.launcher3.notification.NotificationItemView>
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2017 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<merge
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<!-- header -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/notification_header_height"
|
||||||
|
android:paddingEnd="@dimen/notification_padding_end"
|
||||||
|
android:paddingStart="@dimen/notification_padding_start">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/notifications_header"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/notification_header_text_size" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_count"
|
||||||
|
android:layout_width="@dimen/notification_icon_size"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/notification_header_count_text_size" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<!-- Main view -->
|
||||||
|
<com.android.launcher3.notification.NotificationMainView
|
||||||
|
android:id="@+id/main_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/notification_main_height"
|
||||||
|
android:background="@drawable/bg_notification_content"
|
||||||
|
android:focusable="true" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/text_and_background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/popupColorPrimary"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="14dp"
|
||||||
|
android:paddingEnd="@dimen/notification_main_text_padding_end"
|
||||||
|
android:paddingStart="@dimen/notification_padding_start">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:lines="1"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/notification_main_title_size" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:lines="1"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="@dimen/notification_main_text_size" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/popup_item_icon"
|
||||||
|
android:layout_width="@dimen/notification_icon_size"
|
||||||
|
android:layout_height="@dimen/notification_icon_size"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:layout_marginBottom="7dp"
|
||||||
|
android:layout_marginEnd="@dimen/notification_padding_end" />
|
||||||
|
|
||||||
|
</com.android.launcher3.notification.NotificationMainView>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/popup_item_divider_height"
|
||||||
|
android:layout_below="@id/main_view"
|
||||||
|
android:background="?attr/popupColorTertiary" />
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<com.android.launcher3.notification.NotificationFooterLayout
|
||||||
|
android:id="@+id/footer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/notification_footer_height"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:clipChildren="false">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/icon_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:gravity="end|center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="@dimen/notification_footer_icon_row_padding"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/overflow"
|
||||||
|
android:layout_width="@dimen/horizontal_ellipsis_size"
|
||||||
|
android:layout_height="@dimen/horizontal_ellipsis_size"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
|
||||||
|
android:background="@drawable/horizontal_ellipsis" />
|
||||||
|
|
||||||
|
</com.android.launcher3.notification.NotificationFooterLayout>
|
||||||
|
</merge>
|
|
@ -1,46 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (C) 2017 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
<com.android.launcher3.notification.NotificationFooterLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:elevation="@dimen/notification_elevation"
|
|
||||||
android:clipChildren="false"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:background="?attr/popupColorPrimary">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/icon_row"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="end|center_vertical"
|
|
||||||
android:padding="@dimen/notification_footer_icon_row_padding"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:clipChildren="false"/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/overflow"
|
|
||||||
android:layout_width="@dimen/horizontal_ellipsis_size"
|
|
||||||
android:layout_height="@dimen/horizontal_ellipsis_size"
|
|
||||||
android:background="@drawable/horizontal_ellipsis"
|
|
||||||
android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
|
|
||||||
android:layout_gravity="start|center_vertical" />
|
|
||||||
|
|
||||||
</com.android.launcher3.notification.NotificationFooterLayout>
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2017 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<View
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="@drawable/bg_notification_content" />
|
|
@ -1,66 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (C) 2017 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
<com.android.launcher3.notification.NotificationMainView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:focusable="true"
|
|
||||||
android:elevation="@dimen/notification_elevation" >
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/text_and_background"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:background="?attr/popupColorPrimary"
|
|
||||||
android:paddingStart="@dimen/notification_padding_start"
|
|
||||||
android:paddingEnd="@dimen/notification_main_text_padding_end"
|
|
||||||
android:paddingBottom="14dp">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
android:fontFamily="sans-serif"
|
|
||||||
android:textSize="@dimen/notification_main_title_size"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:lines="1"
|
|
||||||
android:ellipsize="end" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="sans-serif"
|
|
||||||
android:textSize="@dimen/notification_main_text_size"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:lines="1"
|
|
||||||
android:ellipsize="end" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/popup_item_icon"
|
|
||||||
android:layout_width="@dimen/notification_icon_size"
|
|
||||||
android:layout_height="@dimen/notification_icon_size"
|
|
||||||
android:layout_marginEnd="@dimen/notification_padding_end"
|
|
||||||
android:layout_marginBottom="7dp"
|
|
||||||
android:layout_gravity="center_vertical|end" />
|
|
||||||
|
|
||||||
</com.android.launcher3.notification.NotificationMainView>
|
|
||||||
|
|
|
@ -19,11 +19,8 @@
|
||||||
android:id="@+id/deep_shortcuts_container"
|
android:id="@+id/deep_shortcuts_container"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="4dp"
|
android:background="?attr/popupColorPrimary"
|
||||||
android:paddingBottom="4dp"
|
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:elevation="@dimen/deep_shortcuts_elevation"
|
android:elevation="@dimen/deep_shortcuts_elevation"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical" />
|
||||||
|
|
||||||
</com.android.launcher3.popup.PopupContainerWithArrow>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (C) 2017 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<com.android.launcher3.shortcuts.ShortcutsItemView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/shortcuts_view"
|
|
||||||
android:layout_width="@dimen/bg_popup_item_width"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:elevation="@dimen/deep_shortcuts_elevation">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- The shortcuts header is added at runtime when necessary. -->
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/shortcuts"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.android.launcher3.shortcuts.ShortcutsItemView>
|
|
|
@ -22,6 +22,4 @@
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="end|center_vertical"
|
android:gravity="end|center_vertical"
|
||||||
android:background="?attr/popupColorSecondary"
|
android:background="?attr/popupColorSecondary"
|
||||||
android:elevation="1dp"
|
android:clipToPadding="true" />
|
||||||
android:outlineProvider="none" />
|
|
||||||
<!-- We have elevation so this is drawn on top, but no outline provider to remove shadow -->
|
|
||||||
|
|
|
@ -179,6 +179,7 @@
|
||||||
<dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
|
<dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
|
||||||
<dimen name="popup_padding_start">10dp</dimen>
|
<dimen name="popup_padding_start">10dp</dimen>
|
||||||
<dimen name="popup_padding_end">16dp</dimen>
|
<dimen name="popup_padding_end">16dp</dimen>
|
||||||
|
<dimen name="popup_vertical_padding">4dp</dimen>
|
||||||
<dimen name="popup_arrow_width">10dp</dimen>
|
<dimen name="popup_arrow_width">10dp</dimen>
|
||||||
<dimen name="popup_arrow_height">8dp</dimen>
|
<dimen name="popup_arrow_height">8dp</dimen>
|
||||||
<dimen name="popup_arrow_vertical_offset">-2dp</dimen>
|
<dimen name="popup_arrow_vertical_offset">-2dp</dimen>
|
||||||
|
@ -226,7 +227,6 @@
|
||||||
<dimen name="notification_footer_icon_size">18dp</dimen>
|
<dimen name="notification_footer_icon_size">18dp</dimen>
|
||||||
<!-- notification_icon_size + notification_padding_end + 16dp padding between icon and text -->
|
<!-- notification_icon_size + notification_padding_end + 16dp padding between icon and text -->
|
||||||
<dimen name="notification_main_text_padding_end">52dp</dimen>
|
<dimen name="notification_main_text_padding_end">52dp</dimen>
|
||||||
<dimen name="notification_elevation">2dp</dimen>
|
|
||||||
<dimen name="horizontal_ellipsis_size">18dp</dimen>
|
<dimen name="horizontal_ellipsis_size">18dp</dimen>
|
||||||
<!-- arrow_horizontal_offset_start - (ellipsis_size - arrow_width) / 2 -->
|
<!-- arrow_horizontal_offset_start - (ellipsis_size - arrow_width) / 2 -->
|
||||||
<dimen name="horizontal_ellipsis_offset">19dp</dimen>
|
<dimen name="horizontal_ellipsis_offset">19dp</dimen>
|
||||||
|
|
|
@ -136,13 +136,6 @@
|
||||||
<style name="PopupItem">
|
<style name="PopupItem">
|
||||||
<item name="android:colorControlHighlight">?attr/popupColorTertiary</item>
|
<item name="android:colorControlHighlight">?attr/popupColorTertiary</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="PopupGutter">
|
|
||||||
<item name="android:backgroundTintMode">multiply</item>
|
|
||||||
<item name="android:backgroundTint">?attr/popupColorSecondary</item>
|
|
||||||
<item name="android:background">@drawable/gutter_horizontal</item>
|
|
||||||
<item name="android:elevation">@dimen/notification_elevation</item>
|
|
||||||
<item name="android:outlineProvider">none</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- Drop targets -->
|
<!-- Drop targets -->
|
||||||
<style name="DropTargetButtonBase">
|
<style name="DropTargetButtonBase">
|
||||||
|
|
|
@ -118,7 +118,6 @@ import com.android.launcher3.logging.UserEventDispatcher;
|
||||||
import com.android.launcher3.model.ModelWriter;
|
import com.android.launcher3.model.ModelWriter;
|
||||||
import com.android.launcher3.notification.NotificationListener;
|
import com.android.launcher3.notification.NotificationListener;
|
||||||
import com.android.launcher3.pageindicators.PageIndicator;
|
import com.android.launcher3.pageindicators.PageIndicator;
|
||||||
import com.android.launcher3.popup.BaseActionPopup;
|
|
||||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||||
import com.android.launcher3.popup.PopupDataProvider;
|
import com.android.launcher3.popup.PopupDataProvider;
|
||||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||||
|
@ -1257,9 +1256,9 @@ public class Launcher extends BaseActivity
|
||||||
mWorkspace.updateIconBadges(updatedBadges);
|
mWorkspace.updateIconBadges(updatedBadges);
|
||||||
mAppsView.updateIconBadges(updatedBadges);
|
mAppsView.updateIconBadges(updatedBadges);
|
||||||
|
|
||||||
BaseActionPopup popup = BaseActionPopup.getOpen(Launcher.this);
|
PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this);
|
||||||
if (popup instanceof PopupContainerWithArrow) {
|
if (popup != null) {
|
||||||
((PopupContainerWithArrow) popup).updateNotificationHeader(updatedBadges);
|
popup.updateNotificationHeader(updatedBadges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3178,7 +3177,7 @@ public class Launcher extends BaseActivity
|
||||||
&& mAccessibilityDelegate.performAction(focusedView,
|
&& mAccessibilityDelegate.performAction(focusedView,
|
||||||
(ItemInfo) focusedView.getTag(),
|
(ItemInfo) focusedView.getTag(),
|
||||||
LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
|
LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
|
||||||
BaseActionPopup.getOpen(this).requestFocus();
|
PopupContainerWithArrow.getOpen(this).requestFocus();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -28,10 +28,6 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
|
||||||
/** Sets the progress, from 0 to 1, of the reveal animation. */
|
/** Sets the progress, from 0 to 1, of the reveal animation. */
|
||||||
abstract void setProgress(float progress);
|
abstract void setProgress(float progress);
|
||||||
|
|
||||||
public ValueAnimator createRevealAnimator(final View revealView) {
|
|
||||||
return createRevealAnimator(revealView, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
|
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
|
||||||
ValueAnimator va =
|
ValueAnimator va =
|
||||||
isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
|
isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
|
||||||
|
@ -39,8 +35,13 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
|
||||||
|
|
||||||
va.addListener(new AnimatorListenerAdapter() {
|
va.addListener(new AnimatorListenerAdapter() {
|
||||||
private boolean mWasCanceled = false;
|
private boolean mWasCanceled = false;
|
||||||
|
private boolean mIsClippedToOutline;
|
||||||
|
private ViewOutlineProvider mOldOutlineProvider;
|
||||||
|
|
||||||
public void onAnimationStart(Animator animation) {
|
public void onAnimationStart(Animator animation) {
|
||||||
|
mIsClippedToOutline = revealView.getClipToOutline();
|
||||||
|
mOldOutlineProvider = revealView.getOutlineProvider();
|
||||||
|
|
||||||
revealView.setOutlineProvider(RevealOutlineAnimation.this);
|
revealView.setOutlineProvider(RevealOutlineAnimation.this);
|
||||||
revealView.setClipToOutline(true);
|
revealView.setClipToOutline(true);
|
||||||
if (shouldRemoveElevationDuringAnimation()) {
|
if (shouldRemoveElevationDuringAnimation()) {
|
||||||
|
@ -55,8 +56,8 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
|
||||||
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
public void onAnimationEnd(Animator animation) {
|
||||||
if (!mWasCanceled) {
|
if (!mWasCanceled) {
|
||||||
revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
revealView.setOutlineProvider(mOldOutlineProvider);
|
||||||
revealView.setClipToOutline(false);
|
revealView.setClipToOutline(mIsClippedToOutline);
|
||||||
if (shouldRemoveElevationDuringAnimation()) {
|
if (shouldRemoveElevationDuringAnimation()) {
|
||||||
revealView.setTranslationZ(0);
|
revealView.setTranslationZ(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,6 @@ package com.android.launcher3.anim;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
|
||||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
|
||||||
|
|
||||||
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
|
|
||||||
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
|
* A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
|
||||||
* and two {@link Rect}s.
|
* and two {@link Rect}s.
|
||||||
|
@ -37,21 +32,12 @@ public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
|
||||||
private final Rect mStartRect;
|
private final Rect mStartRect;
|
||||||
private final Rect mEndRect;
|
private final Rect mEndRect;
|
||||||
|
|
||||||
private final @PopupContainerWithArrow.RoundedCornerFlags int mRoundedCorners;
|
|
||||||
|
|
||||||
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
|
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
|
||||||
Rect endRect) {
|
Rect endRect) {
|
||||||
this(startRadius, endRadius, startRect, endRect,
|
|
||||||
ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
|
|
||||||
Rect endRect, int roundedCorners) {
|
|
||||||
mStartRadius = startRadius;
|
mStartRadius = startRadius;
|
||||||
mEndRadius = endRadius;
|
mEndRadius = endRadius;
|
||||||
mStartRect = startRect;
|
mStartRect = startRect;
|
||||||
mEndRect = endRect;
|
mEndRect = endRect;
|
||||||
mRoundedCorners = roundedCorners;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -65,13 +51,7 @@ public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
|
||||||
|
|
||||||
mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
|
mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
|
||||||
mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
|
mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
|
||||||
if ((mRoundedCorners & ROUNDED_TOP_CORNERS) == 0) {
|
|
||||||
mOutline.top -= mOutlineRadius;
|
|
||||||
}
|
|
||||||
mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
|
mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
|
||||||
mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
|
mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
|
||||||
if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) == 0) {
|
|
||||||
mOutline.bottom += mOutlineRadius;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
|
||||||
import com.android.launcher3.ItemInfo;
|
import com.android.launcher3.ItemInfo;
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
||||||
import com.android.launcher3.popup.BaseActionPopup;
|
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -46,7 +46,7 @@ public class CustomActionsPopup implements OnMenuItemClickListener {
|
||||||
public CustomActionsPopup(Launcher launcher, View icon) {
|
public CustomActionsPopup(Launcher launcher, View icon) {
|
||||||
mLauncher = launcher;
|
mLauncher = launcher;
|
||||||
mIcon = icon;
|
mIcon = icon;
|
||||||
BaseActionPopup container = BaseActionPopup.getOpen(launcher);
|
PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
|
||||||
if (container != null) {
|
if (container != null) {
|
||||||
mDelegate = container.getAccessibilityDelegate();
|
mDelegate = container.getAccessibilityDelegate();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,22 +23,18 @@ import android.animation.ObjectAnimator;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import com.android.launcher3.Launcher;
|
|
||||||
import com.android.launcher3.LauncherAnimUtils;
|
import com.android.launcher3.LauncherAnimUtils;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.Utilities;
|
import com.android.launcher3.Utilities;
|
||||||
import com.android.launcher3.anim.PropertyListBuilder;
|
import com.android.launcher3.anim.PropertyListBuilder;
|
||||||
import com.android.launcher3.anim.PropertyResetListener;
|
import com.android.launcher3.anim.PropertyResetListener;
|
||||||
import com.android.launcher3.popup.BaseActionPopup;
|
import com.android.launcher3.util.Themes;
|
||||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -61,11 +57,12 @@ public class NotificationFooterLayout extends FrameLayout {
|
||||||
private final List<NotificationInfo> mNotifications = new ArrayList<>();
|
private final List<NotificationInfo> mNotifications = new ArrayList<>();
|
||||||
private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
|
private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
|
||||||
private final boolean mRtl;
|
private final boolean mRtl;
|
||||||
|
private final int mBackgroundColor;
|
||||||
|
|
||||||
FrameLayout.LayoutParams mIconLayoutParams;
|
FrameLayout.LayoutParams mIconLayoutParams;
|
||||||
private View mOverflowEllipsis;
|
private View mOverflowEllipsis;
|
||||||
private LinearLayout mIconRow;
|
private LinearLayout mIconRow;
|
||||||
private int mBackgroundColor;
|
private NotificationItemView mContainer;
|
||||||
|
|
||||||
public NotificationFooterLayout(Context context) {
|
public NotificationFooterLayout(Context context) {
|
||||||
this(context, null, 0);
|
this(context, null, 0);
|
||||||
|
@ -93,14 +90,19 @@ public class NotificationFooterLayout extends FrameLayout {
|
||||||
int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
|
int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
|
||||||
- iconSize * MAX_FOOTER_NOTIFICATIONS;
|
- iconSize * MAX_FOOTER_NOTIFICATIONS;
|
||||||
mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
|
mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
|
||||||
|
|
||||||
|
mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
mOverflowEllipsis = findViewById(R.id.overflow);
|
mOverflowEllipsis = findViewById(R.id.overflow);
|
||||||
mIconRow = (LinearLayout) findViewById(R.id.icon_row);
|
mIconRow = findViewById(R.id.icon_row);
|
||||||
mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
|
}
|
||||||
|
|
||||||
|
void setContainer(NotificationItemView container) {
|
||||||
|
mContainer = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -198,25 +200,8 @@ public class NotificationFooterLayout extends FrameLayout {
|
||||||
updateOverflowEllipsisVisibility();
|
updateOverflowEllipsisVisibility();
|
||||||
if (mIconRow.getChildCount() == 0) {
|
if (mIconRow.getChildCount() == 0) {
|
||||||
// There are no more icons in the footer, so hide it.
|
// There are no more icons in the footer, so hide it.
|
||||||
BaseActionPopup popup = BaseActionPopup.getOpen(
|
if (mContainer != null) {
|
||||||
Launcher.getLauncher(getContext()));
|
mContainer.removeFooter();
|
||||||
if (popup instanceof PopupContainerWithArrow) {
|
|
||||||
final int newHeight = getResources().getDimensionPixelSize(
|
|
||||||
R.dimen.notification_empty_footer_height);
|
|
||||||
Animator collapseFooter = ((PopupContainerWithArrow) popup)
|
|
||||||
.reduceNotificationViewHeight(getHeight() - newHeight,
|
|
||||||
getResources().getInteger(R.integer.config_removeNotificationViewDuration));
|
|
||||||
collapseFooter.addListener(new AnimatorListenerAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE);
|
|
||||||
// Keep view around because gutter is aligned to it, but remove height to
|
|
||||||
// both hide the view and keep calculations correct for last dismissal.
|
|
||||||
getLayoutParams().height = newHeight;
|
|
||||||
requestLayout();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
collapseFooter.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,117 +16,102 @@
|
||||||
|
|
||||||
package com.android.launcher3.notification;
|
package com.android.launcher3.notification;
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.view.ViewGroup.MarginLayoutParams;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.launcher3.ItemInfo;
|
|
||||||
import com.android.launcher3.LauncherAnimUtils;
|
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.anim.PropertyResetListener;
|
|
||||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
|
||||||
import com.android.launcher3.graphics.IconPalette;
|
import com.android.launcher3.graphics.IconPalette;
|
||||||
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
|
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||||
import com.android.launcher3.popup.PopupItemView;
|
|
||||||
import com.android.launcher3.touch.SwipeDetector;
|
import com.android.launcher3.touch.SwipeDetector;
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
|
||||||
import com.android.launcher3.util.Themes;
|
import com.android.launcher3.util.Themes;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link FrameLayout} that contains a header, main view and a footer.
|
* Utility class to manage notification UI
|
||||||
* The main view contains the icon and text (title + subtext) of the first notification.
|
|
||||||
* The footer contains: A list of just the icons of all the notifications past the first one.
|
|
||||||
* @see NotificationFooterLayout
|
|
||||||
*/
|
*/
|
||||||
public class NotificationItemView extends PopupItemView implements LogContainerProvider {
|
public class NotificationItemView {
|
||||||
|
|
||||||
private static final Rect sTempRect = new Rect();
|
private static final Rect sTempRect = new Rect();
|
||||||
|
|
||||||
private TextView mHeaderText;
|
private final Context mContext;
|
||||||
private TextView mHeaderCount;
|
private final PopupContainerWithArrow mContainer;
|
||||||
private NotificationMainView mMainView;
|
|
||||||
private NotificationFooterLayout mFooter;
|
private final TextView mHeaderText;
|
||||||
private SwipeDetector mSwipeDetector;
|
private final TextView mHeaderCount;
|
||||||
|
private final NotificationMainView mMainView;
|
||||||
|
private final NotificationFooterLayout mFooter;
|
||||||
|
private final SwipeDetector mSwipeDetector;
|
||||||
|
private final View mIconView;
|
||||||
|
|
||||||
|
private final View mHeader;
|
||||||
|
private final View mDivider;
|
||||||
|
|
||||||
|
private View mGutter;
|
||||||
|
|
||||||
|
private boolean mIgnoreTouch = false;
|
||||||
private boolean mAnimatingNextIcon;
|
private boolean mAnimatingNextIcon;
|
||||||
private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
|
private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
|
||||||
|
|
||||||
public NotificationItemView(Context context) {
|
public NotificationItemView(PopupContainerWithArrow container) {
|
||||||
this(context, null, 0);
|
mContainer = container;
|
||||||
}
|
mContext = container.getContext();
|
||||||
|
|
||||||
public NotificationItemView(Context context, AttributeSet attrs) {
|
mHeaderText = container.findViewById(R.id.notification_text);
|
||||||
this(context, attrs, 0);
|
mHeaderCount = container.findViewById(R.id.notification_count);
|
||||||
}
|
mMainView = container.findViewById(R.id.main_view);
|
||||||
|
mFooter = container.findViewById(R.id.footer);
|
||||||
|
mIconView = container.findViewById(R.id.popup_item_icon);
|
||||||
|
|
||||||
public NotificationItemView(Context context, AttributeSet attrs, int defStyle) {
|
mHeader = container.findViewById(R.id.header);
|
||||||
super(context, attrs, defStyle);
|
mDivider = container.findViewById(R.id.divider);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL);
|
||||||
protected void onFinishInflate() {
|
|
||||||
super.onFinishInflate();
|
|
||||||
mHeaderText = findViewById(R.id.notification_text);
|
|
||||||
mHeaderCount = findViewById(R.id.notification_count);
|
|
||||||
mMainView = findViewById(R.id.main_view);
|
|
||||||
mFooter = findViewById(R.id.footer);
|
|
||||||
|
|
||||||
mSwipeDetector = new SwipeDetector(getContext(), mMainView, SwipeDetector.HORIZONTAL);
|
|
||||||
mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
|
mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
|
||||||
mMainView.setSwipeDetector(mSwipeDetector);
|
mMainView.setSwipeDetector(mSwipeDetector);
|
||||||
|
mFooter.setContainer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NotificationMainView getMainView() {
|
public void addGutter() {
|
||||||
return mMainView;
|
if (mGutter == null) {
|
||||||
|
mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void removeFooter() {
|
||||||
* This method is used to calculate the height to remove when dismissing the last notification.
|
if (mContainer.indexOfChild(mFooter) >= 0) {
|
||||||
* We subtract the height of the footer in this case since the footer should be gone or in the
|
mContainer.removeView(mFooter);
|
||||||
* process of being removed.
|
mContainer.removeView(mDivider);
|
||||||
* @return The height of the entire notification item, minus the footer if it still exists.
|
|
||||||
*/
|
|
||||||
public int getHeightMinusFooter() {
|
|
||||||
if (mFooter.getParent() == null) {
|
|
||||||
return getHeight();
|
|
||||||
}
|
}
|
||||||
int excessFooterHeight = mFooter.getHeight() - getResources().getDimensionPixelSize(
|
|
||||||
R.dimen.notification_empty_footer_height);
|
|
||||||
return getHeight() - excessFooterHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
|
public void inverseGutterMargin() {
|
||||||
AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
|
MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
|
||||||
|
int top = lp.topMargin;
|
||||||
Rect startRect = new Rect(mPillRect);
|
lp.topMargin = lp.bottomMargin;
|
||||||
Rect endRect = new Rect(mPillRect);
|
lp.bottomMargin = top;
|
||||||
if (shouldRemoveFromTop) {
|
|
||||||
endRect.top += heightToRemove;
|
|
||||||
} else {
|
|
||||||
endRect.bottom -= heightToRemove;
|
|
||||||
}
|
|
||||||
anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
|
|
||||||
startRect, endRect, mRoundedCorners).createRevealAnimator(this, false));
|
|
||||||
|
|
||||||
View bottomGutter = findViewById(R.id.gutter_bottom);
|
|
||||||
if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) {
|
|
||||||
Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y,
|
|
||||||
-heightToRemove);
|
|
||||||
translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f));
|
|
||||||
anim.play(translateGutter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return anim;
|
public void removeAllViews() {
|
||||||
|
mContainer.removeView(mMainView);
|
||||||
|
mContainer.removeView(mHeader);
|
||||||
|
|
||||||
|
if (mContainer.indexOfChild(mFooter) >= 0) {
|
||||||
|
mContainer.removeView(mFooter);
|
||||||
|
mContainer.removeView(mDivider);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mGutter != null) {
|
||||||
|
mContainer.removeView(mGutter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
|
public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
|
||||||
|
@ -134,32 +119,44 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
|
||||||
if (palette != null) {
|
if (palette != null) {
|
||||||
if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
|
if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
|
||||||
mNotificationHeaderTextColor =
|
mNotificationHeaderTextColor =
|
||||||
IconPalette.resolveContrastColor(getContext(), palette.dominantColor,
|
IconPalette.resolveContrastColor(mContext, palette.dominantColor,
|
||||||
Themes.getAttrColor(getContext(), R.attr.popupColorPrimary));
|
Themes.getAttrColor(mContext, R.attr.popupColorPrimary));
|
||||||
}
|
}
|
||||||
mHeaderText.setTextColor(mNotificationHeaderTextColor);
|
mHeaderText.setTextColor(mNotificationHeaderTextColor);
|
||||||
mHeaderCount.setTextColor(mNotificationHeaderTextColor);
|
mHeaderCount.setTextColor(mNotificationHeaderTextColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
|
||||||
|
mMainView.getRight(), mMainView.getBottom());
|
||||||
|
mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
|
||||||
|
if (!mIgnoreTouch) {
|
||||||
|
mContainer.getParent().requestDisallowInterceptTouchEvent(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mIgnoreTouch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (mMainView.getNotificationInfo() == null) {
|
if (mMainView.getNotificationInfo() == null) {
|
||||||
// The notification hasn't been populated yet.
|
// The notification hasn't been populated yet.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
getParent().requestDisallowInterceptTouchEvent(true);
|
|
||||||
mSwipeDetector.onTouchEvent(ev);
|
mSwipeDetector.onTouchEvent(ev);
|
||||||
return mSwipeDetector.isDraggingOrSettling();
|
return mSwipeDetector.isDraggingOrSettling();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent ev) {
|
public boolean onTouchEvent(MotionEvent ev) {
|
||||||
|
if (mIgnoreTouch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (mMainView.getNotificationInfo() == null) {
|
if (mMainView.getNotificationInfo() == null) {
|
||||||
// The notification hasn't been populated yet.
|
// The notification hasn't been populated yet.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return mSwipeDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
|
return mSwipeDetector.onTouchEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
|
public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
|
||||||
|
@ -168,7 +165,7 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationInfo mainNotification = notificationInfos.get(0);
|
NotificationInfo mainNotification = notificationInfos.get(0);
|
||||||
mMainView.applyNotificationInfo(mainNotification, mIconView);
|
mMainView.applyNotificationInfo(mainNotification, false);
|
||||||
|
|
||||||
for (int i = 1; i < notificationInfos.size(); i++) {
|
for (int i = 1; i < notificationInfos.size(); i++) {
|
||||||
mFooter.addNotificationInfo(notificationInfos.get(i));
|
mFooter.addNotificationInfo(notificationInfos.get(i));
|
||||||
|
@ -182,29 +179,18 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
|
||||||
if (dismissedMainNotification && !mAnimatingNextIcon) {
|
if (dismissedMainNotification && !mAnimatingNextIcon) {
|
||||||
// Animate the next icon into place as the new main notification.
|
// Animate the next icon into place as the new main notification.
|
||||||
mAnimatingNextIcon = true;
|
mAnimatingNextIcon = true;
|
||||||
mMainView.setVisibility(INVISIBLE);
|
mMainView.setContentVisibility(View.INVISIBLE);
|
||||||
mMainView.setTranslationX(0);
|
mMainView.setContentTranslation(0);
|
||||||
mIconView.getGlobalVisibleRect(sTempRect);
|
mIconView.getGlobalVisibleRect(sTempRect);
|
||||||
mFooter.animateFirstNotificationTo(sTempRect,
|
mFooter.animateFirstNotificationTo(sTempRect, (newMainNotification) -> {
|
||||||
new NotificationFooterLayout.IconAnimationEndListener() {
|
|
||||||
@Override
|
|
||||||
public void onIconAnimationEnd(NotificationInfo newMainNotification) {
|
|
||||||
if (newMainNotification != null) {
|
if (newMainNotification != null) {
|
||||||
mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
|
mMainView.applyNotificationInfo(newMainNotification, true);
|
||||||
mMainView.setVisibility(VISIBLE);
|
mMainView.setContentVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
mAnimatingNextIcon = false;
|
mAnimatingNextIcon = false;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
mFooter.trimNotifications(notificationKeys);
|
mFooter.trimNotifications(notificationKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
|
|
||||||
LauncherLogProto.Target targetParent) {
|
|
||||||
target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
|
|
||||||
targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,17 @@ package com.android.launcher3.notification;
|
||||||
|
|
||||||
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
|
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.drawable.RippleDrawable;
|
import android.graphics.drawable.RippleDrawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.FloatProperty;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
@ -33,6 +37,7 @@ import android.widget.TextView;
|
||||||
import com.android.launcher3.ItemInfo;
|
import com.android.launcher3.ItemInfo;
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
|
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||||
import com.android.launcher3.touch.OverScroll;
|
import com.android.launcher3.touch.OverScroll;
|
||||||
import com.android.launcher3.touch.SwipeDetector;
|
import com.android.launcher3.touch.SwipeDetector;
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||||
|
@ -42,13 +47,33 @@ import com.android.launcher3.util.Themes;
|
||||||
* A {@link android.widget.FrameLayout} that contains a single notification,
|
* A {@link android.widget.FrameLayout} that contains a single notification,
|
||||||
* e.g. icon + title + text.
|
* e.g. icon + title + text.
|
||||||
*/
|
*/
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
|
public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
|
||||||
|
|
||||||
|
private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
|
||||||
|
new FloatProperty<NotificationMainView>("contentTranslation") {
|
||||||
|
@Override
|
||||||
|
public void setValue(NotificationMainView view, float v) {
|
||||||
|
view.setContentTranslation(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(NotificationMainView view) {
|
||||||
|
return view.mTextAndBackground.getTranslationX();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is used only to track the notification view, so that it can be properly logged.
|
||||||
|
public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo();
|
||||||
|
|
||||||
|
private final ObjectAnimator mContentTranslateAnimator;
|
||||||
|
|
||||||
private NotificationInfo mNotificationInfo;
|
private NotificationInfo mNotificationInfo;
|
||||||
private ViewGroup mTextAndBackground;
|
private ViewGroup mTextAndBackground;
|
||||||
private int mBackgroundColor;
|
private int mBackgroundColor;
|
||||||
private TextView mTitleView;
|
private TextView mTitleView;
|
||||||
private TextView mTextView;
|
private TextView mTextView;
|
||||||
|
private View mIconView;
|
||||||
|
|
||||||
private SwipeDetector mSwipeDetector;
|
private SwipeDetector mSwipeDetector;
|
||||||
|
|
||||||
|
@ -62,25 +87,24 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
|
||||||
|
|
||||||
public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
|
public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
|
|
||||||
|
mContentTranslateAnimator = ObjectAnimator.ofFloat(this, CONTENT_TRANSLATION, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
|
|
||||||
mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
|
mTextAndBackground = findViewById(R.id.text_and_background);
|
||||||
ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
|
ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
|
||||||
mBackgroundColor = colorBackground.getColor();
|
mBackgroundColor = colorBackground.getColor();
|
||||||
RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
|
RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
|
||||||
Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
|
Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
|
||||||
colorBackground, null);
|
colorBackground, null);
|
||||||
mTextAndBackground.setBackground(rippleBackground);
|
mTextAndBackground.setBackground(rippleBackground);
|
||||||
mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
|
mTitleView = mTextAndBackground.findViewById(R.id.title);
|
||||||
mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
|
mTextView = mTextAndBackground.findViewById(R.id.text);
|
||||||
}
|
mIconView = findViewById(R.id.popup_item_icon);
|
||||||
|
|
||||||
public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
|
|
||||||
applyNotificationInfo(mainNotification, iconView, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSwipeDetector(SwipeDetector swipeDetector) {
|
public void setSwipeDetector(SwipeDetector swipeDetector) {
|
||||||
|
@ -90,8 +114,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
|
||||||
/**
|
/**
|
||||||
* Sets the content of this view, animating it after a new icon shifts up if necessary.
|
* Sets the content of this view, animating it after a new icon shifts up if necessary.
|
||||||
*/
|
*/
|
||||||
public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
|
public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) {
|
||||||
boolean animate) {
|
|
||||||
mNotificationInfo = mainNotification;
|
mNotificationInfo = mainNotification;
|
||||||
CharSequence title = mNotificationInfo.title;
|
CharSequence title = mNotificationInfo.title;
|
||||||
CharSequence text = mNotificationInfo.text;
|
CharSequence text = mNotificationInfo.text;
|
||||||
|
@ -103,20 +126,30 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
|
||||||
mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString());
|
mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString());
|
||||||
mTextView.setVisibility(GONE);
|
mTextView.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
|
mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
|
||||||
mBackgroundColor));
|
mBackgroundColor));
|
||||||
if (mNotificationInfo.intent != null) {
|
if (mNotificationInfo.intent != null) {
|
||||||
setOnClickListener(mNotificationInfo);
|
setOnClickListener(mNotificationInfo);
|
||||||
}
|
}
|
||||||
setTranslationX(0);
|
setContentTranslation(0);
|
||||||
// Add a dummy ItemInfo so that logging populates the correct container and item types
|
// Add a dummy ItemInfo so that logging populates the correct container and item types
|
||||||
// instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
|
// instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
|
||||||
setTag(new ItemInfo());
|
setTag(NOTIFICATION_ITEM_INFO);
|
||||||
if (animate) {
|
if (animate) {
|
||||||
ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
|
ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setContentTranslation(float translation) {
|
||||||
|
mTextAndBackground.setTranslationX(translation);
|
||||||
|
mIconView.setTranslationX(translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentVisibility(int visibility) {
|
||||||
|
mTextAndBackground.setVisibility(visibility);
|
||||||
|
mIconView.setVisibility(visibility);
|
||||||
|
}
|
||||||
|
|
||||||
public NotificationInfo getNotificationInfo() {
|
public NotificationInfo getNotificationInfo() {
|
||||||
return mNotificationInfo;
|
return mNotificationInfo;
|
||||||
}
|
}
|
||||||
|
@ -143,9 +176,9 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onDrag(float displacement, float velocity) {
|
public boolean onDrag(float displacement, float velocity) {
|
||||||
setTranslationX(canChildBeDismissed()
|
setContentTranslation(canChildBeDismissed()
|
||||||
? displacement : OverScroll.dampedScroll(displacement, getWidth()));
|
? displacement : OverScroll.dampedScroll(displacement, getWidth()));
|
||||||
animate().cancel();
|
mContentTranslateAnimator.cancel();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +186,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
|
||||||
public void onDragEnd(float velocity, boolean fling) {
|
public void onDragEnd(float velocity, boolean fling) {
|
||||||
final boolean willExit;
|
final boolean willExit;
|
||||||
final float endTranslation;
|
final float endTranslation;
|
||||||
|
final float startTranslation = mTextAndBackground.getTranslationX();
|
||||||
|
|
||||||
if (!canChildBeDismissed()) {
|
if (!canChildBeDismissed()) {
|
||||||
willExit = false;
|
willExit = false;
|
||||||
|
@ -160,28 +194,30 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
|
||||||
} else if (fling) {
|
} else if (fling) {
|
||||||
willExit = true;
|
willExit = true;
|
||||||
endTranslation = velocity < 0 ? - getWidth() : getWidth();
|
endTranslation = velocity < 0 ? - getWidth() : getWidth();
|
||||||
} else if (Math.abs(getTranslationX()) > getWidth() / 2) {
|
} else if (Math.abs(startTranslation) > getWidth() / 2) {
|
||||||
willExit = true;
|
willExit = true;
|
||||||
endTranslation = (getTranslationX() < 0 ? -getWidth() : getWidth());
|
endTranslation = (startTranslation < 0 ? -getWidth() : getWidth());
|
||||||
} else {
|
} else {
|
||||||
willExit = false;
|
willExit = false;
|
||||||
endTranslation = 0;
|
endTranslation = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
long duration = SwipeDetector.calculateDuration(velocity,
|
long duration = SwipeDetector.calculateDuration(velocity,
|
||||||
(endTranslation - getTranslationX()) / getWidth());
|
(endTranslation - startTranslation) / getWidth());
|
||||||
animate()
|
|
||||||
.setDuration(duration)
|
mContentTranslateAnimator.removeAllListeners();
|
||||||
.setInterpolator(scrollInterpolatorForVelocity(velocity))
|
mContentTranslateAnimator.setDuration(duration)
|
||||||
.translationX(endTranslation)
|
.setInterpolator(scrollInterpolatorForVelocity(velocity));
|
||||||
.withEndAction(new Runnable() {
|
mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation);
|
||||||
|
mContentTranslateAnimator.addListener(new AnimationSuccessListener() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void onAnimationSuccess(Animator animator) {
|
||||||
mSwipeDetector.finishedScrolling();
|
mSwipeDetector.finishedScrolling();
|
||||||
if (willExit) {
|
if (willExit) {
|
||||||
onChildDismissed();
|
onChildDismissed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
|
mContentTranslateAnimator.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,599 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.launcher3.popup;
|
|
||||||
|
|
||||||
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.animation.TimeInterpolator;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.CornerPathEffect;
|
|
||||||
import android.graphics.Outline;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.PointF;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.support.annotation.IntDef;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewConfiguration;
|
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.android.launcher3.AbstractFloatingView;
|
|
||||||
import com.android.launcher3.Launcher;
|
|
||||||
import com.android.launcher3.LauncherAnimUtils;
|
|
||||||
import com.android.launcher3.R;
|
|
||||||
import com.android.launcher3.Utilities;
|
|
||||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
|
||||||
import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
|
|
||||||
import com.android.launcher3.anim.PropertyListBuilder;
|
|
||||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
|
||||||
import com.android.launcher3.dragndrop.DragLayer;
|
|
||||||
import com.android.launcher3.graphics.TriangleShape;
|
|
||||||
import com.android.launcher3.logging.LoggerUtils;
|
|
||||||
import com.android.launcher3.notification.NotificationItemView;
|
|
||||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
|
||||||
import com.android.launcher3.shortcuts.ShortcutsItemView;
|
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
|
|
||||||
import com.android.launcher3.util.PackageUserKey;
|
|
||||||
import com.android.launcher3.util.Themes;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base popup container for showing shortcuts to deep links within apps.
|
|
||||||
*/
|
|
||||||
@TargetApi(Build.VERSION_CODES.N)
|
|
||||||
public class BaseActionPopup<V extends TextView> extends AbstractFloatingView {
|
|
||||||
|
|
||||||
public static final int ROUNDED_TOP_CORNERS = 1 << 0;
|
|
||||||
public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1;
|
|
||||||
|
|
||||||
@IntDef(flag = true, value = {
|
|
||||||
ROUNDED_TOP_CORNERS,
|
|
||||||
ROUNDED_BOTTOM_CORNERS
|
|
||||||
})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
public @interface RoundedCornerFlags {}
|
|
||||||
|
|
||||||
protected final Launcher mLauncher;
|
|
||||||
protected final LauncherAccessibilityDelegate mAccessibilityDelegate;
|
|
||||||
private final boolean mIsRtl;
|
|
||||||
|
|
||||||
public ShortcutsItemView mShortcutsItemView;
|
|
||||||
|
|
||||||
protected V mOriginalIcon;
|
|
||||||
private final Rect mTempRect = new Rect();
|
|
||||||
private PointF mInterceptTouchDown = new PointF();
|
|
||||||
private boolean mIsLeftAligned;
|
|
||||||
protected boolean mIsAboveIcon;
|
|
||||||
protected View mArrow;
|
|
||||||
private int mGravity;
|
|
||||||
|
|
||||||
protected Animator mOpenCloseAnimator;
|
|
||||||
protected boolean mDeferContainerRemoval;
|
|
||||||
private final Rect mStartRect = new Rect();
|
|
||||||
private final Rect mEndRect = new Rect();
|
|
||||||
|
|
||||||
public BaseActionPopup(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
mLauncher = Launcher.getLauncher(context);
|
|
||||||
|
|
||||||
mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
|
|
||||||
mIsRtl = Utilities.isRtl(getResources());
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseActionPopup(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseActionPopup(Context context) {
|
|
||||||
this(context, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
|
|
||||||
return mAccessibilityDelegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected PopupItemView getItemViewAt(int index) {
|
|
||||||
if (!mIsAboveIcon) {
|
|
||||||
// Opening down, so arrow is the first view.
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return (PopupItemView) getChildAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getItemCount() {
|
|
||||||
// All children except the arrow are items.
|
|
||||||
return getChildCount() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void animateOpen() {
|
|
||||||
setVisibility(View.VISIBLE);
|
|
||||||
mIsOpen = true;
|
|
||||||
|
|
||||||
final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
|
|
||||||
final Resources res = getResources();
|
|
||||||
final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
|
|
||||||
final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
|
|
||||||
|
|
||||||
// Rectangular reveal.
|
|
||||||
int itemsTotalHeight = 0;
|
|
||||||
for (int i = 0; i < getItemCount(); i++) {
|
|
||||||
itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
|
|
||||||
}
|
|
||||||
Point startPoint = computeAnimStartPoint(itemsTotalHeight);
|
|
||||||
int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
|
|
||||||
float radius = getItemViewAt(0).getBackgroundRadius();
|
|
||||||
mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
|
|
||||||
mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
|
|
||||||
final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider
|
|
||||||
(radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false);
|
|
||||||
revealAnim.setDuration(revealDuration);
|
|
||||||
revealAnim.setInterpolator(revealInterpolator);
|
|
||||||
|
|
||||||
Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
|
|
||||||
fadeIn.setDuration(revealDuration);
|
|
||||||
fadeIn.setInterpolator(revealInterpolator);
|
|
||||||
openAnim.play(fadeIn);
|
|
||||||
|
|
||||||
// Animate the arrow.
|
|
||||||
mArrow.setScaleX(0);
|
|
||||||
mArrow.setScaleY(0);
|
|
||||||
Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger(
|
|
||||||
R.integer.config_popupArrowOpenDuration));
|
|
||||||
|
|
||||||
openAnim.addListener(new AnimatorListenerAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
mOpenCloseAnimator = null;
|
|
||||||
Utilities.sendCustomAccessibilityEvent(
|
|
||||||
BaseActionPopup.this,
|
|
||||||
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
|
|
||||||
getContext().getString(R.string.action_deep_shortcut));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mOpenCloseAnimator = openAnim;
|
|
||||||
openAnim.playSequentially(revealAnim, arrowScale);
|
|
||||||
openAnim.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
||||||
super.onLayout(changed, l, t, r, b);
|
|
||||||
enforceContainedWithinScreen(l, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enforceContainedWithinScreen(int left, int right) {
|
|
||||||
DragLayer dragLayer = mLauncher.getDragLayer();
|
|
||||||
if (getTranslationX() + left < 0 ||
|
|
||||||
getTranslationX() + right > dragLayer.getWidth()) {
|
|
||||||
// If we are still off screen, center horizontally too.
|
|
||||||
mGravity |= Gravity.CENTER_HORIZONTAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Gravity.isHorizontal(mGravity)) {
|
|
||||||
setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
|
|
||||||
}
|
|
||||||
if (Gravity.isVertical(mGravity)) {
|
|
||||||
setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the point at which the center of the arrow merges with the first popup item.
|
|
||||||
*/
|
|
||||||
private Point computeAnimStartPoint(int itemsTotalHeight) {
|
|
||||||
int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
|
|
||||||
R.dimen.popup_arrow_horizontal_center_start:
|
|
||||||
R.dimen.popup_arrow_horizontal_center_end);
|
|
||||||
if (!mIsLeftAligned) {
|
|
||||||
arrowCenterX = getMeasuredWidth() - arrowCenterX;
|
|
||||||
}
|
|
||||||
int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom()
|
|
||||||
- itemsTotalHeight;
|
|
||||||
// The y-coordinate of edge between the arrow and the first popup item.
|
|
||||||
int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight);
|
|
||||||
return new Point(arrowCenterX, arrowEdge);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Orients this container above or below the given icon, aligning with the left or right.
|
|
||||||
*
|
|
||||||
* These are the preferred orientations, in order (RTL prefers right-aligned over left):
|
|
||||||
* - Above and left-aligned
|
|
||||||
* - Above and right-aligned
|
|
||||||
* - Below and left-aligned
|
|
||||||
* - Below and right-aligned
|
|
||||||
*
|
|
||||||
* So we always align left if there is enough horizontal space
|
|
||||||
* and align above if there is enough vertical space.
|
|
||||||
*/
|
|
||||||
protected void orientAboutIcon(int arrowHeight) {
|
|
||||||
int width = getMeasuredWidth();
|
|
||||||
int height = getMeasuredHeight() + arrowHeight;
|
|
||||||
|
|
||||||
DragLayer dragLayer = mLauncher.getDragLayer();
|
|
||||||
dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
|
|
||||||
Rect insets = dragLayer.getInsets();
|
|
||||||
|
|
||||||
// Align left (right in RTL) if there is room.
|
|
||||||
int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
|
|
||||||
int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
|
|
||||||
int x = leftAlignedX;
|
|
||||||
boolean canBeLeftAligned = leftAlignedX + width + insets.left
|
|
||||||
< dragLayer.getRight() - insets.right;
|
|
||||||
boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
|
|
||||||
if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
|
|
||||||
x = rightAlignedX;
|
|
||||||
}
|
|
||||||
mIsLeftAligned = x == leftAlignedX;
|
|
||||||
if (mIsRtl) {
|
|
||||||
x -= dragLayer.getWidth() - width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
|
|
||||||
int iconWidth = mOriginalIcon.getWidth()
|
|
||||||
- mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
|
|
||||||
iconWidth *= mOriginalIcon.getScaleX();
|
|
||||||
Resources resources = getResources();
|
|
||||||
int xOffset;
|
|
||||||
if (isAlignedWithStart()) {
|
|
||||||
// Aligning with the shortcut icon.
|
|
||||||
int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
|
|
||||||
int shortcutPaddingStart = resources.getDimensionPixelSize(
|
|
||||||
R.dimen.popup_padding_start);
|
|
||||||
xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
|
|
||||||
} else {
|
|
||||||
// Aligning with the drag handle.
|
|
||||||
int shortcutDragHandleWidth = resources.getDimensionPixelSize(
|
|
||||||
R.dimen.deep_shortcut_drag_handle_size);
|
|
||||||
int shortcutPaddingEnd = resources.getDimensionPixelSize(
|
|
||||||
R.dimen.popup_padding_end);
|
|
||||||
xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
|
|
||||||
}
|
|
||||||
x += mIsLeftAligned ? xOffset : -xOffset;
|
|
||||||
|
|
||||||
// Open above icon if there is room.
|
|
||||||
int iconHeight = getIconHeightForPopupPlacement();
|
|
||||||
int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
|
|
||||||
mIsAboveIcon = y > dragLayer.getTop() + insets.top;
|
|
||||||
if (!mIsAboveIcon) {
|
|
||||||
y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insets are added later, so subtract them now.
|
|
||||||
if (mIsRtl) {
|
|
||||||
x += insets.right;
|
|
||||||
} else {
|
|
||||||
x -= insets.left;
|
|
||||||
}
|
|
||||||
y -= insets.top;
|
|
||||||
|
|
||||||
mGravity = 0;
|
|
||||||
if (y + height > dragLayer.getBottom() - insets.bottom) {
|
|
||||||
// The container is opening off the screen, so just center it in the drag layer instead.
|
|
||||||
mGravity = Gravity.CENTER_VERTICAL;
|
|
||||||
// Put the container next to the icon, preferring the right side in ltr (left in rtl).
|
|
||||||
int rightSide = leftAlignedX + iconWidth - insets.left;
|
|
||||||
int leftSide = rightAlignedX - iconWidth - insets.left;
|
|
||||||
if (!mIsRtl) {
|
|
||||||
if (rightSide + width < dragLayer.getRight()) {
|
|
||||||
x = rightSide;
|
|
||||||
mIsLeftAligned = true;
|
|
||||||
} else {
|
|
||||||
x = leftSide;
|
|
||||||
mIsLeftAligned = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (leftSide > dragLayer.getLeft()) {
|
|
||||||
x = leftSide;
|
|
||||||
mIsLeftAligned = false;
|
|
||||||
} else {
|
|
||||||
x = rightSide;
|
|
||||||
mIsLeftAligned = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mIsAboveIcon = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
setX(x);
|
|
||||||
setY(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getIconHeightForPopupPlacement() {
|
|
||||||
return mOriginalIcon.getHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isAlignedWithStart() {
|
|
||||||
return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an arrow view pointing at the original icon.
|
|
||||||
* @param horizontalOffset the horizontal offset of the arrow, so that it
|
|
||||||
* points at the center of the original icon
|
|
||||||
*/
|
|
||||||
protected View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
|
|
||||||
LayoutParams layoutParams = new LayoutParams(width, height);
|
|
||||||
if (mIsLeftAligned) {
|
|
||||||
layoutParams.gravity = Gravity.LEFT;
|
|
||||||
layoutParams.leftMargin = horizontalOffset;
|
|
||||||
} else {
|
|
||||||
layoutParams.gravity = Gravity.RIGHT;
|
|
||||||
layoutParams.rightMargin = horizontalOffset;
|
|
||||||
}
|
|
||||||
if (mIsAboveIcon) {
|
|
||||||
layoutParams.topMargin = verticalOffset;
|
|
||||||
} else {
|
|
||||||
layoutParams.bottomMargin = verticalOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
View arrowView = new View(getContext());
|
|
||||||
if (Gravity.isVertical(mGravity)) {
|
|
||||||
// This is only true if there wasn't room for the container next to the icon,
|
|
||||||
// so we centered it instead. In that case we don't want to show the arrow.
|
|
||||||
arrowView.setVisibility(INVISIBLE);
|
|
||||||
} else {
|
|
||||||
ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
|
|
||||||
width, height, !mIsAboveIcon));
|
|
||||||
Paint arrowPaint = arrowDrawable.getPaint();
|
|
||||||
arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
|
|
||||||
// The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
|
|
||||||
int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
|
|
||||||
arrowPaint.setPathEffect(new CornerPathEffect(radius));
|
|
||||||
arrowView.setBackground(arrowDrawable);
|
|
||||||
arrowView.setElevation(getElevation());
|
|
||||||
}
|
|
||||||
addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
|
|
||||||
return arrowView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
||||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
|
||||||
mInterceptTouchDown.set(ev.getX(), ev.getY());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Stop sending touch events to deep shortcut views if user moved beyond touch slop.
|
|
||||||
return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
|
|
||||||
> ViewConfiguration.get(getContext()).getScaledTouchSlop();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ObjectAnimator createArrowScaleAnim(float scale) {
|
|
||||||
return LauncherAnimUtils.ofPropertyValuesHolder(
|
|
||||||
mArrow, new PropertyListBuilder().scale(scale).build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void handleClose(boolean animate) {
|
|
||||||
if (animate) {
|
|
||||||
animateClose();
|
|
||||||
} else {
|
|
||||||
closeComplete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void animateClose() {
|
|
||||||
if (!mIsOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mEndRect.setEmpty();
|
|
||||||
if (mOpenCloseAnimator != null) {
|
|
||||||
Outline outline = new Outline();
|
|
||||||
getOutlineProvider().getOutline(this, outline);
|
|
||||||
outline.getRect(mEndRect);
|
|
||||||
mOpenCloseAnimator.cancel();
|
|
||||||
}
|
|
||||||
mIsOpen = false;
|
|
||||||
|
|
||||||
final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
|
|
||||||
prepareCloseAnimator(closeAnim);
|
|
||||||
|
|
||||||
closeAnim.addListener(new AnimatorListenerAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
mOpenCloseAnimator = null;
|
|
||||||
if (mDeferContainerRemoval) {
|
|
||||||
setVisibility(INVISIBLE);
|
|
||||||
} else {
|
|
||||||
closeComplete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mOpenCloseAnimator = closeAnim;
|
|
||||||
closeAnim.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void prepareCloseAnimator(AnimatorSet closeAnim) {
|
|
||||||
final Resources res = getResources();
|
|
||||||
final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
|
|
||||||
|
|
||||||
// Rectangular reveal (reversed).
|
|
||||||
int itemsTotalHeight = 0;
|
|
||||||
for (int i = 0; i < getItemCount(); i++) {
|
|
||||||
itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
|
|
||||||
}
|
|
||||||
Point startPoint = computeAnimStartPoint(itemsTotalHeight);
|
|
||||||
int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
|
|
||||||
float radius = getItemViewAt(0).getBackgroundRadius();
|
|
||||||
mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
|
|
||||||
if (mEndRect.isEmpty()) {
|
|
||||||
mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
|
|
||||||
}
|
|
||||||
final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider(
|
|
||||||
radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true);
|
|
||||||
revealAnim.setInterpolator(revealInterpolator);
|
|
||||||
closeAnim.play(revealAnim);
|
|
||||||
|
|
||||||
Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
|
|
||||||
fadeOut.setInterpolator(revealInterpolator);
|
|
||||||
closeAnim.play(fadeOut);
|
|
||||||
closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the folder without animation.
|
|
||||||
*/
|
|
||||||
protected void closeComplete() {
|
|
||||||
if (mOpenCloseAnimator != null) {
|
|
||||||
mOpenCloseAnimator.cancel();
|
|
||||||
mOpenCloseAnimator = null;
|
|
||||||
}
|
|
||||||
mIsOpen = false;
|
|
||||||
mDeferContainerRemoval = false;
|
|
||||||
mLauncher.getDragLayer().removeView(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isOfType(int type) {
|
|
||||||
return (type & TYPE_ACTION_POPUP) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a DeepShortcutsContainer which is already open or null
|
|
||||||
*/
|
|
||||||
public static BaseActionPopup getOpen(Launcher launcher) {
|
|
||||||
return getOpenView(launcher, TYPE_ACTION_POPUP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logActionCommand(int command) {
|
|
||||||
mLauncher.getUserEventDispatcher().logActionCommand(
|
|
||||||
command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
|
||||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
|
||||||
DragLayer dl = mLauncher.getDragLayer();
|
|
||||||
if (!dl.isEventOverView(this, ev)) {
|
|
||||||
mLauncher.getUserEventDispatcher().logActionTapOutside(
|
|
||||||
LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
|
|
||||||
close(true);
|
|
||||||
|
|
||||||
// We let touches on the original icon go through so that users can launch
|
|
||||||
// the app with one tap if they don't find a shortcut they want.
|
|
||||||
return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void populateAndShow(V originalIcon, PopupPopulator.Item[] itemsToPopulate) {
|
|
||||||
setVisibility(View.INVISIBLE);
|
|
||||||
mLauncher.getDragLayer().addView(this);
|
|
||||||
|
|
||||||
final Resources resources = getResources();
|
|
||||||
final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
|
|
||||||
final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
|
|
||||||
final int arrowVerticalOffset = resources.getDimensionPixelSize(
|
|
||||||
R.dimen.popup_arrow_vertical_offset);
|
|
||||||
|
|
||||||
mOriginalIcon = originalIcon;
|
|
||||||
|
|
||||||
// Add dummy views first, and populate with real info when ready.
|
|
||||||
addDummyViews(itemsToPopulate);
|
|
||||||
|
|
||||||
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
|
||||||
orientAboutIcon(arrowHeight + arrowVerticalOffset);
|
|
||||||
|
|
||||||
boolean reverseOrder = mIsAboveIcon;
|
|
||||||
if (reverseOrder) {
|
|
||||||
removeAllViews();
|
|
||||||
mShortcutsItemView = null;
|
|
||||||
itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
|
|
||||||
addDummyViews(itemsToPopulate);
|
|
||||||
|
|
||||||
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
|
||||||
orientAboutIcon(arrowHeight + arrowVerticalOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the arrow.
|
|
||||||
final int arrowHorizontalOffset = resources.getDimensionPixelSize(isAlignedWithStart() ?
|
|
||||||
R.dimen.popup_arrow_horizontal_offset_start :
|
|
||||||
R.dimen.popup_arrow_horizontal_offset_end);
|
|
||||||
mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
|
|
||||||
mArrow.setPivotX(arrowWidth / 2);
|
|
||||||
mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
|
|
||||||
|
|
||||||
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
|
||||||
animateOpen();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate) {
|
|
||||||
final LayoutInflater inflater = mLauncher.getLayoutInflater();
|
|
||||||
int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
|
|
||||||
int numItems = itemTypesToPopulate.length;
|
|
||||||
for (int i = 0; i < numItems; i++) {
|
|
||||||
PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
|
|
||||||
PopupPopulator.Item prevItemTypeToPopulate =
|
|
||||||
i > 0 ? itemTypesToPopulate[i - 1] : null;
|
|
||||||
PopupPopulator.Item nextItemTypeToPopulate =
|
|
||||||
i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
|
|
||||||
final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
|
|
||||||
|
|
||||||
boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
|
|
||||||
&& itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
|
|
||||||
boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
|
|
||||||
&& itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
|
|
||||||
|
|
||||||
onViewInflated(item, itemTypeToPopulate,
|
|
||||||
shouldUnroundTopCorners, shouldUnroundBottomCorners);
|
|
||||||
|
|
||||||
if (itemTypeToPopulate.isShortcut) {
|
|
||||||
if (mShortcutsItemView == null) {
|
|
||||||
mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
|
|
||||||
R.layout.shortcuts_item, this, false);
|
|
||||||
addView(mShortcutsItemView);
|
|
||||||
if (shouldUnroundTopCorners) {
|
|
||||||
shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
|
|
||||||
if (shouldUnroundBottomCorners) {
|
|
||||||
shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addView(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
|
|
||||||
mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onViewInflated(View view, PopupPopulator.Item itemType,
|
|
||||||
boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -148,9 +148,9 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
|
private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
|
||||||
BaseActionPopup openContainer = BaseActionPopup.getOpen(mLauncher);
|
PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
|
||||||
if (openContainer instanceof PopupContainerWithArrow) {
|
if (openContainer != null) {
|
||||||
((PopupContainerWithArrow) openContainer).trimNotifications(updatedBadges);
|
openContainer.trimNotifications(updatedBadges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.launcher3.popup;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.graphics.PorterDuffXfermode;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
|
||||||
import android.graphics.drawable.shapes.RoundRectShape;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
import com.android.launcher3.R;
|
|
||||||
import com.android.launcher3.Utilities;
|
|
||||||
import com.android.launcher3.popup.BaseActionPopup.RoundedCornerFlags;
|
|
||||||
|
|
||||||
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
|
|
||||||
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract {@link FrameLayout} that contains content for {@link PopupContainerWithArrow}.
|
|
||||||
*/
|
|
||||||
public abstract class PopupItemView extends FrameLayout {
|
|
||||||
|
|
||||||
protected final Rect mPillRect;
|
|
||||||
protected @RoundedCornerFlags int mRoundedCorners;
|
|
||||||
protected final boolean mIsRtl;
|
|
||||||
protected View mIconView;
|
|
||||||
|
|
||||||
private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
|
||||||
private final Matrix mMatrix = new Matrix();
|
|
||||||
private Bitmap mRoundedCornerBitmap;
|
|
||||||
|
|
||||||
public PopupItemView(Context context) {
|
|
||||||
this(context, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PopupItemView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
|
|
||||||
mPillRect = new Rect();
|
|
||||||
|
|
||||||
// Initialize corner clipping Bitmap and Paint.
|
|
||||||
int radius = (int) getBackgroundRadius();
|
|
||||||
mRoundedCornerBitmap = Bitmap.createBitmap(radius, radius, Bitmap.Config.ALPHA_8);
|
|
||||||
Canvas canvas = new Canvas();
|
|
||||||
canvas.setBitmap(mRoundedCornerBitmap);
|
|
||||||
canvas.drawArc(0, 0, radius*2, radius*2, 180, 90, true, mBackgroundClipPaint);
|
|
||||||
mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
|
|
||||||
|
|
||||||
mIsRtl = Utilities.isRtl(getResources());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFinishInflate() {
|
|
||||||
super.onFinishInflate();
|
|
||||||
mIconView = findViewById(R.id.popup_item_icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void dispatchDraw(Canvas canvas) {
|
|
||||||
if (mRoundedCorners == 0) {
|
|
||||||
super.dispatchDraw(canvas);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
|
|
||||||
super.dispatchDraw(canvas);
|
|
||||||
|
|
||||||
// Clip children to this item's rounded corners.
|
|
||||||
int cornerWidth = mRoundedCornerBitmap.getWidth();
|
|
||||||
int cornerHeight = mRoundedCornerBitmap.getHeight();
|
|
||||||
int cornerCenterX = Math.round(cornerWidth / 2f);
|
|
||||||
int cornerCenterY = Math.round(cornerHeight / 2f);
|
|
||||||
if ((mRoundedCorners & ROUNDED_TOP_CORNERS) != 0) {
|
|
||||||
// Clip top left corner.
|
|
||||||
mMatrix.reset();
|
|
||||||
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
|
|
||||||
// Clip top right corner.
|
|
||||||
mMatrix.setRotate(90, cornerCenterX, cornerCenterY);
|
|
||||||
mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
|
|
||||||
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
|
|
||||||
}
|
|
||||||
if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) != 0) {
|
|
||||||
// Clip bottom right corner.
|
|
||||||
mMatrix.setRotate(180, cornerCenterX, cornerCenterY);
|
|
||||||
mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
|
|
||||||
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
|
|
||||||
// Clip bottom left corner.
|
|
||||||
mMatrix.setRotate(270, cornerCenterX, cornerCenterY);
|
|
||||||
mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
|
|
||||||
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.restoreToCount(saveCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a round rect drawable (with the specified corners unrounded)
|
|
||||||
* and sets it as this View's background.
|
|
||||||
*/
|
|
||||||
public void setBackgroundWithCorners(int color, @RoundedCornerFlags int roundedCorners) {
|
|
||||||
mRoundedCorners = roundedCorners;
|
|
||||||
float rTop = (roundedCorners & ROUNDED_TOP_CORNERS) == 0 ? 0 : getBackgroundRadius();
|
|
||||||
float rBot = (roundedCorners & ROUNDED_BOTTOM_CORNERS) == 0 ? 0 : getBackgroundRadius();
|
|
||||||
float[] radii = new float[] {rTop, rTop, rTop, rTop, rBot, rBot, rBot, rBot};
|
|
||||||
ShapeDrawable roundRectBackground = new ShapeDrawable(new RoundRectShape(radii, null, null));
|
|
||||||
roundRectBackground.getPaint().setColor(color);
|
|
||||||
setBackground(roundRectBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected float getBackgroundRadius() {
|
|
||||||
return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,23 +17,17 @@
|
||||||
package com.android.launcher3.popup;
|
package com.android.launcher3.popup;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import com.android.launcher3.ItemInfo;
|
import com.android.launcher3.ItemInfo;
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.R;
|
|
||||||
import com.android.launcher3.ShortcutInfo;
|
import com.android.launcher3.ShortcutInfo;
|
||||||
import com.android.launcher3.graphics.LauncherIcons;
|
import com.android.launcher3.graphics.LauncherIcons;
|
||||||
import com.android.launcher3.notification.NotificationInfo;
|
import com.android.launcher3.notification.NotificationInfo;
|
||||||
import com.android.launcher3.notification.NotificationItemView;
|
|
||||||
import com.android.launcher3.notification.NotificationKeyData;
|
import com.android.launcher3.notification.NotificationKeyData;
|
||||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
import com.android.launcher3.shortcuts.DeepShortcutView;
|
||||||
|
@ -56,55 +50,6 @@ public class PopupPopulator {
|
||||||
@VisibleForTesting static final int NUM_DYNAMIC = 2;
|
@VisibleForTesting static final int NUM_DYNAMIC = 2;
|
||||||
public static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
|
public static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
|
||||||
|
|
||||||
public enum Item {
|
|
||||||
SHORTCUT(R.layout.deep_shortcut, true),
|
|
||||||
NOTIFICATION(R.layout.notification, false),
|
|
||||||
SYSTEM_SHORTCUT(R.layout.system_shortcut, true),
|
|
||||||
SYSTEM_SHORTCUT_ICON(R.layout.system_shortcut_icon_only, true);
|
|
||||||
|
|
||||||
public final int layoutId;
|
|
||||||
public final boolean isShortcut;
|
|
||||||
|
|
||||||
Item(int layoutId, boolean isShortcut) {
|
|
||||||
this.layoutId = layoutId;
|
|
||||||
this.isShortcut = isShortcut;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NonNull Item[] getItemsToPopulate(@NonNull List<String> shortcutIds,
|
|
||||||
@NonNull List<NotificationKeyData> notificationKeys,
|
|
||||||
@NonNull List<SystemShortcut> systemShortcuts) {
|
|
||||||
boolean hasNotifications = notificationKeys.size() > 0;
|
|
||||||
int numNotificationItems = hasNotifications ? 1 : 0;
|
|
||||||
int numShortcuts = shortcutIds.size();
|
|
||||||
int numItems = Math.min(MAX_SHORTCUTS, numShortcuts) + numNotificationItems
|
|
||||||
+ systemShortcuts.size();
|
|
||||||
Item[] items = new Item[numItems];
|
|
||||||
for (int i = 0; i < numItems; i++) {
|
|
||||||
items[i] = Item.SHORTCUT;
|
|
||||||
}
|
|
||||||
if (hasNotifications) {
|
|
||||||
// The notification layout is always first.
|
|
||||||
items[0] = Item.NOTIFICATION;
|
|
||||||
}
|
|
||||||
// The system shortcuts are always last.
|
|
||||||
boolean iconsOnly = !shortcutIds.isEmpty();
|
|
||||||
for (int i = 0; i < systemShortcuts.size(); i++) {
|
|
||||||
items[numItems - 1 - i] = iconsOnly ? Item.SYSTEM_SHORTCUT_ICON : Item.SYSTEM_SHORTCUT;
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Item[] reverseItems(Item[] items) {
|
|
||||||
if (items == null) return null;
|
|
||||||
int numItems = items.length;
|
|
||||||
Item[] reversedArray = new Item[numItems];
|
|
||||||
for (int i = 0; i < numItems; i++) {
|
|
||||||
reversedArray[i] = items[numItems - i - 1];
|
|
||||||
}
|
|
||||||
return reversedArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
|
* Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
|
||||||
*/
|
*/
|
||||||
|
@ -179,15 +124,11 @@ public class PopupPopulator {
|
||||||
public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
|
public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
|
||||||
final Handler uiHandler, final PopupContainerWithArrow container,
|
final Handler uiHandler, final PopupContainerWithArrow container,
|
||||||
final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
|
final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
|
||||||
final List<NotificationKeyData> notificationKeys,
|
final List<NotificationKeyData> notificationKeys) {
|
||||||
final NotificationItemView notificationView, final List<SystemShortcut> systemShortcuts,
|
|
||||||
final List<View> systemShortcutViews) {
|
|
||||||
final ComponentName activity = originalInfo.getTargetComponent();
|
final ComponentName activity = originalInfo.getTargetComponent();
|
||||||
final UserHandle user = originalInfo.user;
|
final UserHandle user = originalInfo.user;
|
||||||
return new Runnable() {
|
return () -> {
|
||||||
@Override
|
if (!notificationKeys.isEmpty()) {
|
||||||
public void run() {
|
|
||||||
if (notificationView != null) {
|
|
||||||
List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
|
List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
|
||||||
.getStatusBarNotificationsForKeys(notificationKeys);
|
.getStatusBarNotificationsForKeys(notificationKeys);
|
||||||
List<NotificationInfo> infos = new ArrayList<>(notifications.size());
|
List<NotificationInfo> infos = new ArrayList<>(notifications.size());
|
||||||
|
@ -195,7 +136,7 @@ public class PopupPopulator {
|
||||||
StatusBarNotification notification = notifications.get(i);
|
StatusBarNotification notification = notifications.get(i);
|
||||||
infos.add(new NotificationInfo(launcher, notification));
|
infos.add(new NotificationInfo(launcher, notification));
|
||||||
}
|
}
|
||||||
uiHandler.post(new UpdateNotificationChild(notificationView, infos));
|
uiHandler.post(() -> container.applyNotificationInfos(infos));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
|
List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
|
||||||
|
@ -205,111 +146,20 @@ public class PopupPopulator {
|
||||||
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
|
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
|
||||||
for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
|
for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
|
||||||
final ShortcutInfoCompat shortcut = shortcuts.get(i);
|
final ShortcutInfoCompat shortcut = shortcuts.get(i);
|
||||||
ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
|
final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
|
||||||
// Use unbadged icon for the menu.
|
// Use unbadged icon for the menu.
|
||||||
si.iconBitmap = LauncherIcons.createShortcutIcon(
|
si.iconBitmap = LauncherIcons.createShortcutIcon(
|
||||||
shortcut, launcher, false /* badged */);
|
shortcut, launcher, false /* badged */);
|
||||||
si.rank = i;
|
si.rank = i;
|
||||||
uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
|
|
||||||
si, shortcut));
|
final DeepShortcutView view = shortcutViews.get(i);
|
||||||
|
uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This ensures that mLauncher.getWidgetsForPackageUser()
|
// This ensures that mLauncher.getWidgetsForPackageUser()
|
||||||
// doesn't return null (it puts all the widgets in memory).
|
// doesn't return null (it puts all the widgets in memory).
|
||||||
for (int i = 0; i < systemShortcuts.size(); i++) {
|
uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
|
||||||
final SystemShortcut systemShortcut = systemShortcuts.get(i);
|
PackageUserKey.fromItemInfo(originalInfo)));
|
||||||
uiHandler.post(new UpdateSystemShortcutChild(container,
|
|
||||||
systemShortcutViews.get(i), systemShortcut, launcher, originalInfo));
|
|
||||||
}
|
|
||||||
uiHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
launcher.refreshAndBindWidgetsForPackageUser(
|
|
||||||
PackageUserKey.fromItemInfo(originalInfo));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Updates the shortcut child of this container based on the given shortcut info. */
|
|
||||||
private static class UpdateShortcutChild implements Runnable {
|
|
||||||
private final PopupContainerWithArrow mContainer;
|
|
||||||
private final DeepShortcutView mShortcutChild;
|
|
||||||
private final ShortcutInfo mShortcutChildInfo;
|
|
||||||
private final ShortcutInfoCompat mDetail;
|
|
||||||
|
|
||||||
public UpdateShortcutChild(PopupContainerWithArrow container, DeepShortcutView shortcutChild,
|
|
||||||
ShortcutInfo shortcutChildInfo, ShortcutInfoCompat detail) {
|
|
||||||
mContainer = container;
|
|
||||||
mShortcutChild = shortcutChild;
|
|
||||||
mShortcutChildInfo = shortcutChildInfo;
|
|
||||||
mDetail = detail;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
|
|
||||||
mContainer.mShortcutsItemView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Updates the notification child based on the given notification info. */
|
|
||||||
private static class UpdateNotificationChild implements Runnable {
|
|
||||||
private NotificationItemView mNotificationView;
|
|
||||||
private List<NotificationInfo> mNotificationInfos;
|
|
||||||
|
|
||||||
public UpdateNotificationChild(NotificationItemView notificationView,
|
|
||||||
List<NotificationInfo> notificationInfos) {
|
|
||||||
mNotificationView = notificationView;
|
|
||||||
mNotificationInfos = notificationInfos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mNotificationView.applyNotificationInfos(mNotificationInfos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Updates the system shortcut child based on the given shortcut info. */
|
|
||||||
private static class UpdateSystemShortcutChild implements Runnable {
|
|
||||||
|
|
||||||
private final PopupContainerWithArrow mContainer;
|
|
||||||
private final View mSystemShortcutChild;
|
|
||||||
private final SystemShortcut mSystemShortcutInfo;
|
|
||||||
private final Launcher mLauncher;
|
|
||||||
private final ItemInfo mItemInfo;
|
|
||||||
|
|
||||||
public UpdateSystemShortcutChild(PopupContainerWithArrow container, View systemShortcutChild,
|
|
||||||
SystemShortcut systemShortcut, Launcher launcher, ItemInfo originalInfo) {
|
|
||||||
mContainer = container;
|
|
||||||
mSystemShortcutChild = systemShortcutChild;
|
|
||||||
mSystemShortcutInfo = systemShortcut;
|
|
||||||
mLauncher = launcher;
|
|
||||||
mItemInfo = originalInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
final Context context = mSystemShortcutChild.getContext();
|
|
||||||
initializeSystemShortcut(context, mSystemShortcutChild, mSystemShortcutInfo);
|
|
||||||
mSystemShortcutChild.setOnClickListener(mSystemShortcutInfo
|
|
||||||
.getOnClickListener(mLauncher, mItemInfo));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initializeSystemShortcut(Context context, View view, SystemShortcut info) {
|
|
||||||
if (view instanceof DeepShortcutView) {
|
|
||||||
// Expanded system shortcut, with both icon and text shown on white background.
|
|
||||||
final DeepShortcutView shortcutView = (DeepShortcutView) view;
|
|
||||||
shortcutView.getIconView().setBackground(info.getIcon(context));
|
|
||||||
shortcutView.getBubbleText().setText(info.getLabel(context));
|
|
||||||
} else if (view instanceof ImageView) {
|
|
||||||
// Only the system shortcut icon shows on a gray background header.
|
|
||||||
final ImageView shortcutIcon = (ImageView) view;
|
|
||||||
shortcutIcon.setImageDrawable(info.getIcon(context));
|
|
||||||
shortcutIcon.setContentDescription(info.getLabel(context));
|
|
||||||
}
|
|
||||||
view.setTag(info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package com.android.launcher3.popup;
|
package com.android.launcher3.popup;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
@ -31,20 +29,12 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
|
||||||
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
|
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
|
||||||
*/
|
*/
|
||||||
public abstract class SystemShortcut extends ItemInfo {
|
public abstract class SystemShortcut extends ItemInfo {
|
||||||
private final int mIconResId;
|
public final int iconResId;
|
||||||
private final int mLabelResId;
|
public final int labelResId;
|
||||||
|
|
||||||
public SystemShortcut(int iconResId, int labelResId) {
|
public SystemShortcut(int iconResId, int labelResId) {
|
||||||
mIconResId = iconResId;
|
this.iconResId = iconResId;
|
||||||
mLabelResId = labelResId;
|
this.labelResId = labelResId;
|
||||||
}
|
|
||||||
|
|
||||||
public Drawable getIcon(Context context) {
|
|
||||||
return context.getResources().getDrawable(mIconResId, context.getTheme());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLabel(Context context) {
|
|
||||||
return context.getString(mLabelResId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
|
public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
|
||||||
|
|
|
@ -29,6 +29,7 @@ import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.ShortcutInfo;
|
import com.android.launcher3.ShortcutInfo;
|
||||||
import com.android.launcher3.Utilities;
|
import com.android.launcher3.Utilities;
|
||||||
|
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
|
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
|
||||||
|
@ -42,6 +43,7 @@ public class DeepShortcutView extends FrameLayout {
|
||||||
|
|
||||||
private BubbleTextView mBubbleText;
|
private BubbleTextView mBubbleText;
|
||||||
private View mIconView;
|
private View mIconView;
|
||||||
|
private View mDivider;
|
||||||
|
|
||||||
private ShortcutInfo mInfo;
|
private ShortcutInfo mInfo;
|
||||||
private ShortcutInfoCompat mDetail;
|
private ShortcutInfoCompat mDetail;
|
||||||
|
@ -65,6 +67,11 @@ public class DeepShortcutView extends FrameLayout {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
mBubbleText = findViewById(R.id.bubble_text);
|
mBubbleText = findViewById(R.id.bubble_text);
|
||||||
mIconView = findViewById(R.id.icon);
|
mIconView = findViewById(R.id.icon);
|
||||||
|
mDivider = findViewById(R.id.divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDividerVisibility(int visibility) {
|
||||||
|
mDivider.setVisibility(visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BubbleTextView getBubbleText() {
|
public BubbleTextView getBubbleText() {
|
||||||
|
@ -98,7 +105,7 @@ public class DeepShortcutView extends FrameLayout {
|
||||||
|
|
||||||
/** package private **/
|
/** package private **/
|
||||||
public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
|
public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
|
||||||
ShortcutsItemView container) {
|
PopupContainerWithArrow container) {
|
||||||
mInfo = info;
|
mInfo = info;
|
||||||
mDetail = detail;
|
mDetail = detail;
|
||||||
mBubbleText.applyFromShortcutInfo(info);
|
mBubbleText.applyFromShortcutInfo(info);
|
||||||
|
|
|
@ -1,340 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.launcher3.shortcuts;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
|
|
||||||
import com.android.launcher3.AbstractFloatingView;
|
|
||||||
import com.android.launcher3.BubbleTextView;
|
|
||||||
import com.android.launcher3.ItemInfo;
|
|
||||||
import com.android.launcher3.Launcher;
|
|
||||||
import com.android.launcher3.LauncherAnimUtils;
|
|
||||||
import com.android.launcher3.R;
|
|
||||||
import com.android.launcher3.anim.PropertyListBuilder;
|
|
||||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
|
||||||
import com.android.launcher3.dragndrop.DragOptions;
|
|
||||||
import com.android.launcher3.dragndrop.DragView;
|
|
||||||
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
|
|
||||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
|
||||||
import com.android.launcher3.popup.PopupItemView;
|
|
||||||
import com.android.launcher3.popup.PopupPopulator;
|
|
||||||
import com.android.launcher3.popup.SystemShortcut;
|
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
|
|
||||||
* as well as the system shortcuts such as Widgets and App Info.
|
|
||||||
*/
|
|
||||||
public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
|
|
||||||
View.OnTouchListener, LogContainerProvider {
|
|
||||||
|
|
||||||
private static final String TAG = "ShortcutsItem";
|
|
||||||
|
|
||||||
private Launcher mLauncher;
|
|
||||||
private LinearLayout mContent;
|
|
||||||
private LinearLayout mShortcutsLayout;
|
|
||||||
private LinearLayout mSystemShortcutIcons;
|
|
||||||
private final Point mIconShift = new Point();
|
|
||||||
private final Point mIconLastTouchPos = new Point();
|
|
||||||
private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
|
|
||||||
private final List<View> mSystemShortcutViews = new ArrayList<>();
|
|
||||||
|
|
||||||
private int mHiddenShortcutsHeight;
|
|
||||||
|
|
||||||
public ShortcutsItemView(Context context) {
|
|
||||||
this(context, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShortcutsItemView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
|
|
||||||
mLauncher = Launcher.getLauncher(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFinishInflate() {
|
|
||||||
super.onFinishInflate();
|
|
||||||
mContent = findViewById(R.id.content);
|
|
||||||
mShortcutsLayout = findViewById(R.id.shortcuts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View v, MotionEvent ev) {
|
|
||||||
// Touched a shortcut, update where it was touched so we can drag from there on long click.
|
|
||||||
switch (ev.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onLongClick(View v) {
|
|
||||||
// Return early if not the correct view
|
|
||||||
if (!(v.getParent() instanceof DeepShortcutView)) return false;
|
|
||||||
// Return early if global dragging is not enabled
|
|
||||||
if (!mLauncher.isDraggingEnabled()) return false;
|
|
||||||
// Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
|
|
||||||
if (mLauncher.getDragController().isDragging()) return false;
|
|
||||||
|
|
||||||
// Long clicked on a shortcut.
|
|
||||||
DeepShortcutView sv = (DeepShortcutView) v.getParent();
|
|
||||||
sv.setWillDrawIcon(false);
|
|
||||||
|
|
||||||
// Move the icon to align with the center-top of the touch point
|
|
||||||
mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
|
|
||||||
mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
|
|
||||||
|
|
||||||
DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
|
|
||||||
(PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
|
|
||||||
new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
|
|
||||||
dv.animateShift(-mIconShift.x, -mIconShift.y);
|
|
||||||
|
|
||||||
// TODO: support dragging from within folder without having to close it
|
|
||||||
AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType) {
|
|
||||||
addShortcutView(shortcutView, shortcutType, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType, int index) {
|
|
||||||
if (shortcutType == PopupPopulator.Item.SHORTCUT) {
|
|
||||||
mDeepShortcutViews.add((DeepShortcutView) shortcutView);
|
|
||||||
} else {
|
|
||||||
mSystemShortcutViews.add(shortcutView);
|
|
||||||
}
|
|
||||||
if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
|
|
||||||
// System shortcut icons are added to a header that is separate from the full shortcuts.
|
|
||||||
if (mSystemShortcutIcons == null) {
|
|
||||||
mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
|
|
||||||
R.layout.system_shortcut_icons, mContent, false);
|
|
||||||
boolean iconsAreBelowShortcuts = mShortcutsLayout.getChildCount() > 0;
|
|
||||||
mContent.addView(mSystemShortcutIcons, iconsAreBelowShortcuts ? -1 : 0);
|
|
||||||
}
|
|
||||||
mSystemShortcutIcons.addView(shortcutView, index);
|
|
||||||
} else {
|
|
||||||
if (mShortcutsLayout.getChildCount() > 0) {
|
|
||||||
View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
|
|
||||||
if (prevChild instanceof DeepShortcutView) {
|
|
||||||
prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mShortcutsLayout.addView(shortcutView, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
|
|
||||||
if (reverseOrder) {
|
|
||||||
Collections.reverse(mDeepShortcutViews);
|
|
||||||
}
|
|
||||||
return mDeepShortcutViews;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<View> getSystemShortcutViews(boolean reverseOrder) {
|
|
||||||
// Always reverse system shortcut icons (in the header)
|
|
||||||
// so they are in priority order from right to left.
|
|
||||||
if (reverseOrder || mSystemShortcutIcons != null) {
|
|
||||||
Collections.reverse(mSystemShortcutViews);
|
|
||||||
}
|
|
||||||
return mSystemShortcutViews;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides shortcuts until only {@param maxShortcuts} are showing. Also sets
|
|
||||||
* {@link #mHiddenShortcutsHeight} to be the amount of extra space that shortcuts will
|
|
||||||
* require when {@link #showAllShortcuts(boolean)} is called.
|
|
||||||
*/
|
|
||||||
public void hideShortcuts(boolean hideFromTop, int maxShortcuts) {
|
|
||||||
// When shortcuts are shown, they get more space allocated to them.
|
|
||||||
final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
|
|
||||||
final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
|
|
||||||
mHiddenShortcutsHeight = (newHeight - oldHeight) * mShortcutsLayout.getChildCount();
|
|
||||||
|
|
||||||
int numToHide = mShortcutsLayout.getChildCount() - maxShortcuts;
|
|
||||||
if (numToHide <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final int numShortcuts = mShortcutsLayout.getChildCount();
|
|
||||||
final int dir = hideFromTop ? 1 : -1;
|
|
||||||
for (int i = hideFromTop ? 0 : numShortcuts - 1; 0 <= i && i < numShortcuts; i += dir) {
|
|
||||||
View child = mShortcutsLayout.getChildAt(i);
|
|
||||||
if (child instanceof DeepShortcutView) {
|
|
||||||
mHiddenShortcutsHeight += child.getLayoutParams().height;
|
|
||||||
child.setVisibility(GONE);
|
|
||||||
int prev = i + dir;
|
|
||||||
if (!hideFromTop && 0 <= prev && prev < numShortcuts) {
|
|
||||||
// When hiding views from the bottom, make sure to hide the last divider.
|
|
||||||
mShortcutsLayout.getChildAt(prev).findViewById(R.id.divider).setVisibility(GONE);
|
|
||||||
}
|
|
||||||
numToHide--;
|
|
||||||
if (numToHide == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHiddenShortcutsHeight() {
|
|
||||||
return mHiddenShortcutsHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets all shortcuts in {@link #mShortcutsLayout} to VISIBLE, then creates an
|
|
||||||
* animation to reveal the newly shown shortcuts.
|
|
||||||
*
|
|
||||||
* @see #hideShortcuts(boolean, int)
|
|
||||||
*/
|
|
||||||
public Animator showAllShortcuts(boolean showFromTop) {
|
|
||||||
// First set all the shortcuts to VISIBLE.
|
|
||||||
final int numShortcuts = mShortcutsLayout.getChildCount();
|
|
||||||
if (numShortcuts == 0) {
|
|
||||||
Log.w(TAG, "Tried to show all shortcuts but there were no shortcuts to show");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
|
|
||||||
final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
|
|
||||||
for (int i = 0; i < numShortcuts; i++) {
|
|
||||||
DeepShortcutView view = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
|
|
||||||
view.getLayoutParams().height = newHeight;
|
|
||||||
view.requestLayout();
|
|
||||||
view.setVisibility(VISIBLE);
|
|
||||||
if (i < numShortcuts - 1) {
|
|
||||||
view.findViewById(R.id.divider).setVisibility(VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now reveal the newly shown shortcuts.
|
|
||||||
AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
|
|
||||||
|
|
||||||
if (showFromTop) {
|
|
||||||
// The new shortcuts pushed the original shortcuts down, but we want to animate them
|
|
||||||
// to that position. So we revert the translation and animate to the new.
|
|
||||||
animation.play(translateYFrom(mShortcutsLayout, -mHiddenShortcutsHeight));
|
|
||||||
} else if (mSystemShortcutIcons != null) {
|
|
||||||
// When adding the shortcuts from the bottom, things are a little trickier, since
|
|
||||||
// that means they push the icons header down. To account for this, we do the same
|
|
||||||
// translation trick as above, but on the header. Since this means leaving behind
|
|
||||||
// a blank area where the header was, we also need to clip the background.
|
|
||||||
animation.play(translateYFrom(mSystemShortcutIcons, -mHiddenShortcutsHeight));
|
|
||||||
// mPillRect is the bounds of this view before the new shortcuts were shown.
|
|
||||||
Rect backgroundStartRect = new Rect(mPillRect);
|
|
||||||
Rect backgroundEndRect = new Rect(mPillRect);
|
|
||||||
backgroundEndRect.bottom += mHiddenShortcutsHeight;
|
|
||||||
animation.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(),
|
|
||||||
getBackgroundRadius(), backgroundStartRect, backgroundEndRect, mRoundedCorners)
|
|
||||||
.createRevealAnimator(this, false));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < numShortcuts; i++) {
|
|
||||||
// Animate each shortcut to its new height.
|
|
||||||
DeepShortcutView shortcut = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
|
|
||||||
int heightDiff = newHeight - oldHeight;
|
|
||||||
int heightAdjustmentIndex = showFromTop ? numShortcuts - i - 1 : i;
|
|
||||||
int fromDir = showFromTop ? 1 : -1;
|
|
||||||
animation.play(translateYFrom(shortcut, heightDiff * heightAdjustmentIndex * fromDir));
|
|
||||||
// Make sure the text and icon stay centered in the shortcut.
|
|
||||||
animation.play(translateYFrom(shortcut.getBubbleText(), heightDiff / 2 * fromDir));
|
|
||||||
animation.play(translateYFrom(shortcut.getIconView(), heightDiff / 2 * fromDir));
|
|
||||||
// Scale icons back up to full size.
|
|
||||||
animation.play(LauncherAnimUtils.ofPropertyValuesHolder(shortcut.getIconView(),
|
|
||||||
new PropertyListBuilder().scale(1f).build()));
|
|
||||||
}
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Animates the translationY of the view from the given offset to the view's current translation
|
|
||||||
* @return an Animator, which should be started by the caller.
|
|
||||||
*/
|
|
||||||
private Animator translateYFrom(View v, int diff) {
|
|
||||||
float finalY = v.getTranslationY();
|
|
||||||
return ObjectAnimator.ofFloat(v, TRANSLATION_Y, finalY + diff, finalY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link SystemShortcut.Widgets} item if there are widgets for the given ItemInfo.
|
|
||||||
*/
|
|
||||||
public void enableWidgetsIfExist(final BubbleTextView originalIcon) {
|
|
||||||
ItemInfo itemInfo = (ItemInfo) originalIcon.getTag();
|
|
||||||
SystemShortcut widgetInfo = new SystemShortcut.Widgets();
|
|
||||||
View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
|
|
||||||
View widgetsView = null;
|
|
||||||
for (View systemShortcutView : mSystemShortcutViews) {
|
|
||||||
if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
|
|
||||||
widgetsView = systemShortcutView;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final PopupPopulator.Item widgetsItem = mSystemShortcutIcons == null
|
|
||||||
? PopupPopulator.Item.SYSTEM_SHORTCUT
|
|
||||||
: PopupPopulator.Item.SYSTEM_SHORTCUT_ICON;
|
|
||||||
if (onClickListener != null && widgetsView == null) {
|
|
||||||
// We didn't have any widgets cached but now there are some, so enable the shortcut.
|
|
||||||
widgetsView = mLauncher.getLayoutInflater().inflate(widgetsItem.layoutId, this, false);
|
|
||||||
PopupPopulator.initializeSystemShortcut(getContext(), widgetsView, widgetInfo);
|
|
||||||
widgetsView.setOnClickListener(onClickListener);
|
|
||||||
if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
|
|
||||||
addShortcutView(widgetsView, widgetsItem, 0);
|
|
||||||
} else {
|
|
||||||
// If using the expanded system shortcut (as opposed to just the icon), we need to
|
|
||||||
// reopen the container to ensure measurements etc. all work out. While this could
|
|
||||||
// be quite janky, in practice the user would typically see a small flicker as the
|
|
||||||
// animation restarts partway through, and this is a very rare edge case anyway.
|
|
||||||
((PopupContainerWithArrow) getParent()).close(false);
|
|
||||||
PopupContainerWithArrow.showForIcon(originalIcon);
|
|
||||||
}
|
|
||||||
} else if (onClickListener == null && widgetsView != null) {
|
|
||||||
// No widgets exist, but we previously added the shortcut so remove it.
|
|
||||||
if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
|
|
||||||
mSystemShortcutViews.remove(widgetsView);
|
|
||||||
mSystemShortcutIcons.removeView(widgetsView);
|
|
||||||
} else {
|
|
||||||
((PopupContainerWithArrow) getParent()).close(false);
|
|
||||||
PopupContainerWithArrow.showForIcon(originalIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
|
|
||||||
LauncherLogProto.Target targetParent) {
|
|
||||||
target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
|
|
||||||
target.rank = info.rank;
|
|
||||||
targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue