Skip to content

Instantly share code, notes, and snippets.

@Palatis
Last active June 24, 2017 01:00
Show Gist options
  • Save Palatis/6282ec4441c475906e6f4279ba995e78 to your computer and use it in GitHub Desktop.
Save Palatis/6282ec4441c475906e6f4279ba995e78 to your computer and use it in GitHub Desktop.
MultilineHintTextInputLayout :-D
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MultilineHintTextInputLayout">
<!-- the "label" to be displayed when the child is focused -->
<!-- will be using the hint if not set. -->
<attr name="mlhtil_label" format="string" />
<attr name="android:hint" format="string" />
</declare-styleable>
</resources>
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.StringRes;
import android.support.design.widget.TextInputLayout;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.EditText;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import tw.com.wusa.smartwatch.R;
/**
* A TextInputLayout that draws multiline text.
* <p>
* api18+ is required, because versions below use its own texture for drawing, and it calls
* {@code new Canvas()} which is too difficult to git rid of.
*/
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class MultilineHintTextInputLayout extends TextInputLayout {
private static final String TAG = "MlHintTextInputLayout";
private CanvasWrapper mCanvasWrapper = new CanvasWrapper();
private Object mCollapsingTextHelper = null;
private Method mCollapsingTextHelper_draw = null;
private Field mHintEnabledField = null;
private Field mCollapsingTextHelper_mTextToDraw = null;
private boolean mFocused = false;
private CharSequence mHint = null;
private CharSequence mHintLabel = null;
public MultilineHintTextInputLayout(@NonNull Context context) {
this(context, null);
}
public MultilineHintTextInputLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MultilineHintTextInputLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultilineHintTextInputLayout, defStyleAttr, 0);
try {
setHint(a.getText(R.styleable.MultilineHintTextInputLayout_android_hint));
mHintLabel = a.getText(R.styleable.MultilineHintTextInputLayout_mlhtil_label);
} finally {
a.recycle();
}
try {
mHintEnabledField = TextInputLayout.class.getDeclaredField("mHintEnabled");
mHintEnabledField.setAccessible(true);
final Field field = TextInputLayout.class.getDeclaredField("mCollapsingTextHelper");
field.setAccessible(true);
mCollapsingTextHelper = field.get(this);
final Class klassCollapsingTextHelper = Class.forName("android.support.design.widget.CollapsingTextHelper");
//noinspection unchecked
mCollapsingTextHelper_draw = klassCollapsingTextHelper.getDeclaredMethod("draw", Canvas.class);
mCollapsingTextHelper_mTextToDraw = klassCollapsingTextHelper.getDeclaredField("mTextToDraw");
mCollapsingTextHelper_mTextToDraw.setAccessible(true);
} catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException | ClassNotFoundException ex) {
mCanvasWrapper = null;
mCollapsingTextHelper = null;
mCollapsingTextHelper_draw = null;
mHintEnabledField = null;
mCollapsingTextHelper_mTextToDraw = null;
}
}
/**
* {@inheritDoc}
*/
@Override
public void setHint(@Nullable CharSequence hint) {
mHint = hint;
super.setHint(hint);
}
/**
* set the label to be displayed when the hint shifts to the top
*
* @param label label id from string resources
* @see #setLabel(int)
* @see #getLabel()
* @see #getHint()
* @see #setHint(CharSequence)
*/
public void setLabel(@StringRes int label) {
mHintLabel = getResources().getText(label);
postInvalidateOnAnimation();
}
/**
* set the label to be displayed when the hint shifts to the top
*
* @param label the label, null to use the same for both.
* @see #setLabel(int)
* @see #getLabel()
* @see #getHint()
* @see #setHint(CharSequence)
*/
public void setLabel(@Nullable CharSequence label) {
mHintLabel = label;
postInvalidateOnAnimation();
}
/**
* get the label that will be displayed when the hint shifts to the top
*
* @return the label, null if using the the same for both.
* @see #setLabel(int)
* @see #setLabel(CharSequence)
* @see #getHint()
* @see #setHint(CharSequence)
*/
@Nullable
public CharSequence getLabel() {
return mHintLabel;
}
@Override
public void draw(Canvas canvas) {
if (mCollapsingTextHelper == null) {
super.draw(canvas);
return;
}
try {
final boolean hint = (boolean) mHintEnabledField.get(this);
if (hint)
mHintEnabledField.set(this, false);
super.draw(canvas);
if (hint) {
if (mHintLabel == null) {
mCollapsingTextHelper_mTextToDraw.set(mCollapsingTextHelper, mHint);
} else {
final EditText child = getEditText();
if (child.isFocused() || !TextUtils.isEmpty(child.getText()))
mCollapsingTextHelper_mTextToDraw.set(mCollapsingTextHelper, mHintLabel);
else
mCollapsingTextHelper_mTextToDraw.set(mCollapsingTextHelper, mHint);
}
mHintEnabledField.set(this, true);
mCanvasWrapper.setCanvas(canvas);
mCollapsingTextHelper_draw.invoke(mCollapsingTextHelper, mCanvasWrapper);
mCanvasWrapper.setCanvas(null); // do not hold a ref
}
} catch (IllegalAccessException | InvocationTargetException ex) {
mCanvasWrapper = null;
mCollapsingTextHelper = null;
mCollapsingTextHelper_draw = null;
mHintEnabledField = null;
mCollapsingTextHelper_mTextToDraw = null;
// failed, try again with super only.
postInvalidate();
}
}
private static int nextNewLineAt(@NonNull CharSequence text, int start, int end) {
while (start < end) {
if (text.charAt(start) == '\n')
return start;
++start;
}
return end;
}
private static final class CanvasWrapper extends Canvas {
private Canvas mCanvas = null;
CanvasWrapper() {
super();
}
void setCanvas(Canvas canvas) {
mCanvas = canvas;
}
// only these one is used
@Override
public void drawText(@NonNull CharSequence text, int start, int end, float x, float y, @NonNull Paint paint) {
final float spacing = paint.getFontSpacing();
while (start < end) {
final int newline = nextNewLineAt(text, start, end);
mCanvas.drawText(text, start, newline, x, y, paint);
y += spacing;
start = newline + 1;
}
}
@Override
public boolean isHardwareAccelerated() {
// if we don't check this it will crash in super()...
return mCanvas != null ? mCanvas.isHardwareAccelerated() : super.isHardwareAccelerated();
}
@Override
public int save() {
return mCanvas.save();
}
@Override
public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
mCanvas.drawRect(left, top, right, bottom, paint);
}
@Override
public void scale(float sx, float sy) {
mCanvas.scale(sx, sy);
}
@Override
public void restoreToCount(int saveCount) {
mCanvas.restoreToCount(saveCount);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment