Как мне расширить TextView
, чтобы разрешить рисование текста с эффектом градиента?
Текст с градиентом в Android
Ответ 1
Невозможно расширить TextView для рисования текста с помощью градиента. Однако достичь этого эффекта можно, создав холст и опираясь на него. Сначала нам нужно объявить наш пользовательский элемент интерфейса. В начале нам нужно создать подкласс Layout. В этом случае мы будем использовать BoringLayout, который поддерживает только текст с одной строкой.
Shader textShader=new LinearGradient(0, 0, 0, 20,
new int[]{bottom,top},
new float[]{0, 1}, TileMode.CLAMP);//Assumes bottom and top are colors defined above
textPaint.setTextSize(textSize);
textPaint.setShader(textShader);
BoringLayout.Metrics boringMetrics=BoringLayout.isBoring(text, textPaint);
boringLayout=new BoringLayout(text, textPaint, 0, Layout.Alignment.ALIGN_CENTER,
0.0f, 0.0f, boringMetrics, false);
Затем мы переопределяем onMeasure
и onDraw
:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
setMeasuredDimension((int) textPaint.measureText(text), (int) textPaint.getFontSpacing());
}
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
boringLayout.draw(canvas);
}
Наша реализация onDraw
на данный момент довольно ленива (она полностью игнорирует спецификации измерений!, но пока вы гарантируете, что представление дано достаточное пространство, оно должно работать нормально.
В качестве альтернативы можно было бы наследовать от Canvas
и переопределить метод onPaint
. Если это будет сделано, то, к сожалению, якорь для рисованного текста всегда будет внизу, поэтому мы должны добавить -textPaint.getFontMetricsInt().ascent()
к нашей координате y.
Ответ 2
TextView secondTextView = new TextView(this);
Shader textShader=new LinearGradient(0, 0, 0, 20,
new int[]{Color.GREEN,Color.BLUE},
new float[]{0, 1}, TileMode.CLAMP);
secondTextView.getPaint().setShader(textShader);
Ответ 3
Я использовал верхний ответ (@Taras) с градиентом 5 цветов, но есть проблема: textView выглядит так, как будто я наложил на него белую обложку. Вот мой код и скриншот.
textView = (TextView) findViewById(R.id.main_tv);
textView.setText("Tianjin, China".toUpperCase());
TextPaint paint = textView.getPaint();
float width = paint.measureText("Tianjin, China");
Shader textShader = new LinearGradient(0, 0, width, textView.getTextSize(),
new int[]{
Color.parseColor("#F97C3C"),
Color.parseColor("#FDB54E"),
Color.parseColor("#64B678"),
Color.parseColor("#478AEA"),
Color.parseColor("#8446CC"),
}, null, Shader.TileMode.CLAMP);
textView.getPaint().setShader(textShader);
Через много часов я обнаружил, что мне нужно вызвать textView.setTextColor()
с первым цветом градиента. Тогда скриншот:
Надеюсь помочь кому-нибудь!
Ответ 4
Я свернул библиотеку, которая охватывает оба этих метода. Вы можете создать GradientTextView в XML или просто использовать GradientTextView.setGradient(TextView textView...), чтобы сделать это на обычном объекте TextView.
Ответ 5
Вот он, с поддержкой нескольких строк в качестве одного лайнера. Это должно работать и для кнопок.
Shader shader = new LinearGradient(0,0,0,textView.getLineHeight(),
startColor, endColor, Shader.TileMode.REPEAT);
textView.getPaint().setShader(shader);
Ответ 6
Простым, но несколько ограниченным решением было бы использовать эти атрибуты:
android:fadingEdge="horizontal"
android:scrollHorizontally="true"
Я использовал его на текстовых полях, где я хочу, чтобы они исчезли, если они слишком длинны.
Ответ 7
Вот хороший способ сделать это:
/**
* sets a vertical gradient on the textView paint, so that on its onDraw method, it will use it.
*
* @param viewAlreadyHasSize
* set to true only if the textView already has a size
*/
public static void setVerticalGradientOnTextView(final TextView tv, final int positionsAndColorsResId,
final boolean viewAlreadyHasSize) {
final String[] positionsAndColors = tv.getContext().getResources().getStringArray(positionsAndColorsResId);
final int[] colors = new int[positionsAndColors.length];
float[] positions = new float[positionsAndColors.length];
for (int i = 0; i < positionsAndColors.length; ++i) {
final String positionAndColors = positionsAndColors[i];
final int delimeterPos = positionAndColors.lastIndexOf(':');
if (delimeterPos == -1 || positions == null) {
positions = null;
colors[i] = Color.parseColor(positionAndColors);
} else {
positions[i] = Float.parseFloat(positionAndColors.substring(0, delimeterPos));
String colorStr = positionAndColors.substring(delimeterPos + 1);
if (colorStr.startsWith("0x"))
colorStr = '#' + colorStr.substring(2);
else if (!colorStr.startsWith("#"))
colorStr = '#' + colorStr;
colors[i] = Color.parseColor(colorStr);
}
}
setVerticalGradientOnTextView(tv, colors, positions, viewAlreadyHasSize);
}
/**
* sets a vertical gradient on the textView paint, so that on its onDraw method, it will use it. <br/>
*
* @param colors
* the colors to use. at least one should exist.
* @param tv
* the textView to set the gradient on it
* @param positions
* where to put each color (fraction, max is 1). if null, colors are spread evenly .
* @param viewAlreadyHasSize
* set to true only if the textView already has a size
*/
public static void setVerticalGradientOnTextView(final TextView tv, final int[] colors, final float[] positions,
final boolean viewAlreadyHasSize) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
final TileMode tile_mode = TileMode.CLAMP;
final int height = tv.getHeight();
final LinearGradient lin_grad = new LinearGradient(0, 0, 0, height, colors, positions, tile_mode);
final Shader shader_gradient = lin_grad;
tv.getPaint().setShader(shader_gradient);
}
};
if (viewAlreadyHasSize)
runnable.run();
else
runJustBeforeBeingDrawn(tv, runnable);
}
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
runnable.run();
return true;
}
};
view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
Также, если вы хотите использовать растровое изображение градиента вместо реального, используйте:
/**
* sets an image for the textView <br/>
* NOTE: this function must be called after you have the view have its height figured out <br/>
*/
public static void setBitmapOnTextView(final TextView tv, final Bitmap bitmap) {
final TileMode tile_mode = TileMode.CLAMP;
final int height = tv.getHeight();
final int width = tv.getWidth();
final Bitmap temp = Bitmap.createScaledBitmap(bitmap, width, height, true);
final BitmapShader bitmapShader = new BitmapShader(temp, tile_mode, tile_mode);
tv.getPaint().setShader(bitmapShader);
}
РЕДАКТИРОВАТЬ: Альтернатива runJustBeforeBeingDrawn: fooobar.com/questions/35172/...
Ответ 8
Вот пример для linearlayout, вы можете также использовать этот пример для текстового просмотра, а в исходном коде не будет градиентного кодирования, вы получаете исходный код и добавляете код с этого сайта - http://android-codes-examples.blogspot.com/2011/07/design-linearlayout-or-textview-and-any.html