среда, 18 апреля 2012 г.

4. Представление

В предыдущей статье мы разработали Модель нашей головоломки. Для того чтобы отобразить текущее состояние Модели на устройстве, необходимо создать Представление.

4. Представление


Создадим класс MainView, производный от View:

package com.whiterabbit.tags;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.View;

public class MainView extends View {

    private Model model;
    private int maxX = 10000;
    private int maxY = 10000;

    public MainView(Context context) {
        super(context);
        model = new Model();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((MeasureSpec.getMode(widthMeasureSpec)== MeasureSpec.UNSPECIFIED) ||
            (MeasureSpec.getMode(heightMeasureSpec)== MeasureSpec.UNSPECIFIED)) {
            setMeasuredDimension(maxX, maxY);
        } else {
            int szX = MeasureSpec.getSize(widthMeasureSpec);
            int szY = MeasureSpec.getSize(heightMeasureSpec);
            if (MeasureSpec.getMode(widthMeasureSpec)== MeasureSpec.AT_MOST) {
                if (szX > maxX) {
                    szX = maxX;
                }
            }
            if (MeasureSpec.getMode(heightMeasureSpec)== MeasureSpec.AT_MOST) {
                if (szY > maxY) {
                    szY = maxY;
                }
            }
            setMeasuredDimension(szX, szY);
            model.setSizes(szX, szY);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);
        // TODO: Draw Model
    }
   
}


В класс Model добавим члены и методы, необходимые для пересчета логических координат в экранные:

package com.whiterabbit.tags;

...
public class Model {


    ...
    private int physX;
    private int physY;
    private int marginX = 0;
    private int marginY = 0;
    private int tagSize;

    ...
    public void setSizes(int x, int y) {
        physX = x;
        physY = y;
        int szX = physX / sizeX;
        int szY = physY / sizeY;
        if (szX < szY) {
            tagSize = szX;
            marginY = (physY - tagSize * sizeY) / 2;
            marginX = 0;
        }
        if (szX > szY) {
            tagSize = szY;
            marginY = 0;
            marginX = (physX - tagSize * sizeX) / 2;
        }
    }
}


В класс, производный от Activity (автоматически созданный при создании проекта) добавляем следующий код:

package com.whiterabbit.tags;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

public class PuzzleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        LinearLayout.LayoutParams containerParams =
                new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT, 0.0F);
        LinearLayout root = new LinearLayout(this);
        root.setOrientation(LinearLayout.VERTICAL);
        root.setBackgroundColor(Color.LTGRAY);
        root.setLayoutParams(containerParams);
        View view = new MainView(this);
        root.addView(view);
        setContentView(root);
   }
}


Здесь мы создаем Vertical Layout и добавляем в него наше Представление (MainView). Размером MainView на экране управляет метод MainView.onMeasure, автоматически вызываемый Layout-ом при размещении Представления на экране. При вызове метода с параметрами UNSPECIFIED мы передаем в качестве предполагаемых координат некие значения, ограничивающие максимальные размеры при отображении View на экране, требуя, тем самым, выделить нам все доступное место для отображения View заданного размера.

Произведя необходимые вычисления, Layout вызывает метод onMeasure с параметрами AT_MOST. Мы принимаем максимально возможные размеры методом MeasureSpec.getSize и если они не превышают ограничений по максимальным размерам нашего View, передаем их в setMeasuredDimension, после чего вызываем Model.setSizes для перерасчета параметров, используемых Model для преобразования логических координат в экранные. Теперь наше приложение можно запустить и пронаблюдать отрисовку экрана радикально черного цвета :)

Очевидно, чтобы увидеть на экране плашки, их надо как-то нарисовать. Здесь у нас имеется некоторая свобода выбора. В принципе, не очень сложно нарисовать плашки в виде заполненных замкнутых полигонов (при этом, кстати, придется решить пару довольно нетривиальных алгоритмических задач), но, в надежде на то, что у нас когда нибудь появится нормальный дизайн, мы принимаем решение отображать отдельные Item-ы используя масштабируемые Bitmap-ы.

Добавляем заранее подготовленные PNG-файлы в drawable-ресурсы, добавляем в MainView код загрузки Bitmap-ов (проект под Eclipse выложен в конце статьи), попутно дописывая метод onDraw:

package com.whiterabbit.tags;

import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Bitmap;
import android.view.View;

public class MainView extends View {

    ...
    private Map<Long, Bitmap> bmps = new HashMap<Long, Bitmap>();

    public MainView(Context context) {
        super(context);
        model = new Model();
        loadBitmap(0L,   R.drawable.p000); loadBitmap(10L,  R.drawable.p010);
        loadBitmap(20L,  R.drawable.p020); loadBitmap(30L,  R.drawable.p030);
        loadBitmap(31L,  R.drawable.p031); loadBitmap(40L,  R.drawable.p040);
        loadBitmap(50L,  R.drawable.p050); loadBitmap(60L,  R.drawable.p060);
        loadBitmap(61L,  R.drawable.p061); loadBitmap(70L,  R.drawable.p070);
        loadBitmap(80L,  R.drawable.p080); loadBitmap(90L,  R.drawable.p090);
        loadBitmap(91L,  R.drawable.p091); loadBitmap(100L, R.drawable.p100);
        loadBitmap(110L, R.drawable.p110); loadBitmap(120L, R.drawable.p120);
        loadBitmap(121L, R.drawable.p121); loadBitmap(130L, R.drawable.p130);
        loadBitmap(140L, R.drawable.p140); loadBitmap(150L, R.drawable.p150);
    }

    private void loadBitmap(Long id, int name) {
        Bitmap bmp = BitmapFactory.decodeResource(getResources(), name);
        bmps.put(id, bmp);
    }
   
    public Bitmap getBitmap(Long id) {
        return bmps.get(id);
    }

    ...
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);
        DrawContext dc = new DrawContext(this, canvas);
        model.draw(dc);
    }
}


Для того чтобы инкапсулировать состояние всех объектов, необходимых для рисования, создадим класс DrawContext:

package com.whiterabbit.tags;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;

public class DrawContext {
   
    private MainView view;
    private Canvas canvas;
    private Matrix matrix = new Matrix();
    private Paint paint = new Paint();
   
    public DrawContext(MainView view, Canvas canvas) {
        this.view = view;
        this.canvas = canvas;
        paint.setAntiAlias(true);
    }
   
    public Bitmap getBitmap(Long id) {
        return view.getBitmap(id);
    }
   
    public void paint(Bitmap bmp, float x, float y) {
        matrix.setTranslate(x, y);
        canvas.drawBitmap(bmp, matrix, paint);
    }
}


Добавляем в Model метод draw:

package com.whiterabbit.tags;

...
public class Model {

    ...
    public void draw(DrawContext dc) {
        for (Tag t: tags.values()) {
            for (Item i: t.getItems()) {
                Bitmap bmp = dc.getBitmap(i.getType());
                if (bmp != null) {
                    Bitmap tmp = Bitmap.createScaledBitmap(bmp, 

                                                           tagSize, 
                                                           tagSize, 
                                                           true);
                    float x = (i.getX() - 1) * tagSize + marginX;
                    float y = (i.getY() - 1) * tagSize + marginY;
                    dc.paint(tmp, x, y);
                }
            }
        }
    }
}


Классы Tag и Item также несколько изменяются, для автоматического определения Bitmap-а используемого для отрисовки Item-а:

package com.whiterabbit.tags;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

public class Tag {

    ...
    public Item getItem(int x, int y) {
        for (Item i: items) {
            if ((i.getX() == x)&&(i.getY() == y)) {
                return i;
            }
        }
        return null;
    }
   
    private void setType(Item n, int x, int y, long om, long nm) {
        Item o = getItem(x, y);
        if (o != null) {
            o.changeType(om);
            n.changeType(nm);
        }
    }
   
    public void addItem(int x, int y) {
        Item it = new Item(x, y);
        setType(it, x - 1, y, 10L, 40L);
        setType(it, x + 1, y, 40L, 10L);
        setType(it, x, y - 1, 80L, 20L);
        setType(it, x, y + 1, 20L, 80L);
        items.add(it);
    }
}


package com.whiterabbit.tags;

public class Item {

    ...
    private long type = 150L;

    ...
    public long getType() {
        return type;
    }
   
    public void changeType(long m) {
        type -= m;
    }
}


Этот алгоритм подходит только для отображения выпуклых прямоугольников, но не хочется загромождать текст статьи незначительными деталями. Запустив проект на выполнение в эмуляторе, мы увидим желанное исходное расположение, в чем можно убедиться, скачав и запустив на выполнение следующий Eclipse-проект.

В следующей статье, мы реализуем Controller, заставив плашки перемещаться и завершим, тем самым, разработку Прототипа.

Комментариев нет:

Отправить комментарий