воскресенье, 24 февраля 2013 г.

Кастомизация интерфейса: Градиентный TextView

Кастомизация интерфейса: Градиентный TextView
Сегодня мы посмотрим как можно добавлять к элементам новые свойства на примере TextView, покрасив его в градиент.



Практика


Чтобы добавить новые свойства к элементу, необходимо их описать как атрибуты в xml-файле values/attrs.xml.
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="GradientTextView">
        <attr name="colorStartGradient" format="integer" />
        <attr name="colorEndGradient" format="integer" />
    </declare-styleable>
</resources>

Опишем цвета для нашего градиента (colors.xml):
<?xml version="1.0" encoding="utf-8"?>
<resources> 
       <color name="textview_start_gradient">#ffcccccc</color>
       <color name="textview_end_gradient">#ff56a9c7</color>
</resources>

Теперь разработаем новый класс для нашего элемента. В качестве атрибутов этого класса выступают начальный и конечный цвет градиента. В конструкторе класса получим атрибуты из xml-файла. Градиент устанавливается с помощью метода setShader(). В качестве градиента добавим линейный градиент.
Source code (GradientTextView.java):
public class GradientTextView extends TextView {

 private int colorStartGradient, colorEndGradient;
 
 public GradientTextView(Context context, AttributeSet attrs) {
  super(context, attrs);

  TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.GradientTextView);
  colorStartGradient = a.getColor(R.styleable.GradientTextView_colorStartGradient, -2);
  colorEndGradient = a.getColor(R.styleable.GradientTextView_colorEndGradient, -2);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if (colorStartGradient != -2 && colorEndGradient != -2)
  { 
   getPaint().setShader(
     new LinearGradient(0, 0, 0, getHeight(), colorStartGradient,
       colorEndGradient, TileMode.MIRROR)); 
  }
  super.onDraw(canvas);
 }

}

Теперь добавим наш элемент в main.xml. Чтобы добавить новые свойства, необходимо прописать следующую строку: xmlns:app="http://schemas.android.com/apk/res/org.snowpard.proects.sixteen".

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/org.snowpard.proects.sixteen"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center" >

    <org.snowpard.proects.sixteen.GradientTextView
        android:id="@+id/gradient_textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/app_name"
        android:textSize="35sp"
        app:colorEndGradient="@color/textview_end_gradient"
        app:colorStartGradient="@color/textview_start_gradient" />

</RelativeLayout>

Тень


Если для TextView необходимо добавить тень, то обычный способ (свойства в xml) даст неправильный результат, так как setShader изменит цвет всего элемента TextView. Таким образом, установленный цвет тени в xml не отобразится. Чтобы добавить тень к нашему элементу, необходимо выполнить следующие действия: 
  • Добавить новый цвет в color.xml:
<color name="textview_shadow">#ff674125</color>
  • Описать новое свойство для GradientTextView в attrs.xml:
<attr name="colorShadowGradient" format="integer" />
  • Добавить свойство тени в main.xml
app:colorShadowGradient="@color/textview_shadow"
  • Изменить класс GradientTextView для работы с тенью. Идея заключается в том, чтобы отрисовать элемент частями: сначала тень, затем сам элемент.
public class GradientTextView extends TextView {

 private int colorStartGradient, colorEndGradient, colorShadow;
 
 public GradientTextView(Context context, AttributeSet attrs) {
  super(context, attrs);

  TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.GradientTextView);
  colorShadow = a.getColor(R.styleable.GradientTextView_colorShadowGradient, -2);
  colorStartGradient = a.getColor(R.styleable.GradientTextView_colorStartGradient, -2);
  colorEndGradient = a.getColor(R.styleable.GradientTextView_colorEndGradient, -2);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if (colorShadow != -2)
  {
   getPaint().setShadowLayer(3, 3, 5, colorShadow); 
   getPaint().setShader(null);
   super.onDraw(canvas);
  }
  if (colorStartGradient != -2 && colorEndGradient != -2)
  {
   getPaint().clearShadowLayer();
   getPaint().setShader(
     new LinearGradient(0, 0, 0, getHeight(), colorStartGradient,
       colorEndGradient, TileMode.MIRROR)); 
  }
  super.onDraw(canvas);
 }

}

Ссылки

  • Исходные коды данного проекта можно скачать отсюда: zip

4 комментария:

  1. Как раз искал, как это сделать, спасибо за детальное объяснение!
    Возможно ли добавить drawableGradient вместо colorStartGradient/colorEndGradient и добавлять сам градиент?

    ОтветитьУдалить
    Ответы
    1. Хороший вопрос. Пытался сделать. К сожалению не получилось. Возник вопрос как из Drawable перейти к Shader. Пока ответ - никак :(

      Удалить
    2. Да, тоже над этим задумался.
      Немного поработал с Вашим классом и заметил такие вещи:
      - если один из цветов градиента белый, то логика не срабатывает ( в onDraw такой цвет будет равен -1 - дефолтному значению)
      - поддержка теней :) это уже как дополнительное расширение.

      В любом случае очень своевременный туториал, спасибо большое!

      Удалить
    3. Спасибо за замечания! Исправил и дополнил статью.
      С тенью есть косяк один =) Если нужна тень, цвет которой не такой же градиент, то через свойства TextView в xml (android:shadowColor) установить не получится. Поскольку setShader его изменить на градиент. Статью дополнил, как можно сделать тень другого цвета.

      Удалить