среда, 6 июля 2011 г.

Строим диаграмму в Android-приложении

Допустим, вы решили сделать виджет, который будет отображать текущий курс валют. Вы идёте на любой открытый источник, берёте данные и выводите клиенту. Ничего сложного. Но и ничего особенного - таких виджетов миллион. Вы идёте дальше: принимаете решение хранить полученные данные за предыдущие дни и отображать клиенту информацию в виде диаграммы. Вот тут-то мы и сталкиваемся с реализацией 2D-графики в Android.
Чтобы создать объект изображения, нужно переопределить View. Назовём наш объект ChartView:

  1. public class ChartView extends View {
  2.   private int width = 10;
  3.   ArrayList<ChartItem> charts;
  4.   int max = 0;
  5.   ShapeDrawable background;
  6.   
  7.   public ChartView(Context context, ArrayList<ChartItem> charts, int top, int bgColor) {
  8.     super(context);
  9.     Resources res = getResources();
  10.     int ws = res.getDisplayMetrics().widthPixels;
  11.     
  12.     ArrayList<ChartItem> gencharts = new ArrayList<ChartItem>();
  13.     for (ChartItem ci : charts) {
  14.       int[] points = scaleTo(ci.getPoints(), top);
  15.       ArrayList<ShapeDrawable> myDraws = new ArrayList<ShapeDrawable>();
  16.       for (int i=0; i<points.length; i++) {
  17.         int h = points[i];
  18.         ShapeDrawable mDrawable = new ShapeDrawable(new RectShape());
  19.         mDrawable.getPaint().setColor(ci.getColor());
  20.         mDrawable.getPaint().setAlpha(ci.getAlpha());
  21.         mDrawable.setBounds(width*i, top-h, width*(i+1), top);
  22.         mDrawable.getPaint().setShader(new LinearGradient(width*i, top-h, width*(i+1), top-h, ci.getColor(), Color.WHITE, Shader.TileMode.REPEAT));
  23.         myDraws.add(mDrawable);
  24.       }
  25.       ci.setDraws(myDraws);
  26.       gencharts.add(ci);
  27.     }
  28.     this.charts = gencharts;
  29.     background = new ShapeDrawable(new RectShape());
  30.     background.setBounds(0, 0, ws, top);
  31.     background.getPaint().setColor(bgColor);
  32.   }
  33.  
  34.   @Override
  35.   protected void onDraw(Canvas canvas) {
  36.     background.draw(canvas);
  37.     for (ChartItem ci : charts) {
  38.       ArrayList<ShapeDrawable> draws = ci.getDraws();
  39.       for (int i=0; i<draws.size(); i++) {
  40.         ShapeDrawable d = draws.get(i);
  41.         d.draw(canvas);
  42.       }
  43.     }
  44.   }
  45.   
  46.   private int getMax(int[] ii) {
  47.     for (int i : ii) {
  48.       if (max<i) max = i;
  49.     }
  50.     return max;
  51.   }
  52.   
  53.   private int[] scaleTo(int[] ii, int vmax) {
  54.     int[] scaled = new int[ii.length];
  55.     double k = (double) vmax / getMax(ii);
  56.     for(int i=0; i<ii.length; i++) {
  57.       scaled[i] = (int) (ii[i]*k);
  58.     }
  59.     return scaled;
  60.   }
  61. }
Рассмотрим наш класс подробнее.
В конструктор обязательно нужно передавать Context - его мы сразу же отдадим в конструктор предка. Кроме этого в конструктор мы передаём массив объектов ChartItem, которые представляют данные для каждой из диаграмм, которые мы собираемся построить на нашем изображении. Следующие два параметра общие для всех диаграмм на изображении: максимальная высота и фон. 
В строках 9-10 получаем текущую ширину экрана, чтобы полностью закрасить её фоном. 
Все графические примитивы создаются как объекты ShapeDrawable. В конструкторе мы устанавливаем параметры для прямоугольников, составляющих столбцы диаграмм: ширину, высоту, цвет. Делаем их симпатичнее с помощью градиента (строка 22). Все созданные объекты должны быть нарисованы на канве, для чего переопределяем метод protected void onDraw(Canvas canvas) и в нём для всех ShapeDrawable вызываем метод draw(canvas) (строки 35-44). Делать это нужно в правильном порядке: следующий объект будет рисоваться поверх предыдущего. 
Определённая сложность есть в расчёте высоты столбцов диаграммы: нужно чтобы они поместились в максимальную высоту и при этом сохранили пропорции. Это решаем с помощью метода  private int[] scaleTo(int[] ii, int vmax) в который передаём массив данных для конкретной диаграммы и максимальную высоту. На выходе получаем уже масштабированный массив (строки 53-60). 
Добавляя в setContentView вашего Activity созданный экземпляр СhartView, получаете результат вроде того, что на картинке. В данном случае выбран серый фон и две диаграммы с прозрачностью 50% синего и зелёного цвета. 

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

  1. Круто, но где описание класса ChartItem?

    ОтветитьУдалить
  2. На самом деле круто. Но сложновато без разъяснения всех строк и команд. Если бы автор расписал все от А до Я то тогда бы эта статья имела море просмотров.

    ОтветитьУдалить