Skip to content

Commit

Permalink
[CleanUp][TextField] Move drop-down background and ripple creation to…
Browse files Browse the repository at this point in the history
… TextInputLayout class

This CL also cleans up and fixes the edit text background setting logic - when the edit text is an autocomplete text view and provides its own background, we won't override the background by trying to add ripples to it.

Later they should be split again from the main class with other box-background-relevant logic.

Resolves #1431

PiperOrigin-RevId: 445486075
  • Loading branch information
drchen authored and dsn5ft committed May 2, 2022
1 parent 83f7a7a commit 1a42c74
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 223 deletions.
Expand Up @@ -21,23 +21,17 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO;
import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static com.google.android.material.textfield.EditTextUtils.isEditable;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.text.Editable;
import android.text.InputType;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
Expand All @@ -57,9 +51,6 @@
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.google.android.material.animation.AnimationUtils;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;
import com.google.android.material.textfield.TextInputLayout.AccessibilityDelegate;
import com.google.android.material.textfield.TextInputLayout.BoxBackgroundMode;

Expand Down Expand Up @@ -140,7 +131,6 @@ public void onFocusChange(View v, boolean hasFocus) {

@Override
void setUp() {

// For lollipop+, the arrow icon changes orientation based on dropdown popup, otherwise it
// always points down.
int drawableResId =
Expand Down Expand Up @@ -193,19 +183,6 @@ boolean isBoxBackgroundModeSupported(@BoxBackgroundMode int boxBackgroundMode) {
return boxBackgroundMode != TextInputLayout.BOX_BACKGROUND_NONE;
}

/*
* This method should be called if the ripple background should be updated. For example,
* if a new {@link ShapeAppearanceModel} is set on the text field, or if a different
* {@link InputType} is set on the {@link AutoCompleteTextView}.
*/
void updateBackground(@NonNull AutoCompleteTextView editText) {
if (isEditable(editText)) {
removeRippleEffect(editText);
} else {
addRippleEffect(editText);
}
}

@Override
OnClickListener getOnIconClickListener() {
return onIconClickListener;
Expand All @@ -214,15 +191,6 @@ OnClickListener getOnIconClickListener() {
@Override
public void onEditTextAttached(@Nullable EditText editText) {
AutoCompleteTextView autoCompleteTextView = castAutoCompleteTextViewOrThrow(editText);

float popupElevation =
(autoCompleteTextView instanceof MaterialAutoCompleteTextView)
? ((MaterialAutoCompleteTextView) autoCompleteTextView).getPopupElevation()
: context
.getResources()
.getDimensionPixelOffset(R.dimen.m3_exposed_dropdown_menu_popup_elevation);
setPopupBackground(autoCompleteTextView, popupElevation);
addRippleEffect(autoCompleteTextView);
setUpDropdownShowHideBehavior(autoCompleteTextView);
autoCompleteTextView.setThreshold(0);
textInputLayout.setEndIconCheckable(true);
Expand Down Expand Up @@ -286,120 +254,6 @@ private void showHideDropdown(@Nullable AutoCompleteTextView editText) {
}
}

private void setPopupBackground(@NonNull AutoCompleteTextView editText, float popupElevation) {
if (IS_LOLLIPOP && editText.getDropDownBackground() == null) {
int boxBackgroundMode = textInputLayout.getBoxBackgroundMode();
float popupCornerRadius =
context
.getResources()
.getDimensionPixelOffset(R.dimen.mtrl_shape_corner_size_small_component);
int popupVerticalPadding =
context
.getResources()
.getDimensionPixelOffset(R.dimen.mtrl_exposed_dropdown_menu_popup_vertical_padding);

Drawable popupBackground =
boxBackgroundMode == TextInputLayout.BOX_BACKGROUND_OUTLINE
? getPopUpMaterialShapeDrawable(
popupCornerRadius, popupCornerRadius, popupElevation, popupVerticalPadding)
: getPopupBackgroundFilledMode(
popupCornerRadius, popupElevation, popupVerticalPadding);
editText.setDropDownBackgroundDrawable(popupBackground);
}
}

/* Remove ripple effect from editable layouts if it's present. */
private void removeRippleEffect(@NonNull AutoCompleteTextView editText) {
if (!(editText.getBackground() instanceof LayerDrawable) || !isEditable(editText)) {
return;
}
int boxBackgroundMode = textInputLayout.getBoxBackgroundMode();
LayerDrawable layerDrawable = (LayerDrawable) editText.getBackground();
int backgroundLayerIndex = boxBackgroundMode == TextInputLayout.BOX_BACKGROUND_OUTLINE ? 1 : 0;
ViewCompat.setBackground(editText, layerDrawable.getDrawable(backgroundLayerIndex));
}

/* Add ripple effect to non editable layouts. */
private void addRippleEffect(@NonNull AutoCompleteTextView editText) {
if (isEditable(editText)) {
return;
}

int boxBackgroundMode = textInputLayout.getBoxBackgroundMode();
MaterialShapeDrawable boxBackground = textInputLayout.getBoxBackground();
int rippleColor = MaterialColors.getColor(editText, R.attr.colorControlHighlight);
int[][] states =
new int[][] {
new int[] {android.R.attr.state_pressed}, new int[] {},
};

if (boxBackgroundMode == TextInputLayout.BOX_BACKGROUND_OUTLINE) {
addRippleEffectOnOutlinedLayout(editText, rippleColor, states, boxBackground);
} else if (boxBackgroundMode == TextInputLayout.BOX_BACKGROUND_FILLED) {
addRippleEffectOnFilledLayout(editText, rippleColor, states, boxBackground);
}
}

private void addRippleEffectOnOutlinedLayout(
@NonNull AutoCompleteTextView editText,
int rippleColor,
int[][] states,
@NonNull MaterialShapeDrawable boxBackground) {
LayerDrawable editTextBackground;
int surfaceColor = MaterialColors.getColor(editText, R.attr.colorSurface);
MaterialShapeDrawable rippleBackground =
new MaterialShapeDrawable(boxBackground.getShapeAppearanceModel());
int pressedBackgroundColor = MaterialColors.layer(rippleColor, surfaceColor, 0.1f);
int[] rippleBackgroundColors = new int[] {pressedBackgroundColor, Color.TRANSPARENT};
rippleBackground.setFillColor(new ColorStateList(states, rippleBackgroundColors));

if (IS_LOLLIPOP) {
rippleBackground.setTint(surfaceColor);
int[] colors = new int[] {pressedBackgroundColor, surfaceColor};
ColorStateList rippleColorStateList = new ColorStateList(states, colors);
MaterialShapeDrawable mask =
new MaterialShapeDrawable(boxBackground.getShapeAppearanceModel());
mask.setTint(Color.WHITE);
Drawable rippleDrawable = new RippleDrawable(rippleColorStateList, rippleBackground, mask);
Drawable[] layers = {rippleDrawable, boxBackground};
editTextBackground = new LayerDrawable(layers);
} else {
Drawable[] layers = {rippleBackground, boxBackground};
editTextBackground = new LayerDrawable(layers);
}

ViewCompat.setBackground(editText, editTextBackground);
}

private void addRippleEffectOnFilledLayout(
@NonNull AutoCompleteTextView editText,
int rippleColor,
int[][] states,
@NonNull MaterialShapeDrawable boxBackground) {
int boxBackgroundColor = textInputLayout.getBoxBackgroundColor();
int pressedBackgroundColor = MaterialColors.layer(rippleColor, boxBackgroundColor, 0.1f);
int[] colors = new int[] {pressedBackgroundColor, boxBackgroundColor};

if (IS_LOLLIPOP) {
ColorStateList rippleColorStateList = new ColorStateList(states, colors);
Drawable editTextBackground =
new RippleDrawable(rippleColorStateList, boxBackground, boxBackground);
ViewCompat.setBackground(editText, editTextBackground);
} else {
MaterialShapeDrawable rippleBackground =
new MaterialShapeDrawable(boxBackground.getShapeAppearanceModel());
rippleBackground.setFillColor(new ColorStateList(states, colors));
Drawable[] layers = {boxBackground, rippleBackground};
LayerDrawable editTextBackground = new LayerDrawable(layers);
int start = ViewCompat.getPaddingStart(editText);
int top = editText.getPaddingTop();
int end = ViewCompat.getPaddingEnd(editText);
int bottom = editText.getPaddingBottom();
ViewCompat.setBackground(editText, editTextBackground);
ViewCompat.setPaddingRelative(editText, start, top, end, bottom);
}
}

@SuppressLint("ClickableViewAccessibility") // There's an accessibility delegate that handles
// interactions with the dropdown menu.
private void setUpDropdownShowHideBehavior(@NonNull final AutoCompleteTextView editText) {
Expand Down Expand Up @@ -430,40 +284,6 @@ public void onDismiss() {
}
}

private StateListDrawable getPopupBackgroundFilledMode(
float popupCornerRadius, float popupElevation, int popupVerticalPadding) {
// Background for the popup when it is being displayed above the layout.
MaterialShapeDrawable roundedCornersPopupBackground =
getPopUpMaterialShapeDrawable(
popupCornerRadius, popupCornerRadius, popupElevation, popupVerticalPadding);
// Background for the popup when it is being displayed below the layout.
MaterialShapeDrawable roundedBottomCornersPopupBackground =
getPopUpMaterialShapeDrawable(0, popupCornerRadius, popupElevation, popupVerticalPadding);

StateListDrawable popupBackground = new StateListDrawable();
popupBackground.addState(
new int[] {android.R.attr.state_above_anchor}, roundedCornersPopupBackground);
popupBackground.addState(new int[] {}, roundedBottomCornersPopupBackground);

return popupBackground;
}

private MaterialShapeDrawable getPopUpMaterialShapeDrawable(
float topCornerRadius, float bottomCornerRadius, float elevation, int verticalPadding) {
ShapeAppearanceModel shapeAppearanceModel =
ShapeAppearanceModel.builder()
.setTopLeftCornerSize(topCornerRadius)
.setTopRightCornerSize(topCornerRadius)
.setBottomLeftCornerSize(bottomCornerRadius)
.setBottomRightCornerSize(bottomCornerRadius)
.build();
MaterialShapeDrawable popupDrawable =
MaterialShapeDrawable.createWithElevationOverlay(context, elevation);
popupDrawable.setShapeAppearanceModel(shapeAppearanceModel);
popupDrawable.setPadding(0, verticalPadding, 0, verticalPadding);
return popupDrawable;
}

private boolean isDropdownPopupActive() {
long activeFor = System.currentTimeMillis() - dropdownPopupActivatedAt;
return activeFor < 0 || activeFor > 300;
Expand All @@ -485,10 +305,6 @@ private void updateDropdownPopupDirty() {
dropdownPopupActivatedAt = System.currentTimeMillis();
}

private static boolean isEditable(@NonNull EditText editText) {
return editText.getInputType() != InputType.TYPE_NULL;
}

private void setEndIconChecked(boolean checked) {
if (isEndIconChecked != checked) {
isEndIconChecked = checked;
Expand Down
29 changes: 29 additions & 0 deletions lib/java/com/google/android/material/textfield/EditTextUtils.java
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2022 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 android.text.InputType;
import android.widget.EditText;
import androidx.annotation.NonNull;

class EditTextUtils {
private EditTextUtils() {}

static boolean isEditable(@NonNull EditText editText) {
return editText.getInputType() != InputType.TYPE_NULL;
}
}
Expand Up @@ -167,7 +167,7 @@ public <T extends ListAdapter & Filterable> void setAdapter(@Nullable T adapter)
@Override
public void setRawInputType(int type) {
super.setRawInputType(type);
updateDropdownMenuBackground();
onInputTypeChanged();
}

/**
Expand Down Expand Up @@ -295,10 +295,10 @@ private int measureContentWidth() {
return width;
}

private void updateDropdownMenuBackground() {
private void onInputTypeChanged() {
TextInputLayout textInputLayout = findTextInputLayoutAncestor();
if (textInputLayout != null) {
textInputLayout.updateDropdownMenuBackground();
textInputLayout.updateEditTextBoxBackgroundIfNeeded();
}
}

Expand Down

0 comments on commit 1a42c74

Please sign in to comment.