aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Huang <yabinh@google.com>2021-04-16 01:37:50 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-04-16 01:37:50 +0000
commitcafc7b48eec2861df62702c49096d1dcaba1dba1 (patch)
tree49889043e37b5badcd2ba8f388be671fa15fc980
parent20022aae9a24b50c391daf0cda2c7f6516d6c329 (diff)
parentc1eaa136602fd93106c38912a79b5279a831741e (diff)
downloadtests-cafc7b48eec2861df62702c49096d1dcaba1dba1.tar.gz
Demo updating a view without losing focus am: c1eaa13660
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Car/tests/+/14187708 Change-Id: Ib837a97e683b24cfea16d23583faacfcbf9f8ee3
-rw-r--r--RotaryPlayground/res/drawable/button_background.xml21
-rw-r--r--RotaryPlayground/res/drawable/custom_button_background.xml22
-rw-r--r--RotaryPlayground/res/drawable/ic_play_arrow.xml30
-rw-r--r--RotaryPlayground/res/drawable/ic_play_arrow_off.xml35
-rw-r--r--RotaryPlayground/res/drawable/ic_stop.xml28
-rw-r--r--RotaryPlayground/res/layout/rotary_cards.xml85
-rw-r--r--RotaryPlayground/res/values/attrs.xml21
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/CustomButton.java65
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/RotaryCards.java61
9 files changed, 353 insertions, 15 deletions
diff --git a/RotaryPlayground/res/drawable/button_background.xml b/RotaryPlayground/res/drawable/button_background.xml
new file mode 100644
index 0000000..25566c5
--- /dev/null
+++ b/RotaryPlayground/res/drawable/button_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:drawable="@drawable/ic_play_arrow_off" />
+ <item android:drawable="@drawable/ic_play_arrow" />
+</selector>
diff --git a/RotaryPlayground/res/drawable/custom_button_background.xml b/RotaryPlayground/res/drawable/custom_button_background.xml
new file mode 100644
index 0000000..d4716fa
--- /dev/null
+++ b/RotaryPlayground/res/drawable/custom_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item app:state_rotary_enabled="true" android:drawable="@drawable/ic_play_arrow" />
+ <item android:drawable="@drawable/ic_play_arrow_off" />
+</selector>
diff --git a/RotaryPlayground/res/drawable/ic_play_arrow.xml b/RotaryPlayground/res/drawable/ic_play_arrow.xml
new file mode 100644
index 0000000..5c0c252
--- /dev/null
+++ b/RotaryPlayground/res/drawable/ic_play_arrow.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021, 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="56dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+
+ <path
+ android:pathData="M-838-2232H562v3600H-838z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M16 10v28l22-14z" />
+ <path
+ android:pathData="M0 0h48v48H0z" />
+</vector>
diff --git a/RotaryPlayground/res/drawable/ic_play_arrow_off.xml b/RotaryPlayground/res/drawable/ic_play_arrow_off.xml
new file mode 100644
index 0000000..8ad935f
--- /dev/null
+++ b/RotaryPlayground/res/drawable/ic_play_arrow_off.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021, 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="56dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <group
+ android:translateX="3.000000"
+ android:translateY="3.000000">
+ <path
+ android:fillColor="#000000"
+ android:strokeWidth="1"
+ android:pathData="M16,9 L7.249,3.431 L14.048,10.242 L16,9 Z" />
+ <path
+ android:fillColor="#000000"
+ android:strokeWidth="1"
+ android:pathData="M18,16.73 L1.27,0 L0,1.27 L5,6.27 L5,16 L10.946,12.216 L16.73,18 L18,16.73 Z" />
+ </group>
+</vector>
diff --git a/RotaryPlayground/res/drawable/ic_stop.xml b/RotaryPlayground/res/drawable/ic_stop.xml
new file mode 100644
index 0000000..fc42b32
--- /dev/null
+++ b/RotaryPlayground/res/drawable/ic_stop.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021, 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="56dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+
+ <path
+ android:pathData="M0 0h48v48H0z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M12 12h24v24H12z" />
+</vector>
diff --git a/RotaryPlayground/res/layout/rotary_cards.xml b/RotaryPlayground/res/layout/rotary_cards.xml
index e3d9e87..2ffdfdc 100644
--- a/RotaryPlayground/res/layout/rotary_cards.xml
+++ b/RotaryPlayground/res/layout/rotary_cards.xml
@@ -149,7 +149,8 @@
android:layout_width="@dimen/card_width"
android:layout_height="match_parent"
android:padding="@dimen/card_padding"
- android:orientation="vertical">
+ android:orientation="vertical"
+ app:defaultFocus="@+id/default_focus">
<TextView
android:layout_height="@dimen/description_height"
android:layout_width="match_parent"
@@ -160,8 +161,8 @@
android:onClick="onRotaryButtonClick"
android:tag="test_button"
android:text="Button" />
- <!-- TODO(b/154180719): Make this button the default focus in this FocusArea -->
<Button
+ android:id="@+id/default_focus"
android:layout_width="match_parent"
android:layout_height="50dp"
android:onClick="onRotaryButtonClick"
@@ -181,18 +182,9 @@
android:text="Button" />
</com.android.car.ui.FocusArea>
- <!-- A FocusArea with buttons in a circle. The default focus should land on A.
+ <!-- A FocusArea with buttons in a circle.
Rotating clockwise moves the focus from A -> B -> C -> D -> E -> F -> G -> H,
- and reverse counterclockwise.
- Adding app:defaultFocus to A to make it the default focus on this card
- Adding android:nextFocusForward is necessary to ensure the expected focus
- order, without it, the focus will move from
- G -> H -> F -> A -> E -> B -> D -> C.
- Lastly, android:nextFocusForward is not added to H -> A, to avoid linking
- the nodes in a circle. app:wrapAround="true" should be used instead.
- -->
- <!-- TODO(agathaman): add app:wrapAround to this card when b/155698037 is fixed -->
- <!-- TODO(agathaman): add app:defaultFocus to this card when b/155698037 is fixed -->
+ and reverse counterclockwise. -->
<com.android.car.ui.FocusArea
android:id="@+id/card_that_wraps_around"
android:background="@color/card_background_color"
@@ -200,7 +192,8 @@
android:layout_width="@dimen/card_width"
android:layout_height="match_parent"
android:padding="@dimen/card_padding"
- android:orientation="vertical">
+ android:orientation="vertical"
+ app:wrapAround="true">
<TextView
android:layout_height="@dimen/description_height"
android:layout_width="match_parent"
@@ -305,6 +298,68 @@
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.car.ui.FocusArea>
+ <!-- A FocusArea to demonstrate how to update a view properly.
+ Don't remove the focused view, otherwise Android framework will focus on another
+ view (the default focus view, or the first focusable view in the view tree), and
+ the user will see the focus highlight jump away unexpectedly. Some workarounds:
+ 1. Don't remove the view. You may achieve the desired behavior by updating the
+ source image of focused view.
+ 2. Delegate the view focus to its container, therefore removing the view won't
+ affect the focus
+ 3. Make another view request focus explicitly after Android framework adjusts the
+ focus.
+ Don't disable the focused view, otherwise Android framework will focus on another
+ view. A workaround is to use a custom state to replace android:state_disabled so
+ that it appears disabled but is not disabled actually.
+ -->
+ <com.android.car.ui.FocusArea
+ android:id="@+id/focus_area5"
+ android:background="@color/card_background_color"
+ android:layout_margin="16dp"
+ android:layout_width="@dimen/card_width"
+ android:layout_height="match_parent"
+ android:padding="@dimen/card_padding"
+ android:orientation="vertical">
+ <Button
+ android:id="@+id/button_5a"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:text="focus jumps" />
+ <Button
+ android:id="@+id/button_5b"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/ic_play_arrow"/>
+ <FrameLayout
+ android:id="@+id/button_5c_container"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:focusable="true">
+ <Button
+ android:id="@+id/button_5c"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:text="focus stays" />
+ </FrameLayout>
+ <Button
+ android:id="@+id/button_5d"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:text="focus returns" />
+ <Button
+ android:id="@+id/button_5e"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/button_background"/>
+ <com.android.car.rotaryplayground.CustomButton
+ android:id="@+id/button_5f"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/custom_button_background"/>
+ </com.android.car.ui.FocusArea>
</LinearLayout>
</HorizontalScrollView>
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/RotaryPlayground/res/values/attrs.xml b/RotaryPlayground/res/values/attrs.xml
new file mode 100644
index 0000000..6b5aa4f
--- /dev/null
+++ b/RotaryPlayground/res/values/attrs.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+<resources>
+ <declare-styleable name="CustomButton">
+ <attr name="state_rotary_enabled" format="boolean" />
+ </declare-styleable>
+</resources>
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/CustomButton.java b/RotaryPlayground/src/com/android/car/rotaryplayground/CustomButton.java
new file mode 100644
index 0000000..924908f
--- /dev/null
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/CustomButton.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.car.rotaryplayground;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+import androidx.annotation.Nullable;
+
+public class CustomButton extends Button {
+
+ private static final int[] STATE_ROTARY_ENABLED = {R.attr.state_rotary_enabled};
+
+ private boolean mRotaryEnabled = true;
+
+ public CustomButton(Context context) {
+ super(context);
+ }
+
+ public CustomButton(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CustomButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void setRotaryEnabled(boolean enabled) {
+ mRotaryEnabled = enabled;
+ refreshDrawableState();
+ }
+
+ public boolean isRotaryEnabled() {
+ return mRotaryEnabled;
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (mRotaryEnabled) {
+ mergeDrawableStates(drawableState, STATE_ROTARY_ENABLED);
+ }
+ return drawableState;
+ }
+}
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryCards.java b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryCards.java
index 28f34a1..730c7c5 100644
--- a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryCards.java
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryCards.java
@@ -16,13 +16,18 @@
package com.android.car.rotaryplayground;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
+
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import com.android.car.ui.FocusArea;
+
/** Fragment to demo a layout with cards that are FocusArea containers. */
public class RotaryCards extends Fragment {
@@ -30,6 +35,62 @@ public class RotaryCards extends Fragment {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.rotary_cards, container, false);
+
+ // This button will be removed and immediately added back after click. So the focus
+ // highlight will jump to another view after rotary click.
+ FocusArea focusArea5 = view.findViewById(R.id.focus_area5);
+ Button button5a = view.findViewById(R.id.button_5a);
+ button5a.setOnClickListener(v -> {
+ int index = focusArea5.indexOfChild(button5a);
+ focusArea5.removeView(button5a);
+ focusArea5.addView(button5a, index);
+ });
+
+ // The background of this button will be changed after click. The focus highlight will stay
+ // on this Button after click.
+ Button button5b = view.findViewById(R.id.button_5b);
+ boolean[] stopped = {false};
+ button5b.setOnClickListener(v -> {
+ stopped[0] = !stopped[0];
+ Drawable drawable = view.getContext().getDrawable(
+ stopped[0] ? R.drawable.ic_stop : R.drawable.ic_play_arrow);
+ button5b.setBackground(drawable);
+ });
+
+ // This button will be removed and immediately added back after click. It's not focusable
+ // but its container is focusable. The focus highlight will stay on the container after
+ // click.
+ ViewGroup button5cContainer = view.findViewById(R.id.button_5c_container);
+ Button button5c = view.findViewById(R.id.button_5c);
+ button5c.setFocusable(false);
+ button5cContainer.setOnClickListener(v -> {
+ button5cContainer.removeView(button5c);
+ button5cContainer.addView(button5c, 0);
+ });
+
+ // This button will be removed then added back and request focus explicitly after click.
+ // So the focus highlight will jump to another view then jump back after rotary click.
+ Button button5d = view.findViewById(R.id.button_5d);
+ button5d.setOnClickListener(v -> {
+ boolean needRestoreFocus = button5d.isFocused();
+ int index = focusArea5.indexOfChild(button5d);
+ focusArea5.removeView(button5d);
+ focusArea5.addView(button5d, index);
+ if (needRestoreFocus) {
+ button5d.requestFocus();
+ }
+ });
+
+ // This button will be disabled after click. So the focus highlight will jump to another
+ // view after rotary click.
+ Button button5e = view.findViewById(R.id.button_5e);
+ button5e.setOnClickListener(v -> button5e.setEnabled(!button5e.isEnabled()));
+
+ // This button will appear disabled but is not disabled after click. So the focus
+ // highlight will stay after rotary click.
+ CustomButton button5f = view.findViewById(R.id.button_5f);
+ button5f.setOnClickListener(v -> button5f.setRotaryEnabled(!button5f.isRotaryEnabled()));
+
return view;
}
}