Я выпустил в Github исходный код приложения, которое можно использовать для игры в колышек. Это приложение было опубликовано на рынке несколько раз назад, и это новая версия.
Скриншоты приложения показаны ниже:
Есть несколько интересных аспектов, которые мы можем рассмотреть в этом приложении, и они описаны ниже. Это может использоваться в качестве примера того, как интегрировать различные элементы, такие как 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 > |
Результат показан ниже:
Как вы можете заметить, 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); } } |
Таким образом, мы создали пустую доску, которая выглядит как на картинке ниже:
Пользовательский 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