Статьи

Руководство по Android App: настольная игра Peg

Я выпустил в Github исходный код приложения, которое можно использовать для игры в колышек. Это приложение было опубликовано на рынке несколько раз назад, и это новая версия.

Скриншоты приложения показаны ниже:

board_1 [4]

board_2 [4]

board_3 [4]

Есть несколько интересных аспектов, которые мы можем рассмотреть в этом приложении, и они описаны ниже. Это может использоваться в качестве примера того, как интегрировать различные элементы, такие как SlidingPaneLayoutfragments , обработка событий и так далее.

Структура приложения

Основным шаблоном пользовательского интерфейса Android, используемым приложением, является макет Sliding Pane, потому что нам нужно разделить экран на две области: основная область — это место, где мы можем поиграть с нашими колышками, а другая, которая обычно закрыта, — это меню.

Итак, общий макет:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/tilebkg"
    >
 
    <fragment
        android:id="@+id/pinMenu"
        android:name="com.survivingwithandroid.pegboard.fragment.MenuFragment"
        android:layout_width="200dp"
        android:layout_height="match_parent"
        android:layout_gravity="left"
        />
 
    <fragment
        android:id="@+id/pinTable"
        android:name="com.survivingwithandroid.pegboard.fragment.DreamPinTableFragment"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_gravity="right"
        android:layout_weight="1" />
 
</android.support.v4.widget.SlidingPaneLayout>

Результат показан ниже:

board_2_fragment [4]

Как вы можете заметить, SlidingPaneLayout обрабатывает два фрагмента DreamPinTableFragment и MenuFragment . DreamPinTableFragment заботится о создании таблицы и обработке касания пользователя, чтобы пользователь мог размещать на ней булавки, в то время как MenuFragment обрабатывает некоторые параметры, такие как выбор цветов булавки, сохранение работы и так далее.

Пользовательский макет с ViewGroup

Колышковая доска реализована в виде пользовательского макета. Класс, реализующий его, называется PinTableView , расширяющим ViewGroup . Этот пользовательский макет позволяет разделить экран пользовательского интерфейса на маленькие ячейки, соответствующие отверстиям, в которых расположены колышки. Экран приложения обрабатывается как таблица, разделенная на строки и столбцы.

Первый метод, вызываемый DreamPinTableFragment, — это disposePin . Этот метод вычисляет число или строки и столбцы и инициализирует таблицу:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public int[] disposePins(int width, int height, int dotSize) {
 
    this.dotSize = dotSize;
 
    numRow = height / dotSize + 1;
    numCol =  width / dotSize + 1;
 
    int[] dotColors = Pin.createDotArray(dotSize, true);
 
    for (int r=0; r < numRow ; r++) {
        for (int c=0; c < numCol; c++) {
            PinImageView pinImg = new PinImageView(getContext(), r, c);
            this.addView(pinImg);              
        }
    }
 
    return new int[]{numRow, numCol};
}

Обратите внимание, что в этом методе мы добавляем PinImageView (другое пользовательское представление) к макету (строка …), давая им правильный номер строки и столбца.

Затем в методе onLayout мы просматриваем список потомков (мы добавили ранее) и начинаем размещать их в правильном положении. В этом случае нам нужно вычислить координаты x и y в реальном пикселе, это просто, когда мы знаем номер строки и столбца и размер пикселя ячейки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
    int childCount = getChildCount();
 
    for (int i=0; i < childCount; i++) {
        PinImageView pinImg = (PinImageView) getChildAt(i);
 
        //int left = pinImg.getCol() * dotSize + dotSize * (pinImg.getType() == PinImageView.COLOR_COMMANDS || pinImg.getType() == PinImageView.DELETE ? 0 : 1);
        int left = pinImg.getCol() * dotSize;
        //int top = pinImg.getRow()  * dotSize + dotSize * (pinImg.getType() == PinImageView.COLOR_COMMANDS || pinImg.getType() == PinImageView.DELETE ? 0 : 1);
        int top = pinImg.getRow()  * dotSize;
        int right = left + dotSize ;
        int bottom = top + dotSize ;
 
        pinImg.layout(left, top, right, bottom);           
    }
 
}

Таким образом, мы создали пустую доску, которая выглядит как на картинке ниже:

board_1 [8]

Пользовательский imageView: PinImageView

Как мы уже видели, мы размещаем в нашем макете пользовательские дочерние элементы ImageView. PinImageView — это простой класс, который расширяет ImageView и представляет один Peg (или pin, как мы предпочитаем его называть). Этот класс имеет внутреннее состояние, в котором хранится тип колышка, который он представляет, и реализует некоторую анимацию, чтобы сделать пользовательский интерфейс более привлекательным.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class PinImageView extends ImageView  {
private int row;
private int col;   
private int xSize;
private int ySize;   
private int stato = -1;   
private Context ctx;
private int currentPinId;
 
public PinImageView(Context context, int row, int col) {
    super(context);
    this.ctx = context;
    this.row = row;
    this.col = col;
    //this.parent = parent;       
 
    // Load image
    Drawable d  = getResources().getDrawable(TableConfig.pinBackground);
    setImageDrawable(d);       
    xSize = this.getWidth();
    ySize = this.getHeight();
    this.currentPinId = TableConfig.pinBackground;
 
}
...
class AnimView implements Runnable {
 
        Animation anim;
        Drawable d;
 
        public AnimView(Animation anim, Drawable d) {
            this.anim = anim;
            this.d = d;
        }
        @Override
        public void run() {
            anim.setAnimationListener(new Animation.AnimationListener() {
 
                @Override
                public void onAnimationStart(Animation animation) {
                    TableConfig.playSound(PinImageView.this.ctx);
                }
 
                @Override
                public void onAnimationRepeat(Animation animation) {}
 
                @Override
                public void onAnimationEnd(Animation animation) {
                    setImageDrawable(d);
                    PinImageView.this.setVisibility(View.VISIBLE);
                }
            });
 
            PinImageView.this.startAnimation(anim);   
        }
 
    }
}

Обработка касания пользователя: OnTouchEvent и OnTouchListener

Следующим шагом является обработка прикосновения пользователя. Мы определяем, что DreamPinTableFragment прослушивает события, которые запускаются, когда пользователь касается экрана. Мы хотим поместить колышек в положение, где пользователь коснулся экрана:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@Override
public boolean onTouch(View v, MotionEvent event) {
 
    int x = (int) event.getX() ;
    int y = (int) event.getY() ;
 
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        pinsTable.changePinColor(x, y);           
        return true;
    }
    else if (event.getAction() == MotionEvent.ACTION_MOVE) {
        pinsTable.changePinColor(x, y);   
        return true;
    }
 
    return false;
 
}

а затем мы делегируем PinTableView для обработки события, передавая координаты:

01
02
03
04
05
06
07
08
09
10
public void changePinColor(int x, int y) {
    int row = getRow(y);
    int col = getColumn(x);
 
    PinImageView pinImg = (PinImageView) getChildAt(col + ( row  * numCol ));
    if (pinImg != null) {
        pinImg.setPinColor(currentColor);   
 
    }
}

Зная координаты x и y, мы можем вычислить соответствующую строку и столбец и установить колышек нужного цвета.

Меню приложения

Как мы объясняли ранее, в приложении есть меню, в котором пользователь может выбрать пинколор, сохранить работу или изменить фон таблицы. Класс, который обрабатывает меню, называется MenuFragment. Это очень простой класс, он просто уведомляет основную деятельность о событиях, вызванных, когда пользователь выбирает пункт меню. В свою очередь, основное действие обрабатывает это событие, информируя другой фрагмент, когда это событие касается колышков или некоторых действий, которые должны быть предприняты на столе.

MenuFragment определяет интерфейс (слушатель), который мы должны реализовать, когда хотим получить информацию о событиях, произошедших в меню:

1
2
3
4
5
6
public static interface MenuEventListener {
    public void onPinSelected(int pinId);
    public void onClearSelected();
    public void onSaveSelected();
    public void onBackgroundSelected();
}

Итак, основное направление деятельности:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class DreamPinsActivity extends Activity implements
MenuFragment.MenuEventListener {
....
    @Override
    public void onPinSelected(int pinId) {
 
        pinTableFrag.setCurrentPin(pinId);
        closeMenu();
    }
 
    @Override
    public void onClearSelected() {
        ...
    }
 
    @Override
    public void onSaveSelected() {
        closeMenu();
        ...
    }
 
    public void onBackgroundSelected() {
        ...
    }
}

Сохранить макет как изображение

Последний аспект, который мы хотим охватить, это сохранение макета в виде изображения. Мы можем реализовать это следующим образом:

в PinsTableView:

1
2
3
4
5
6
7
8
public Bitmap createBitmap() {
    Bitmap b = Bitmap.createBitmap(this.getWidth(), this.getHeight(), Bitmap.Config.RGB_565);       
    Canvas c = new Canvas(b);
 
    this.draw(c);
 
    return b;
}

где мы создаем пустое растровое изображение с тем же размером экрана, а затем рисуем на соответствующем холсте . Когда у нас есть растровое изображение, которое содержит скриншот экрана приложения, мы сохраняем его в файле:

1
2
3
4
5
6
OutputStream outStream = context.getContentResolver()
        .openOutputStream(uri);
 
b.compress(Bitmap.CompressFormat.PNG, 100, outStream);
outStream.flush();
outStream.close();
  • Исходный код доступен @ github

Ссылка: руководство по Android App: настольная игра Peg от нашего партнера JCG Франческо Аццолы в блоге Surviving w / Android .