2010-04-21 22 views

risposta

16

Non sembra possibile estendere TextView per disegnare il testo con una sfumatura. Tuttavia, è possibile ottenere questo effetto creando una tela e disegnandoci sopra. Per prima cosa dobbiamo declare our custom UI element. All'inizio è necessario creare una sottoclasse di Layout. In questo caso, useremo BoringLayout che supporta solo il testo con una sola riga.

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); 

Abbiamo poi sovrascrivere onMeasure e 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); 
} 

La nostra implementazione di onDraw è a questo punto piuttosto pigro (ignora completamente le specifiche di misura !, ma fino a quando si garantisce che la vista è dato spazio sufficiente, dovrebbe funzionare ok

In alternativa, sarebbe possibile ereditare da un Canvas e sovrascrivere il metodo onPaint. Se questo è d uno, quindi sfortunatamente l'ancoraggio per il testo disegnato sarà sempre sul fondo quindi dobbiamo aggiungere -textPaint.getFontMetricsInt().ascent() alla nostra coordinata y.

+0

Ciao, puoi aggiungere qualche altro codice a questa risposta? In particolare, il resto del layout di estensione di BoringLayout classe grazie! – ekatz

+0

Collegamento a BoringLayout che dovrebbe essere incluso nell'SDK – Casebash

3

Una soluzione semplice ma un po 'limitata sarebbe quella di utilizzare questi attributi:

android:fadingEdge="horizontal" 
android:scrollHorizontally="true" 

ho usato su campi di testo dove voglio loro di fade out se ottengono troppo a lungo.

+0

Questo è un bel trucco. Non credo che tu possa fare quel lavoro in verticale? Non c'è nessun androide: scrollVertically proprietà per quanto posso vedere – Casebash

+0

Spiacente, non so come farlo :( – pgsandstrom

104
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); 
+5

+1 per semplificare ed efficace –

+1

Grazie per una soluzione molto semplice :) –

+1

Sul mio dispositivo ICS, questo non sembra funzionare - - è completamente trasparente.Qualcun altro vede la stessa cosa? La vista non è accelerata dall'hardware. –

9

Ho arrotolato una libreria che racchiude entrambi questi metodi. Puoi creare GradientTextView in XML o semplicemente usare GradientTextView.setGradient (TextView textView ...) per farlo su un normale oggetto TextView.

https://github.com/koush/Widgets

1

Ecco un bel modo per farlo:

/** 
* sets a vertical gradient on the textView's 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's 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); 
} 

Inoltre, se si desidera utilizzare una bitmap del gradiente, invece o uno vero, l'uso:

/** 
* 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); 
} 
4

Eccolo con il supporto multilinea come un solo rivestimento. Questo dovrebbe funzionare anche per i pulsanti.

textView.getPaint().setShader(new LinearGradient(0,0,0,textView.getLineHeight(), startColor, endColor, Shader.TileMode.REPEAT));

+0

semplice e funziona eccellente !! –

+0

Funziona, ma per l'altezza si potrebbe voler prendere in considerazione le lettere che vanno al di sotto della linea attuale (come y, g, j, ecc.) E moltiplicare la altezza della linea per 1,10f, ad es. 'textView.getLineHeight() * 1.10f'. –