Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CleanUp][TextField] Extract start components from TextInputLayout
PiperOrigin-RevId: 432953110
- Loading branch information
Showing
3 changed files
with
374 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
305 changes: 305 additions & 0 deletions
305
lib/java/com/google/android/material/textfield/StartCompoundLayout.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
Oops, something went wrong.