Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/widget/FloatLabelLayout.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/FloatLabelLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/FloatLabelLayout.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d8097e72799a48984261636dd8a10b75d88d628d |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/FloatLabelLayout.java |
| @@ -0,0 +1,251 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| + |
| +package org.chromium.chrome.browser.widget; |
| + |
| +import android.content.Context; |
| +import android.content.res.TypedArray; |
| +import android.os.Build; |
| +import android.support.v4.view.ViewCompat; |
| +import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; |
| +import android.text.Editable; |
| +import android.text.TextUtils; |
| +import android.text.TextWatcher; |
| +import android.util.AttributeSet; |
| +import android.util.TypedValue; |
| +import android.view.View; |
| +import android.view.ViewGroup; |
| +import android.view.animation.AnimationUtils; |
| +import android.view.animation.Interpolator; |
| +import android.widget.EditText; |
| +import android.widget.LinearLayout; |
| +import android.widget.TextView; |
| + |
| +import org.chromium.chrome.R; |
| + |
| +/** |
| + * Layout which an {@link android.widget.EditText} to show a floating label when the hint is hidden |
|
newt (away)
2015/01/27 01:45:06
Not quite a sentence
Theresa
2015/01/28 02:05:41
Done.
|
| + * due to the user inputting text. |
|
newt (away)
2015/01/27 01:45:06
It would be helpful to include a little snippet sh
Theresa
2015/01/28 02:05:41
Done.
|
| + * |
| + * @see <a href="https://dribbble.com/shots/1254439--GIF-Mobile-Form-Interaction">Matt D. Smith on Dribble</a> |
|
newt (away)
2015/01/27 01:45:06
This is from Chris Banes, right? Could you link to
Theresa
2015/01/28 02:05:41
Done.
|
| + * @see <a href="http://bradfrostweb.com/blog/post/float-label-pattern/">Brad Frost's blog post</a> |
| + */ |
| +public class FloatLabelLayout extends LinearLayout { |
| + |
| + private static final long ANIMATION_DURATION = 150; |
| + |
| + private static final float DEFAULT_LABEL_PADDING_LEFT = 3f; |
| + private static final float DEFAULT_LABEL_PADDING_TOP = 4f; |
| + private static final float DEFAULT_LABEL_PADDING_RIGHT = 3f; |
| + private static final float DEFAULT_LABEL_PADDING_BOTTOM = 4f; |
| + |
| + private EditText mEditText; |
| + private TextView mLabel; |
| + |
| + private CharSequence mHint; |
| + private Interpolator mInterpolator; |
| + |
| + public FloatLabelLayout(Context context) { |
| + this(context, null); |
| + } |
| + |
| + public FloatLabelLayout(Context context, AttributeSet attrs) { |
| + this(context, attrs, 0); |
| + } |
| + |
| + public FloatLabelLayout(Context context, AttributeSet attrs, int defStyle) { |
| + super(context, attrs, defStyle); |
| + |
| + setOrientation(VERTICAL); |
| + |
| + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FloatLabelLayout); |
| + |
| + int leftPadding = a.getDimensionPixelSize( |
| + R.styleable.FloatLabelLayout_floatLabelPaddingLeft, |
| + dipsToPix(DEFAULT_LABEL_PADDING_LEFT)); |
| + int topPadding = a.getDimensionPixelSize( |
| + R.styleable.FloatLabelLayout_floatLabelPaddingTop, |
| + dipsToPix(DEFAULT_LABEL_PADDING_TOP)); |
| + int rightPadding = a.getDimensionPixelSize( |
| + R.styleable.FloatLabelLayout_floatLabelPaddingRight, |
| + dipsToPix(DEFAULT_LABEL_PADDING_RIGHT)); |
| + int bottomPadding = a.getDimensionPixelSize( |
| + R.styleable.FloatLabelLayout_floatLabelPaddingBottom, |
| + dipsToPix(DEFAULT_LABEL_PADDING_BOTTOM)); |
| + mHint = a.getText(R.styleable.FloatLabelLayout_floatLabelHint); |
| + |
| + mLabel = new TextView(context); |
| + mLabel.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); |
| + mLabel.setVisibility(INVISIBLE); |
| + mLabel.setText(mHint); |
| + ViewCompat.setPivotX(mLabel, 0f); |
| + ViewCompat.setPivotY(mLabel, 0f); |
| + |
| + mLabel.setTextAppearance(context, |
| + a.getResourceId(R.styleable.FloatLabelLayout_floatLabelTextAppearance, |
| + android.R.style.TextAppearance_Small)); |
| + a.recycle(); |
| + |
| + addView(mLabel, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); |
| + |
| + mInterpolator = AnimationUtils.loadInterpolator(context, |
| + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP |
| + ? android.R.interpolator.fast_out_slow_in |
| + : android.R.anim.decelerate_interpolator); |
| + } |
| + |
| + @Override |
| + public final void addView(View child, int index, ViewGroup.LayoutParams params) { |
| + if (child instanceof EditText) { |
| + setEditText((EditText) child); |
| + } |
| + |
| + // Carry on adding the View... |
| + super.addView(child, index, params); |
| + } |
| + |
| + private void setEditText(EditText editText) { |
| + // If we already have an EditText, throw an exception |
| + if (mEditText != null) { |
| + throw new IllegalArgumentException("We already have an EditText, can only have one"); |
| + } |
| + mEditText = editText; |
| + |
| + // Update the label visibility with no animation |
| + updateLabelVisibility(false); |
| + |
| + // Add a TextWatcher so that we know when the text input has changed |
| + mEditText.addTextChangedListener(new TextWatcher() { |
| + @Override |
| + public void afterTextChanged(Editable s) { |
| + updateLabelVisibility(true); |
| + } |
| + |
| + @Override |
| + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} |
| + |
| + @Override |
| + public void onTextChanged(CharSequence s, int start, int before, int count) {} |
| + }); |
| + |
| + // Add focus listener to the EditText so that we can notify the label that it is activated. |
| + // Allows the use of a ColorStateList for the text color on the label |
| + mEditText.setOnFocusChangeListener(new OnFocusChangeListener() { |
| + @Override |
| + public void onFocusChange(View view, boolean focused) { |
| + updateLabelVisibility(true); |
| + } |
| + }); |
| + |
| + // If we do not have a valid hint, try and retrieve it from the EditText |
| + if (TextUtils.isEmpty(mHint)) { |
| + setHint(mEditText.getHint()); |
| + } |
| + } |
| + |
| + private void updateLabelVisibility(boolean animate) { |
| + boolean hasText = !TextUtils.isEmpty(mEditText.getText()); |
| + boolean isFocused = mEditText.isFocused(); |
| + |
| + mLabel.setActivated(isFocused); |
| + |
| + if (hasText || isFocused) { |
| + // We should be showing the label so do so if it isn't already |
| + if (mLabel.getVisibility() != VISIBLE) { |
| + showLabel(animate); |
| + } |
| + } else { |
| + // We should not be showing the label so hide it |
| + if (mLabel.getVisibility() == VISIBLE) { |
| + hideLabel(animate); |
| + } |
| + } |
| + } |
| + |
| + /** |
| + * @return the {@link android.widget.EditText} text input |
| + */ |
| + public EditText getEditText() { |
| + return mEditText; |
| + } |
| + |
| + /** |
| + * @return the {@link android.widget.TextView} label |
| + */ |
| + public TextView getLabel() { |
| + return mLabel; |
| + } |
| + |
| + /** |
| + * Set the hint to be displayed in the floating label |
| + */ |
| + public void setHint(CharSequence hint) { |
| + mHint = hint; |
| + mLabel.setText(hint); |
| + } |
| + |
| + /** |
| + * Show the label |
| + */ |
| + private void showLabel(boolean animate) { |
| + if (animate) { |
| + mLabel.setVisibility(View.VISIBLE); |
| + ViewCompat.setTranslationY(mLabel, mLabel.getHeight()); |
| + |
| + float scale = mEditText.getTextSize() / mLabel.getTextSize(); |
| + ViewCompat.setScaleX(mLabel, scale); |
| + ViewCompat.setScaleY(mLabel, scale); |
| + |
| + ViewCompat.animate(mLabel) |
| + .translationY(0f) |
| + .scaleY(1f) |
| + .scaleX(1f) |
| + .setDuration(ANIMATION_DURATION) |
| + .setListener(null) |
| + .setInterpolator(mInterpolator).start(); |
| + } else { |
| + mLabel.setVisibility(VISIBLE); |
| + } |
| + |
| + mEditText.setHint(null); |
| + } |
| + |
| + /** |
| + * Hide the label |
| + */ |
| + private void hideLabel(boolean animate) { |
| + if (animate) { |
| + float scale = mEditText.getTextSize() / mLabel.getTextSize(); |
| + ViewCompat.setScaleX(mLabel, 1f); |
| + ViewCompat.setScaleY(mLabel, 1f); |
| + ViewCompat.setTranslationY(mLabel, 0f); |
| + |
| + ViewCompat.animate(mLabel) |
| + .translationY(mLabel.getHeight()) |
| + .setDuration(ANIMATION_DURATION) |
| + .scaleX(scale) |
| + .scaleY(scale) |
| + .setListener(new ViewPropertyAnimatorListenerAdapter() { |
| + @Override |
| + public void onAnimationEnd(View view) { |
| + mLabel.setVisibility(INVISIBLE); |
| + mEditText.setHint(mHint); |
| + } |
| + }) |
| + .setInterpolator(mInterpolator).start(); |
| + } else { |
| + mLabel.setVisibility(INVISIBLE); |
| + mEditText.setHint(mHint); |
| + } |
| + } |
| + |
| + /** |
| + * Helper method to convert dips to pixels. |
| + */ |
| + private int dipsToPix(float dps) { |
| + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dps, |
| + getResources().getDisplayMetrics()); |
| + } |
| +} |