Skip to content

Commit

Permalink
[CleanUp][TextField] Extract start components from TextInputLayout
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 432953110
  • Loading branch information
drchen authored and veganafro committed Mar 9, 2022
1 parent b4d4450 commit 9a46af2
Show file tree
Hide file tree
Showing 3 changed files with 374 additions and 169 deletions.
Expand Up @@ -19,13 +19,47 @@
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import com.google.android.material.internal.CheckableImageButton;
import java.util.Arrays;

class IconTintHelper {
private IconTintHelper() {}
class IconHelper {
private IconHelper() {}

static void setIconOnClickListener(
@NonNull CheckableImageButton iconView,
@Nullable OnClickListener onClickListener,
@Nullable OnLongClickListener onLongClickListener) {
iconView.setOnClickListener(onClickListener);
setIconClickable(iconView, onLongClickListener);
}

static void setIconOnLongClickListener(
@NonNull CheckableImageButton iconView, @Nullable OnLongClickListener onLongClickListener) {
iconView.setOnLongClickListener(onLongClickListener);
setIconClickable(iconView, onLongClickListener);
}

private static void setIconClickable(
@NonNull CheckableImageButton iconView, @Nullable OnLongClickListener onLongClickListener) {
boolean iconClickable = ViewCompat.hasOnClickListeners(iconView);
boolean iconLongClickable = onLongClickListener != null;
boolean iconFocusable = iconClickable || iconLongClickable;
iconView.setFocusable(iconFocusable);
iconView.setClickable(iconClickable);
iconView.setPressable(iconClickable);
iconView.setLongClickable(iconLongClickable);
ViewCompat.setImportantForAccessibility(
iconView,
iconFocusable
? ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES
: ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
}

/**
* Applies the given icon tint according to the merged view state of the host text input layout
Expand Down
@@ -0,0 +1,305 @@
/*
* 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 com.google.android.material.R;

import static com.google.android.material.textfield.IconHelper.applyIconTint;
import static com.google.android.material.textfield.IconHelper.refreshIconDrawableState;
import static com.google.android.material.textfield.IconHelper.setIconOnClickListener;
import static com.google.android.material.textfield.IconHelper.setIconOnLongClickListener;

import android.annotation.SuppressLint;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.TintTypedArray;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.core.view.MarginLayoutParamsCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.widget.TextViewCompat;
import com.google.android.material.internal.CheckableImageButton;
import com.google.android.material.internal.ViewUtils;
import com.google.android.material.resources.MaterialResources;

/**
* A compound layout that includes views that will be shown at the start of {@link TextInputLayout}
* and their relevant rendering and presenting logic.
*/
@SuppressLint("ViewConstructor")
class StartCompoundLayout extends LinearLayout {
private final TextInputLayout textInputLayout;

private final TextView prefixTextView;
@Nullable private CharSequence prefixText;

private final CheckableImageButton startIconView;
private ColorStateList startIconTintList;
private PorterDuff.Mode startIconTintMode;
private OnLongClickListener startIconOnLongClickListener;

private boolean hintExpanded;

StartCompoundLayout(TextInputLayout textInputLayout, TintTypedArray a) {
super(textInputLayout.getContext());

this.textInputLayout = textInputLayout;

setVisibility(GONE);
setOrientation(HORIZONTAL);
setLayoutParams(
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.START | Gravity.LEFT));

final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
startIconView =
(CheckableImageButton)
layoutInflater.inflate(R.layout.design_text_input_start_icon, this, false);

prefixTextView = new AppCompatTextView(getContext());

initStartIconView(a);
initPrefixTextView(a);

addView(startIconView);
addView(prefixTextView);
}

private void initStartIconView(TintTypedArray a) {

if (MaterialResources.isFontScaleAtLeast1_3(getContext())) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) startIconView.getLayoutParams();
MarginLayoutParamsCompat.setMarginEnd(lp, 0);
}
setStartIconOnClickListener(null);
setStartIconOnLongClickListener(null);
// Default tint for a start icon or value specified by user.
if (a.hasValue(R.styleable.TextInputLayout_startIconTint)) {
startIconTintList =
MaterialResources.getColorStateList(
getContext(), a, R.styleable.TextInputLayout_startIconTint);
}
// Default tint mode for a start icon or value specified by user.
if (a.hasValue(R.styleable.TextInputLayout_startIconTintMode)) {
startIconTintMode =
ViewUtils.parseTintMode(
a.getInt(R.styleable.TextInputLayout_startIconTintMode, -1), null);
}
// Set up start icon if any.
if (a.hasValue(R.styleable.TextInputLayout_startIconDrawable)) {
setStartIconDrawable(a.getDrawable(R.styleable.TextInputLayout_startIconDrawable));
if (a.hasValue(R.styleable.TextInputLayout_startIconContentDescription)) {
setStartIconContentDescription(
a.getText(R.styleable.TextInputLayout_startIconContentDescription));
}
setStartIconCheckable(a.getBoolean(R.styleable.TextInputLayout_startIconCheckable, true));
}
}

private void initPrefixTextView(TintTypedArray a) {
prefixTextView.setVisibility(GONE);
// Set up prefix view.
prefixTextView.setId(R.id.textinput_prefix_text);
prefixTextView.setLayoutParams(
new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
ViewCompat.setAccessibilityLiveRegion(
prefixTextView, ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);

setPrefixTextAppearance(a.getResourceId(R.styleable.TextInputLayout_prefixTextAppearance, 0));
if (a.hasValue(R.styleable.TextInputLayout_prefixTextColor)) {
setPrefixTextColor(a.getColorStateList(R.styleable.TextInputLayout_prefixTextColor));
}
setPrefixText(a.getText(R.styleable.TextInputLayout_prefixText));
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
updatePrefixTextViewPadding();
}

@NonNull
TextView getPrefixTextView() {
return prefixTextView;
}

void setPrefixText(@Nullable final CharSequence prefixText) {
this.prefixText = TextUtils.isEmpty(prefixText) ? null : prefixText;
prefixTextView.setText(prefixText);
updateVisibility();
}

/**
* Returns the prefix text that was set to be displayed with {@link #setPrefixText(CharSequence)},
* or <code>null</code> if there is no prefix text.
*
* @see #setPrefixText(CharSequence)
*/
@Nullable
CharSequence getPrefixText() {
return prefixText;
}

void setPrefixTextColor(@NonNull ColorStateList prefixTextColor) {
prefixTextView.setTextColor(prefixTextColor);
}

@Nullable
ColorStateList getPrefixTextColor() {
return prefixTextView.getTextColors();
}

void setPrefixTextAppearance(@StyleRes int prefixTextAppearance) {
TextViewCompat.setTextAppearance(prefixTextView, prefixTextAppearance);
}

void setStartIconDrawable(@Nullable Drawable startIconDrawable) {
startIconView.setImageDrawable(startIconDrawable);
if (startIconDrawable != null) {
applyIconTint(textInputLayout, startIconView, startIconTintList, startIconTintMode);
setStartIconVisible(true);
refreshStartIconDrawableState();
} else {
setStartIconVisible(false);
setStartIconOnClickListener(null);
setStartIconOnLongClickListener(null);
setStartIconContentDescription(null);
}
}

@Nullable
Drawable getStartIconDrawable() {
return startIconView.getDrawable();
}

void setStartIconOnClickListener(@Nullable OnClickListener startIconOnClickListener) {
setIconOnClickListener(startIconView, startIconOnClickListener, startIconOnLongClickListener);
}

void setStartIconOnLongClickListener(
@Nullable OnLongClickListener startIconOnLongClickListener) {
this.startIconOnLongClickListener = startIconOnLongClickListener;
setIconOnLongClickListener(startIconView, startIconOnLongClickListener);
}

void setStartIconVisible(boolean visible) {
if (isStartIconVisible() != visible) {
startIconView.setVisibility(visible ? View.VISIBLE : View.GONE);
updatePrefixTextViewPadding();
updateVisibility();
}
}

boolean isStartIconVisible() {
return startIconView.getVisibility() == View.VISIBLE;
}

void refreshStartIconDrawableState() {
refreshIconDrawableState(textInputLayout, startIconView, startIconTintList);
}

void setStartIconCheckable(boolean startIconCheckable) {
startIconView.setCheckable(startIconCheckable);
}

boolean isStartIconCheckable() {
return startIconView.isCheckable();
}

void setStartIconContentDescription(@Nullable CharSequence startIconContentDescription) {
if (getStartIconContentDescription() != startIconContentDescription) {
startIconView.setContentDescription(startIconContentDescription);
}
}

@Nullable
CharSequence getStartIconContentDescription() {
return startIconView.getContentDescription();
}

void setStartIconTintList(@Nullable ColorStateList startIconTintList) {
if (this.startIconTintList != startIconTintList) {
this.startIconTintList = startIconTintList;
applyIconTint(textInputLayout, startIconView, startIconTintList, startIconTintMode);
}
}

void setStartIconTintMode(@Nullable PorterDuff.Mode startIconTintMode) {
if (this.startIconTintMode != startIconTintMode) {
this.startIconTintMode = startIconTintMode;
applyIconTint(textInputLayout, startIconView, startIconTintList, this.startIconTintMode);
}
}

void setupAccessibilityNodeInfo(@NonNull AccessibilityNodeInfoCompat info) {
if (prefixTextView.getVisibility() == VISIBLE) {
info.setLabelFor(prefixTextView);
info.setTraversalAfter(prefixTextView);
} else {
info.setTraversalAfter(startIconView);
}
}

void updatePrefixTextViewPadding() {
EditText editText = textInputLayout.editText;
if (editText == null) {
return;
}
int startPadding = isStartIconVisible() ? 0 : ViewCompat.getPaddingStart(editText);
ViewCompat.setPaddingRelative(
prefixTextView,
startPadding,
editText.getCompoundPaddingTop(),
getContext()
.getResources()
.getDimensionPixelSize(R.dimen.material_input_text_to_prefix_suffix_padding),
editText.getCompoundPaddingBottom());
}

void onHintStateChanged(boolean hintExpanded) {
this.hintExpanded = hintExpanded;
updateVisibility();
}

private void updateVisibility() {
// Set startLayout to visible if start icon or prefix text is present.
int prefixTextVisibility = (prefixText != null && !hintExpanded) ? VISIBLE : GONE;
boolean shouldBeVisible =
startIconView.getVisibility() == VISIBLE || prefixTextVisibility == VISIBLE;
setVisibility(shouldBeVisible ? VISIBLE : GONE);
// Set prefix visibility after updating layout's visibility so screen readers correctly announce
// when prefix text appears.
prefixTextView.setVisibility(prefixTextVisibility);
textInputLayout.updateDummyDrawables();
}
}

0 comments on commit 9a46af2

Please sign in to comment.