Bug 997447 - Add FloatingHintEditText widget. r=lucasr

This commit is contained in:
Brian Nicholson 2014-05-23 09:50:17 -07:00
parent 2f35edcb94
commit 11b3e03e79
8 changed files with 188 additions and 0 deletions

View File

@ -415,6 +415,7 @@ gbjar.sources += [
'widget/DoorHanger.java',
'widget/EllipsisTextView.java',
'widget/FaviconView.java',
'widget/FloatingHintEditText.java',
'widget/FlowLayout.java',
'widget/GeckoActionProvider.java',
'widget/GeckoPopupMenu.java',

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Holo blue for focus -->
<item android:state_focused="true" android:color="@color/text_color_hint_floating_focused" />
<!-- Default gray for all other states -->
<item android:color="@color/text_color_hint"/>
</selector>

View File

@ -60,6 +60,7 @@
<item name="android:actionModeCopyDrawable">@drawable/ab_copy</item>
<item name="android:actionModePasteDrawable">@drawable/ab_paste</item>
<item name="android:actionModeSelectAllDrawable">@drawable/ab_select_all</item>
<item name="floatingHintEditTextStyle">@style/FloatingHintEditText</item>
</style>
</resources>

View File

@ -244,5 +244,9 @@
<attr name="ellipsizeAtLine" format="integer"/>
</declare-styleable>
<declare-styleable name="FloatingHintEditText">
<attr name="floatingHintEditTextStyle" format="reference" />
</declare-styleable>
</resources>

View File

@ -50,6 +50,7 @@
<!-- Hint colors -->
<color name="text_color_hint">#666666</color>
<color name="text_color_hint_inverse">#7F828A</color>
<color name="text_color_hint_floating_focused">#33b5e5</color>
<!-- Highlight colors -->
<color name="text_color_highlight">#FF9500</color>

View File

@ -684,4 +684,8 @@
<item name="android:minHeight">@dimen/menu_item_row_height</item>
</style>
<style name="FloatingHintEditText" parent="android:style/Widget.EditText">
<item name="android:paddingTop">0dp</item>
</style>
</resources>

View File

@ -92,6 +92,7 @@
<item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
<item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
<item name="menuItemShareActionButtonStyle">@style/Widget.MenuItemSecondaryActionBar</item>
<item name="floatingHintEditTextStyle">@style/FloatingHintEditText</item>
</style>
<style name="Gecko.Preferences" parent="GeckoPreferencesBase"/>

View File

@ -0,0 +1,168 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.EditText;
import org.mozilla.gecko.R;
public class FloatingHintEditText extends EditText {
private static enum Animation { NONE, SHRINK, GROW }
private static final float HINT_SCALE = 0.6f;
private static final int ANIMATION_STEPS = 6;
private final Paint floatingHintPaint = new Paint();
private final ColorStateList floatingHintColors;
private final ColorStateList normalHintColors;
private final int defaultFloatingHintColor;
private final int defaultNormalHintColor;
private boolean wasEmpty;
private int animationFrame;
private Animation animation = Animation.NONE;
public FloatingHintEditText(Context context) {
this(context, null);
}
public FloatingHintEditText(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.floatingHintEditTextStyle);
}
public FloatingHintEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
floatingHintColors = getResources().getColorStateList(R.color.floating_hint_text);
normalHintColors = getHintTextColors();
defaultFloatingHintColor = floatingHintColors.getDefaultColor();
defaultNormalHintColor = normalHintColors.getDefaultColor();
wasEmpty = TextUtils.isEmpty(getText());
}
@Override
public int getCompoundPaddingTop() {
final FontMetricsInt metrics = getPaint().getFontMetricsInt();
final int floatingHintHeight = (int) ((metrics.bottom - metrics.top) * HINT_SCALE);
return super.getCompoundPaddingTop() + floatingHintHeight;
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
final boolean isEmpty = TextUtils.isEmpty(getText());
// The empty state hasn't changed, so the hint stays the same.
if (wasEmpty == isEmpty) {
return;
}
wasEmpty = isEmpty;
// Don't animate if we aren't visible.
if (!isShown()) {
return;
}
if (isEmpty) {
animation = Animation.GROW;
// The TextView will show a hint since the field is empty, but since we're animating
// from the floating hint, we don't want the normal hint to appear yet. We set it to
// transparent here, then restore the hint color after the animation has finished.
setHintTextColor(Color.TRANSPARENT);
} else {
animation = Animation.SHRINK;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (TextUtils.isEmpty(getHint())) {
return;
}
final boolean isAnimating = (animation != Animation.NONE);
// The large hint is drawn by Android, so do nothing.
if (!isAnimating && TextUtils.isEmpty(getText())) {
return;
}
final Paint paint = getPaint();
final float hintPosX = getCompoundPaddingLeft() + getScrollX();
final float normalHintPosY = getBaseline();
final float floatingHintPosY = normalHintPosY + paint.getFontMetricsInt().top + getScrollY();
final float normalHintSize = getTextSize();
final float floatingHintSize = normalHintSize * HINT_SCALE;
final int[] stateSet = getDrawableState();
final int floatingHintColor = floatingHintColors.getColorForState(stateSet, defaultFloatingHintColor);
floatingHintPaint.set(paint);
// If we're not animating, we're showing the floating hint, so draw it and bail.
if (!isAnimating) {
drawHint(canvas, floatingHintSize, floatingHintColor, hintPosX, floatingHintPosY);
return;
}
// We are animating, so draw the linearly interpolated frame.
final int normalHintColor = normalHintColors.getColorForState(stateSet, defaultNormalHintColor);
if (animation == Animation.SHRINK) {
drawAnimationFrame(canvas, normalHintSize, floatingHintSize,
hintPosX, normalHintPosY, floatingHintPosY, normalHintColor, floatingHintColor);
} else {
drawAnimationFrame(canvas, floatingHintSize, normalHintSize,
hintPosX, floatingHintPosY, normalHintPosY, floatingHintColor, normalHintColor);
}
animationFrame++;
if (animationFrame == ANIMATION_STEPS) {
// After the grow animation has finished, restore the normal TextView hint color that we
// removed in our onTextChanged listener.
if (animation == Animation.GROW) {
setHintTextColor(normalHintColors);
}
animation = Animation.NONE;
animationFrame = 0;
}
invalidate();
}
private void drawAnimationFrame(Canvas canvas, float fromSize, float toSize,
float hintPosX, float fromY, float toY, int fromColor, int toColor) {
final float textSize = lerp(fromSize, toSize);
final float hintPosY = lerp(fromY, toY);
final int color = Color.rgb((int) lerp(Color.red(fromColor), Color.red(toColor)),
(int) lerp(Color.green(fromColor), Color.green(toColor)),
(int) lerp(Color.blue(fromColor), Color.blue(toColor)));
drawHint(canvas, textSize, color, hintPosX, hintPosY);
}
private void drawHint(Canvas canvas, float textSize, int color, float x, float y) {
floatingHintPaint.setTextSize(textSize);
floatingHintPaint.setColor(color);
canvas.drawText(getHint().toString(), x, y, floatingHintPaint);
}
private float lerp(float from, float to) {
final float alpha = (float) animationFrame / (ANIMATION_STEPS - 1);
return from * (1 - alpha) + to * alpha;
}
}