/* * Copyright (C) 2019 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.google.android.material.textfield; import com.google.android.material.R; import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatAutoCompleteTextView; import androidx.appcompat.widget.ListPopupWindow; import android.text.InputType; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Filterable; import android.widget.ListAdapter; import com.google.android.material.internal.ManufacturerUtils; import com.google.android.material.internal.ThemeEnforcement; /** * A special sub-class of {@link android.widget.AutoCompleteTextView} that is auto-inflated so that * non-editable auto-complete text fields (e.g., for an Exposed Dropdown Menu) are accessible when * being interacted through a screen reader. * *
The {@link ListPopupWindow} of the {@link android.widget.AutoCompleteTextView} is not modal,
* so it does not grab accessibility focus. The {@link MaterialAutoCompleteTextView} changes that
* by having a modal {@link ListPopupWindow} that is displayed instead of the non-modal one when the
* {@link MaterialAutoCompleteTextView} is not editable, so that the first item of the popup is
* automatically focused. This simulates the behavior of the {@link android.widget.Spinner}.
*/
public class MaterialAutoCompleteTextView extends AppCompatAutoCompleteTextView {
private static final int MAX_ITEMS_MEASURED = 15;
@NonNull private final ListPopupWindow modalListPopup;
@Nullable private final AccessibilityManager accessibilityManager;
@NonNull private final Rect tempRect = new Rect();
public MaterialAutoCompleteTextView(@NonNull Context context) {
this(context, null);
}
public MaterialAutoCompleteTextView(
@NonNull Context context, @Nullable AttributeSet attributeSet) {
this(context, attributeSet, R.attr.autoCompleteTextViewStyle);
}
public MaterialAutoCompleteTextView(
@NonNull Context context, @Nullable AttributeSet attributeSet, int defStyleAttr) {
super(wrap(context, attributeSet, defStyleAttr, 0), attributeSet, defStyleAttr);
// Ensure we are using the correctly themed context rather than the context that was passed in.
context = getContext();
TypedArray attributes =
ThemeEnforcement.obtainStyledAttributes(
context,
attributeSet,
R.styleable.MaterialAutoCompleteTextView,
defStyleAttr,
R.style.Widget_AppCompat_AutoCompleteTextView);
// Due to a framework bug, setting android:inputType="none" on xml has no effect. Therefore,
// we check it here in case the autoCompleteTextView should be non-editable.
if (attributes.hasValue(R.styleable.MaterialAutoCompleteTextView_android_inputType)) {
int inputType =
attributes.getInt(
R.styleable.MaterialAutoCompleteTextView_android_inputType, InputType.TYPE_NULL);
if (inputType == InputType.TYPE_NULL) {
setKeyListener(null);
}
}
accessibilityManager =
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
modalListPopup = new ListPopupWindow(context);
modalListPopup.setModal(true);
modalListPopup.setAnchorView(this);
modalListPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
modalListPopup.setAdapter(getAdapter());
modalListPopup.setOnItemClickListener(
new OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View selectedView, int position, long id) {
Object selectedItem =
position < 0 ? modalListPopup.getSelectedItem() : getAdapter().getItem(position);
updateText(selectedItem);
OnItemClickListener userOnitemClickListener = getOnItemClickListener();
if (userOnitemClickListener != null) {
if (selectedView == null || position < 0) {
selectedView = modalListPopup.getSelectedView();
position = modalListPopup.getSelectedItemPosition();
id = modalListPopup.getSelectedItemId();
}
userOnitemClickListener.onItemClick(
modalListPopup.getListView(), selectedView, position, id);
}
modalListPopup.dismiss();
}
});
attributes.recycle();
}
@Override
public void showDropDown() {
if (getInputType() == EditorInfo.TYPE_NULL
&& accessibilityManager != null
&& accessibilityManager.isTouchExplorationEnabled()) {
modalListPopup.show();
} else {
super.showDropDown();
}
}
@Override
public