【问题标题】:Text with gradient in AndroidAndroid中带有渐变的文本
【发布时间】:2010-04-21 05:43:33
【问题描述】:

如何扩展TextView 以允许绘制具有渐变效果的文本?

【问题讨论】:

    标签: android user-interface


    【解决方案1】:
    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);
    

    【讨论】:

    • 在我的 ICS 设备上,这似乎不起作用——它是完全透明的。其他人看到同样的事情吗?该视图未经过硬件加速。
    • @Steve Prentice: 可能是 y1 = 20 太短了,所以你看不到任何渐变,试试: new LinearGradient(0,0,0,secondTextView.getPaint().getTextSize(),.. ..) - 也许它会解决你的问题
    • 有没有办法用 XML 代替?
    • 修复这个:Shader.TileMode.CLAMP
    • @StevePrentice:我在 OS 9.0 上也遇到过这个问题。另一个原因可能是您需要为渐变颜色数组指定 alpha 值。例如,将其中一种颜色设置为 0xFF148CDC 而不是 0x148CDC。
    【解决方案2】:

    我使用了5种颜色渐变的最佳答案(@Taras),但是有一个问题: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()。然后截图:

    希望能帮助别人!

    【讨论】:

    • 为什么要从左上角到右下角渐变?水平渐变不应该是LinearGradient(0, 0, width, 0, ...吗?
    • @fdermishin :我们从 Instagram 复制了这个渐变效果。 ?
    • 嗨,你能帮我了解一下 spanableString -> ImageSpan with gradient:stackoverflow.com/questions/70842940/…
    【解决方案3】:

    似乎无法扩展 TextView 以使用渐变绘制文本。但是,可以通过创建画布并在其上绘图来实现此效果。首先我们需要declare our custom UI element。在初始化中,我们需要创建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);
    

    然后我们覆盖onMeasureonDraw

    @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 坐标中。

    【讨论】:

      【解决方案4】:

      这里是单行支持多行。这也适用于按钮。

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

      【讨论】:

      • 有效,但对于高度,您可能需要考虑低于实际行的字母(如 y、g、j 等)并将行高乘以 1.10f,例如textView.getLineHeight() * 1.10f.
      • 在 Android 6.0 上完美运行。渐变将是垂直的。
      【解决方案5】:

      我已经建立了一个包含这两种方法的库。您可以在 XML 中创建 GradientTextView 或仅使用 GradientTextView.setGradient(TextView textView...) 在常规 TextView 对象上执行此操作。

      https://github.com/koush/Widgets

      【讨论】:

      【解决方案6】:

      一个简单但有些有限的解决方案是使用这些属性:

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

      我已经在文本字段上使用了它,如果它们太长,我希望它们淡出。

      【讨论】:

      • 这是一个不错的技巧。我不认为你可以让它垂直工作吗?据我所知,没有任何 android:scrollVertically 属性
      • 对不起,我不知道该怎么做 :(
      【解决方案7】:

      Kotlin + 协程版本。

      设置垂直渐变的扩展:

      private fun TextView.setGradientTextColor(vararg colorRes: Int) {
          val floatArray = ArrayList<Float>(colorRes.size)
          for (i in colorRes.indices) {
              floatArray.add(i, i.toFloat() / (colorRes.size - 1))
          }
          val textShader: Shader = LinearGradient(
              0f,
              0f,
              0f,
              this.height.toFloat(),
              colorRes.map { ContextCompat.getColor(requireContext(), it) }.toIntArray(),
              floatArray.toFloatArray(),
              TileMode.CLAMP
          )
          this.paint.shader = textShader
      }
      

      暂停扩展。您需要等待视图更改其高度。

      suspend fun View.awaitLayoutChange() = suspendCancellableCoroutine<Unit> { cont ->
      val listener = object : View.OnLayoutChangeListener {
          override fun onLayoutChange(
              view: View?,
              left: Int,
              top: Int,
              right: Int,
              bottom: Int,
              oldLeft: Int,
              oldTop: Int,
              oldRight: Int,
              oldBottom: Int
          ) {
              view?.removeOnLayoutChangeListener(this)
              cont.resumeWith(Result.success(Unit))
          }
      }
      
      addOnLayoutChangeListener(listener)
      cont.invokeOnCancellation { removeOnLayoutChangeListener(listener) }
      

      }

      及用法:

      lifecycle.coroutineScope.launch {
              binding.tvAmount.text = "Dumb text"
              binding.tvAmount.awaitLayoutChange()
              binding.tvAmount.setGradientTextColor(
                  R.color.yellow,
                  R.color.green
              )
          }
      

      【讨论】:

        【解决方案8】:

        对于 Kotlin:

        val paint: TextPaint = textView.paint
            val width: Float = paint.measureText(holder.langs.text.toString())
        
            val textShader: Shader = LinearGradient(0f, 0f, width, holder.langs.textSize, intArrayOf(
                    Color.parseColor("#8913FC"),
                    Color.parseColor("#00BFFC")), null, Shader.TileMode.CLAMP)
            holder.langs.paint.shader = textShader
        

        【讨论】:

          【解决方案9】:

          这是一个很好的方法:

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

          另外,如果您希望使用渐变的位图,而不是真实的,请使用:

          /**
           * 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 的替代方案:https://stackoverflow.com/a/28136027/878126

          【讨论】:

          【解决方案10】:

          这是我的解决方法。使用文本跨度实现。 screenshot

          class LinearGradientForegroundSpan extends CharacterStyle implements UpdateAppearance {
              private int startColor;
              private int endColor;
              private int lineHeight;
          
              public LinearGradientForegroundSpan(int startColor, int endColor, int lineHeight) {
                  this.startColor = startColor;
                  this.endColor = endColor;
                  this.lineHeight = lineHeight;
              }
          
              @Override
              public void updateDrawState(TextPaint tp) {
                  tp.setShader(new LinearGradient(0, 0, 0, lineHeight,
                          startColor, endColor, Shader.TileMode.REPEAT));
              }
          }
          

          为渐变文本设置样式。

              SpannableString gradientText = new SpannableString("Gradient Text");
              gradientText.setSpan(new LinearGradientForegroundSpan(Color.RED, Color.LTGRAY, textView.getLineHeight()),
                      0, gradientText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
              SpannableStringBuilder sb = new SpannableStringBuilder();
              sb.append(gradientText);
              sb.append(" Normal Text");
              textView.setText(sb);
          

          【讨论】:

          • 你好,你有 ImageSpan 的解决方案吗
          【解决方案11】:

          我已经结合了这个线程的答案并制作了一个轻量级库。您可以将它与 gradle 实现一起使用,或者只需将其添加到源代码中即可使用所需的文件。

          https://github.com/veeyaarVR/SuperGradientTextView

          【讨论】:

          • 完美的解决方案,在 TextView 上应用渐变...
          【解决方案12】:

          对我有用的解决方案是在应用任何着色器之前应用文本颜色。正如问题的作者所发布的那样:

          几个小时后,我发现我需要使用渐变的第一种颜色调用 textView.setTextColor()。然后截图:

          例如,首先将白色设置为文本颜色。然后我们可以应用着色器,它将应用在白色之上,这样我们就可以得到想要的渐变颜色。

          【讨论】:

            【解决方案13】:

            这是一个线性布局的例子,你也可以把这个例子用于 textview,而且在源代码中不会有渐变编码,你可以从那个站点本身获取源代码并添加代码 - http://android-codes-examples.blogspot.com/2011/07/design-linearlayout-or-textview-and-any.html

            【讨论】:

              【解决方案14】:

              我找到了无需 TextView 类扩展的方法。

              class MainActivity : AppCompatActivity() {
                  private val textGradientOnGlobalLayoutListener = object: ViewTreeObserver.OnGlobalLayoutListener {
                      override fun onGlobalLayout() {
                          textGradient.paint.shader = LinearGradient(0f, 0f,
                                  textGradient.width.toFloat(),
                                  textGradient.height.toFloat(),
                                  color0, color1, Shader.TileMode.CLAMP)
                          textGradient.viewTreeObserver.removeOnGlobalLayoutListener(this)
                      }
                  }
                  private val textGradient by lazy {
                      findViewById<TextView>(R.id.text_gradient)
                  }
                  private val color0 by lazy {
                      ContextCompat.getColor(applicationContext, R.color.purple_200)
                  }
                  private val color1 by lazy {
                      ContextCompat.getColor(applicationContext, R.color.teal_200)
                  }
              
                  override fun onCreate(savedInstanceState: Bundle?) {
                      super.onCreate(savedInstanceState)
                      setContentView(R.layout.activity_main)
                      textGradient.viewTreeObserver.addOnGlobalLayoutListener(textGradientOnGlobalLayoutListener)
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2018-07-25
                • 2010-12-18
                • 1970-01-01
                • 2013-02-11
                • 1970-01-01
                • 2021-01-19
                • 1970-01-01
                • 2019-08-19
                相关资源
                最近更新 更多