diff options
author | Yabin Huang <yabinh@google.com> | 2021-08-12 12:29:20 -0700 |
---|---|---|
committer | Yabin Huang <yabinh@google.com> | 2021-08-19 22:18:46 +0000 |
commit | 2b9df41b68200f43ae55e901f5a8bdab53d2ecae (patch) | |
tree | f9fed77efa60ac40fedb06833e01349fdd7f3d61 | |
parent | 01091850d9e99ffabcd8091c3dc15473adc47303 (diff) | |
download | tests-2b9df41b68200f43ae55e901f5a8bdab53d2ecae.tar.gz |
Add an example of popup window
Bug: 194954154
Test: manual
Change-Id: Ifbd468ec38a2cb519e5f5343dde5e64c10d2a8aa
5 files changed, 198 insertions, 0 deletions
diff --git a/RotaryPlayground/res/layout/list_item.xml b/RotaryPlayground/res/layout/list_item.xml new file mode 100644 index 0000000..dd36ec7 --- /dev/null +++ b/RotaryPlayground/res/layout/list_item.xml @@ -0,0 +1,20 @@ +<?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. + --> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="true"/> diff --git a/RotaryPlayground/res/layout/popup_window_fragment.xml b/RotaryPlayground/res/layout/popup_window_fragment.xml new file mode 100644 index 0000000..f3ba9d2 --- /dev/null +++ b/RotaryPlayground/res/layout/popup_window_fragment.xml @@ -0,0 +1,26 @@ +<?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. + --> +<com.android.car.ui.FocusArea + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <Button + android:id="@+id/overflow" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Overflow Button"/> +</com.android.car.ui.FocusArea> diff --git a/RotaryPlayground/res/layout/rotary_menu.xml b/RotaryPlayground/res/layout/rotary_menu.xml index e2a523a..53f17b7 100644 --- a/RotaryPlayground/res/layout/rotary_menu.xml +++ b/RotaryPlayground/res/layout/rotary_menu.xml @@ -77,6 +77,13 @@ android:text="Custom FocusAreas" style="@style/tab" /> <Button + android:id="@+id/popup_window" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:text="Popup window" + style="@style/tab" /> + <Button android:id="@+id/surface_view" android:layout_width="match_parent" android:layout_height="0dp" diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/PopupWindowFragment.java b/RotaryPlayground/src/com/android/car/rotaryplayground/PopupWindowFragment.java new file mode 100644 index 0000000..f2ae91a --- /dev/null +++ b/RotaryPlayground/src/com/android/car/rotaryplayground/PopupWindowFragment.java @@ -0,0 +1,131 @@ +/* + * 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.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListPopupWindow; +import android.widget.ListView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.android.car.ui.FocusParkingView; + +/** Fragment to demo a popup window. */ +public class PopupWindowFragment extends Fragment { + + private static final String[] ACTIONS = {"Action 1", "Action 2", "Action 3"}; + private static final int WINDOW_WIDTH = 300; + private static final int WINDOW_HEIGHT = 400; + + @NonNull + private ListPopupWindow mPopupWindow; + @Nullable + private ListView mListView; + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.popup_window_fragment, container, false); + Button overflow = view.findViewById(R.id.overflow); + + mPopupWindow = new ListPopupWindow(getActivity()); + mPopupWindow.setAdapter(new ArrayAdapter<>(getActivity(), R.layout.list_item, ACTIONS)); + mPopupWindow.setAnchorView(overflow); + mPopupWindow.setWidth(WINDOW_WIDTH); + mPopupWindow.setHeight(WINDOW_HEIGHT); + mPopupWindow.setModal(true); + mPopupWindow.setOnItemClickListener((parent, v, position, id) -> { + Toast.makeText(getActivity(), ACTIONS[position], Toast.LENGTH_SHORT).show(); + mPopupWindow.dismiss(); + }); + + overflow.setOnClickListener((v) -> { + mPopupWindow.show(); + mListView = mPopupWindow.getListView(); + if (mListView != null && mListView.isFocused()) { + initRotaryMode(); + } + }); + + return view; + } + + /** + * Initializes the popup window to support rotary controller rotations and nudges. + * <p> + * In rotary mode the first focusable element in the ListView should be focused automatically + * when the popup window shows up. However, in {@link ListPopupWindow#show}, Android framework + * creates the ListView, sets it focusable, then focuses on it automatically. What's worse, + * ListView doesn't handle focusSearch() properly, causing rotation fails once it's focused. + * To work around it, move focus to its first focusable element, then set the ListView + * to not be focusable. + */ + private void initRotaryMode() { + if (mListView == null) { + return; + } + + // Add a FocusParkingView to prevent rotary controller rotation wrap-around. + View root = mListView.getRootView(); + if (root instanceof ViewGroup) { + ViewGroup rootView = (ViewGroup) root; + FocusParkingView fpv = new FocusParkingView(getActivity()); + + // This popup window can be dismissed by controller nudges. + fpv.setOnPopupWindowDismiss(() -> mPopupWindow.dismiss()); + + rootView.addView(fpv); + } + + // At this time the elements in the list are not loaded yet. Because there is no callbacks + // to notify whether the elements are loaded, use an OnPreDrawListener as workaround. + ViewTreeObserver.OnPreDrawListener listeners[] = + new ViewTreeObserver.OnPreDrawListener[1]; + listeners[0] = (() -> { + boolean focusInitialized = false; + for (int i = 0; i < mListView.getChildCount(); i++) { + View child = mListView.getChildAt(i); + if (child != null) { + // Because the elements are set focusable, they'll steal click events from the + // list, so they need to handle the events on their own. + int position = i; + child.setOnClickListener(v -> mPopupWindow.performItemClick(position)); + + // Move focus to the first focusable element and set the list non-focussable. + if (!focusInitialized) { + focusInitialized = child.requestFocus(); + if (focusInitialized) { + mListView.setFocusable(false); + mListView.getViewTreeObserver().removeOnPreDrawListener(listeners[0]); + } + } + } + } + return true; + }); + mListView.getViewTreeObserver().addOnPreDrawListener(listeners[0]); + } +} diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java index 4b8844d..f1ffe86 100644 --- a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java +++ b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java @@ -39,6 +39,7 @@ public class RotaryMenu extends Fragment { private Fragment mScrollFragment; private Fragment mWebViewFragment; private Fragment mCustomFocusAreasFragment; + private Fragment mPopupWindowFragment; private Fragment mSurfaceViewFragment; @Override @@ -94,6 +95,12 @@ public class RotaryMenu extends Fragment { showCustomFocusAreasFragment(); }); + Button popupWindowButton = view.findViewById(R.id.popup_window); + popupWindowButton.setOnClickListener(v -> { + selectTab(v); + showPopupWindowFragment(); + }); + Button surfaceViewButton = view.findViewById(R.id.surface_view); surfaceViewButton.setOnClickListener(v -> { selectTab(v); @@ -169,6 +176,13 @@ public class RotaryMenu extends Fragment { showFragment(mCustomFocusAreasFragment); } + private void showPopupWindowFragment() { + if (mPopupWindowFragment == null) { + mPopupWindowFragment = new PopupWindowFragment(); + } + showFragment(mPopupWindowFragment); + } + private void showSurfaceViewFragment() { if (mSurfaceViewFragment == null) { mSurfaceViewFragment = new SurfaceViewFragment(); |