2010-04-21 12 views

Odpowiedz

16

Nie wydaje się możliwe rozszerzenie TextView w celu narysowania tekstu z gradientem. Efekt ten można jednak osiągnąć, tworząc płótno i rysując na nim. Najpierw musimy declare our custom UI element. W inicjacji musimy stworzyć podklasę o nazwie Layout. W takim przypadku użyjemy BoringLayout, który obsługuje tylko tekst z pojedynczą linią.

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

Następnie zastąpić onMeasure i 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); 
} 

Nasza realizacja onDraw jest w tym momencie dość leniwy (nie jest to całkowicie ignoruje specyfikacje pomiarowych !, ale tak długo, jak zagwarantować, że widok jest biorąc pod uwagę wystarczającą ilość miejsca, powinien on działać poprawnie:

Alternatywnie możliwe byłoby dziedziczenie z Canvas i zastąpienie metody onPaint. jeden, to niestety kotwica dla narysowanego tekstu zawsze będzie na dole, więc musimy dodać -textPaint.getFontMetricsInt().ascent() do naszej współrzędnej y.

+0

Witam, czy możesz dodać trochę kodu do tej odpowiedzi? W szczególności, reszta rozłożonego układu BoringLayout klasa dziękuję! – ekatz

+0

Łącze do BoringLayout, które powinno być zawarte w SDK – Casebash

3

Prosty, ale nieco ograniczone rozwiązaniem byłoby wykorzystać te atrybuty:

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

Użyłem go na pól tekstowych, gdzie chcę, żeby znikną, jeśli się zbyt długo.

+0

To jest fajny hack. Nie sądzę, że możesz wykonać tę pracę pionowo? Nie ma żadnej cechy android: scrollVertically o ile widzę – Casebash

+0

Niestety, nie wiem jak to zrobić :( – 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 dla ułatwienia i wydajności –

+1

Dzięki za bardzo proste rozwiązanie :) –

+1

Na moim urządzeniu ICS, nie wydaje się działać - - jest całkowicie przezroczysty.Ktoś jeszcze widział to samo? Widok nie jest przyspieszany sprzętowo. –

9

Zwołałem bibliotekę, która obejmuje obie te metody. Możesz utworzyć GradientTextView w XML lub po prostu użyć GradientTextView.setGradient (TextView textView ...), aby zrobić to na zwykłym obiekcie TextView.

https://github.com/koush/Widgets

1

Oto dobry sposób, aby to zrobić:

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

Ponadto, jeśli chcesz użyć bitmapy gradientu, zamiast albo prawdziwy, zastosowanie:

/** 
* 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

Tutaj jest obsługa wielolinii jako jednej liniowej. Powinno to również działać w przypadku przycisków.

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

+0

proste i działa doskonale !! –

+0

Działa, ale na wysokość możesz wziąć pod uwagę litery, które znajdują się poniżej rzeczywistej linii (np. Y, g, j, itp.) I pomnożyć wysokość linii przez 1,10f, np. 'textView.getLineHeight() * 1.10f'. –

Powiązane problemy