Add keboard focus outline for Chooser targets
This change applies the same focus outline as Launcher but the end
result still needs more polishing. Some noticeable issues are:
* the outline may overlap with a long label;
* targets with a one-line labels look adjusted to the top and not
centered;
* with the light system ui theme, the outer online frame is barely
visible compare to the inner outline (this is also true for Launcher).
Bug: 295175912
Test: visual effect testing
Flag: com.android.intentresolver.target_hover_and_keyboard_focus_states
Change-Id: I1d22b187e0cc4b95c385d4f5b956effa31fd4505
diff --git a/java/res/layout/chooser_grid_item_hover.xml b/java/res/layout/chooser_grid_item_hover.xml
index f4396ec..0520606 100644
--- a/java/res/layout/chooser_grid_item_hover.xml
+++ b/java/res/layout/chooser_grid_item_hover.xml
@@ -19,6 +19,7 @@
<com.android.intentresolver.widget.ChooserTargetItemView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@androidprv:id/item"
android:orientation="vertical"
android:layout_width="match_parent"
@@ -28,7 +29,11 @@
android:paddingVertical="1dp"
android:paddingHorizontal="4dp"
android:focusable="true"
- android:background="?android:attr/selectableItemBackgroundBorderless">
+ android:defaultFocusHighlightEnabled="false"
+ app:focusOutlineWidth="@dimen/chooser_item_focus_outline_width"
+ app:focusOutlineCornerRadius="@dimen/chooser_item_focus_outline_corner_radius"
+ app:focusOutlineColor="?androidprv:attr/materialColorSecondaryFixed"
+ app:focusInnerOutlineColor="?androidprv:attr/materialColorOnSecondaryFixedVariant">
<ImageView android:id="@android:id/icon"
android:layout_width="@dimen/chooser_icon_width_with_padding"
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index 240ee06..e152ba0 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -20,4 +20,5 @@
<resources>
<dimen name="chooser_width">624dp</dimen>
<dimen name="modify_share_text_toggle_max_width">250dp</dimen>
+ <dimen name="chooser_item_focus_outline_corner_radius">16dp</dimen>
</resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index c9f2c30..8c3ff7e 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -56,4 +56,11 @@
<attr name="itemOuterSpacing" format="dimension" />
<attr name="maxWidthHint" format="dimension" />
</declare-styleable>
+
+ <declare-styleable name="ChooserTargetItemView">
+ <attr name="focusOutlineColor" format="color" />
+ <attr name="focusInnerOutlineColor" format="color" />
+ <attr name="focusOutlineWidth" format="dimension" />
+ <attr name="focusOutlineCornerRadius" format="dimension" />
+ </declare-styleable>
</resources>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index f85ad06..515343b 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -41,6 +41,8 @@
<dimen name="chooser_headline_text_size">18sp</dimen>
<dimen name="chooser_grid_target_name_text_size">12sp</dimen>
<dimen name="chooser_grid_activity_name_text_size">12sp</dimen>
+ <dimen name="chooser_item_focus_outline_corner_radius">11dp</dimen>
+ <dimen name="chooser_item_focus_outline_width">2dp</dimen>
<dimen name="resolver_icon_size">32dp</dimen>
<dimen name="resolver_button_bar_spacing">0dp</dimen>
<dimen name="resolver_badge_size">18dp</dimen>
diff --git a/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt b/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
index 2893449..b5a4d61 100644
--- a/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
+++ b/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
@@ -17,11 +17,16 @@
package com.android.intentresolver.widget
import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
import android.util.AttributeSet
+import android.util.TypedValue
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
+import com.android.intentresolver.R
class ChooserTargetItemView(
context: Context,
@@ -29,6 +34,14 @@
defStyleAttr: Int,
defStyleRes: Int,
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+ private val outlineRadius: Float
+ private val outlineWidth: Float
+ private val outlinePaint: Paint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
+ private val outlineInnerPaint: Paint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
+ private var iconView: ImageView? = null
+
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
@@ -39,7 +52,28 @@
defStyleAttr: Int,
) : this(context, attrs, defStyleAttr, 0)
- private var iconView: ImageView? = null
+ init {
+ val a = context.obtainStyledAttributes(attrs, R.styleable.ChooserTargetItemView)
+ val defaultWidth =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ 2f,
+ context.resources.displayMetrics,
+ )
+ outlineRadius =
+ a.getDimension(R.styleable.ChooserTargetItemView_focusOutlineCornerRadius, 0f)
+ outlineWidth =
+ a.getDimension(R.styleable.ChooserTargetItemView_focusOutlineWidth, defaultWidth)
+
+ outlinePaint.strokeWidth = outlineWidth
+ outlinePaint.color =
+ a.getColor(R.styleable.ChooserTargetItemView_focusOutlineColor, Color.TRANSPARENT)
+
+ outlineInnerPaint.strokeWidth = outlineWidth
+ outlineInnerPaint.color =
+ a.getColor(R.styleable.ChooserTargetItemView_focusInnerOutlineColor, Color.TRANSPARENT)
+ a.recycle()
+ }
override fun onViewAdded(child: View) {
super.onViewAdded(child)
@@ -70,4 +104,38 @@
}
override fun onInterceptHoverEvent(event: MotionEvent?) = true
+
+ override fun dispatchDraw(canvas: Canvas) {
+ super.dispatchDraw(canvas)
+ if (isFocused) {
+ drawFocusInnerOutline(canvas)
+ drawFocusOutline(canvas)
+ }
+ }
+
+ private fun drawFocusInnerOutline(canvas: Canvas) {
+ val outlineOffset = outlineWidth + outlineWidth / 2
+ canvas.drawRoundRect(
+ outlineOffset,
+ outlineOffset,
+ maxOf(0f, width - outlineOffset),
+ maxOf(0f, height - outlineOffset),
+ outlineRadius - outlineWidth,
+ outlineRadius - outlineWidth,
+ outlineInnerPaint,
+ )
+ }
+
+ private fun drawFocusOutline(canvas: Canvas) {
+ val outlineOffset = outlineWidth / 2
+ canvas.drawRoundRect(
+ outlineOffset,
+ outlineOffset,
+ maxOf(0f, width - outlineOffset),
+ maxOf(0f, height - outlineOffset),
+ outlineRadius,
+ outlineRadius,
+ outlinePaint,
+ )
+ }
}