Construindo um aplicativo Android de Roller de Dados Viável de Produto Viável usando as construções básicas do Android.

Vector AndroidRelativeLayout , ConstraintLayout , LinearLayout ]ListView e ArrayAdapter ListView e lógica AdapterHá pouco tempo, entrei nesse jogo de tabuleiro de loucura. O jogo é um pouco como a pista clássica, onde os jogadores vagam por uma casa tentando resolver algum mistério. É um jogo incrível que eu recomendo . De qualquer forma, o jogo usa rolos de dados para resolver ações e outros eventos de jogo. Estranhamente, os jogadores às vezes precisam rolar mais dados do que o jogo inclui (6)! Decidi que essa era uma oportunidade perfeita para construir um aplicativo personalizado de rolos de dados. Neste tutorial, usarei componentes básicos do Android para construir um rolo de dados de loucura.

Este aplicativo de dados foi projetado especificamente para a jogabilidade de mansões de loucura, então primeiro vou delinear como o jogo usa dados.
Para simplificar as coisas, o aplicativo será uma lista vertical e rolável de dados. O aplicativo terá 3 botões para desencadear funções "Roll Dice", "Adicionar dados" e "Remover dados". Cada dados terão um 'retenção' e 'alteração' correspondente.

A plataforma Android está sempre mudando, tornando os tutoriais como esses obsoletos com o tempo. Para referência, meu ambiente de desenvolvimento:
Ambiente Android


Todos esses nomes podem ser alterados após a criação do projeto, embora possa ficar complicado perseguir todas as referências de nome se o projeto ficar complexo.

O Android tem muitas versões. A cada versão, a plataforma muda. Isso basicamente significa que existem muitos dispositivos Android no mundo com diferentes versões. Isso se torna uma dor de cabeça para os desenvolvedores de aplicativos, porque, dependendo das bibliotecas que o aplicativo usa, o aplicativo pode ser incompatível com certos dispositivos. A troca aqui é que o aplicativo usando as novas bibliotecas do Android não pode ser executado em dispositivos mais antigos. Se o aplicativo deve ser executado em dispositivos mais antigos, o aplicativo deverá usar algumas das construções Android mais antigas.

O modelo inicial na verdade não importa muito para este aplicativo. O código do modelo às vezes é útil porque prepara o layout e as classes iniciais com algum código. Como não vou usar nada disso, escolhi atividades vazias.

A caixa de diálogo Android Project Creation inicializa o projeto com construções básicas:
Gradle é uma estrutura para facilitar os projetos de construção. Na Wikipedia
O manifesto do Android descreve o aplicativo para Android. Propriedades do aplicativo, como permissões e atividades. Os detalhes podem ser encontrados na página de documentação do Android.
Para começar, usei um editor simples de SVG online chamado Clker para desenhar os rostos de dados como SVG.



Em seguida, eu os importo para o meu projeto usando o Asset Studio do Android Studio.


Os dispositivos têm resoluções e dimensões diferentes. É difícil prever a resolução e a dimensão do dispositivo que o aplicativo executa. Os JPEGs de escala podem resultar em gráficos embaçados ou granulados. Uma maneira de enfrentar isso é para os gráficos é usar os SVGs. Existem inúmeros artigos on -line discutindo os SVGs, mas, para referência, confira o artigo da Wikipedia SVG para obter mais informações.
O Android oferece duas maneiras de descrever layouts: layouts programáticos e XML. A descrição de layouts complexos é bastante difícil, então a maioria das pessoas geralmente evita isso. Escrever XML pode não ser tão divertido, mas o Android Studio oferece várias ferramentas para aliviar a dor. Uma ferramenta de visualização e um editor de layout do WSYWIG. Mas mesmo com o editor, mergulhar no XML é quase inevitável.


O modelo de atividade vazia do Android Studio nos inicia com um elemento de layout da raiz ConstraintLayout . Precisamos de dois componentes neste aplicativo: área de dados e área do controlador. A área de dados será uma lista de dados roláveis e o controlador será os 3 botões "Adicionar" "Remover" "Roll".

activity_main.xml <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/dice_list"
android:layout_height="0dp"
android:layout_width="match_parent"
app:layout_constraintBottom_toTopOf="@id/button_bar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</ListView>
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/control_bar_height"
android:orientation="horizontal"
android:weightSum="3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/dice_list">
<Button
android:id="@+id/add_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_button_label"
android:onClick="addDice"/>
<Button
android:id="@+id/rem_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rem_button_label"
android:onClick="removeDice"/>
<Button
android:id="@+id/roll_dice_button"
android:layout_weight="1"
android:layout_gravity = "center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/roll_button_label"
android:onClick="rollDice"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
Para aqueles que não estão familiarizados com XML, o acima pode parecer sem sentido. Explicar o XML está fora do escopo deste tutorial, mas o Google, o YouTube e a Wikipedia são ótimos recursos para quem procura mais informações. Para este layout, estou usando as classes ListView , Button , LinearLayout e ConstraintLayout . Os detalhes em torno de seus atributos podem ser encontrados na página de documentação do Android.
Eu estava procurando um Layout que descreva facilmente uma área inferior de altura fixa (para botões) e uma área superior (para dados) que preenchia o espaço de tela disponível. LinearLayout espaços seus submiets, usando pesos, tornando -o inadequado. RelativeLayout não oferece a capacidade de 'preencher o espaço restante', tornando -o inadequado.
Visualmente, LinearLayout parece bem perto do que eu preciso. No entanto, LinearLayout é para a lista estática de elementos, em vez de listas dinâmicas. Para este aplicativo, a lista de dados pode ter entre 0 e 25 dados, fazer a ListView é um candidato melhor.
O valor 0dp é específico para ConstraintLayout que indica que deve preencher o espaço restante do pai.
Como descrito acima, cada linha de dados inclui 2 botões e uma imagem de dados.

row.xml <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/row_height">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/row_height"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:padding="@dimen/row_padding">
<Button
android:id="@+id/dice_change_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/change_button_label">
</Button>
</FrameLayout>
<ImageView
android:id="@+id/dice_icon"
android:layout_centerInParent="true"
android:layout_width="@dimen/image_width"
android:layout_height="@dimen/image_height"
android:src="@drawable/blank_dice"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/row_height"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:padding="@dimen/row_padding">
<Button
android:id="@+id/dice_hold_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hold_button_label">
</Button>
</FrameLayout>
</RelativeLayout>
O botão fica no centro vertical da linha e a alguma distância de cada borda. Eu senti que o design seria mais limpo se o design separasse o elemento real do botão e sua posição no layout. Portanto, para o meu aplicativo, uso o FrameLayout para especificar a posição e centralizar o botão nesse layout.
Os valores de string e dimensão nos permitem não gravar strings de configuração e números inteiros diretamente no código. Para o nosso pequeno aplicativo, talvez não seja um grande negócio.
string.xml <resources>
<string name="app_name">DiceRoller</string>
<string name="hold_button_label">Hold</string>
<string name="change_button_label">Change</string>
<string name="add_button_label">ADD</string>
<string name="rem_button_label">REM</string>
<string name="roll_button_label">ROLL</string>
</resources>
dimens.xml <?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="row_height">72dp</dimen>
<dimen name="row_padding">16dp</dimen>
<dimen name="control_bar_height">72dp</dimen>
<dimen name="image_width">72dp</dimen>
<dimen name="image_height">72dp</dimen>
</resources>
ListView e ArrayAdapter Nesse ponto, inicializei nosso projeto Android com uma MainActivity vazia e zombei de alguns layouts. Em seguida, vou entrar na lógica e código. Para começar, gostaria de entrar em mais aulas de Java Android. ListView é uma classe de layout básica para renderizar listas visuais. A estrutura do Android separa os componentes visuais ( ListView ) e os componentes de dados ( List<Dice> ) empregando um padrão de adaptador. No nosso caso, tudo o que o adaptador faz é mapear os dados ( Dice ) para algum layout visual ( dice_row.xml ). Nesse caso, um arquivo XML de layout descreve o layout.
MainActivity.java public class MainActivity extends AppCompatActivity {
DiceAdapter diceAdapter;
List <Dice> diceList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//associating activity to layout
setContentView(R.layout.activity_main);
//Setup ListView and Adapter
ListView listView = findViewById(R.id.dice_list);
diceAdapter = new DiceAdapter(this, R.layout.dice_row, diceList);
listView.setAdapter(diceAdapter);
//Initialize Data
diceAdapter.add(new Dice());
}
public class DiceAdapter extends ArrayAdapter<Dice> {
public DiceAdapter(@NonNull Context context, int resource, List<Dice> list) {
super(context, resource, list);
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.dice_row, parent, false);
}
return convertView;
}
}
}
O aplicativo representará o estado dos dados com objetos de dados. O objeto DICE tem duas propriedades: valor dos dados [em branco, amplie, estrela] e se os dados são 'mantidos'. Funcionalmente, o DICE possui um método de rolo que selecionará aleatoriamente uma face de dados. Por fim, adiciono um método que altera o valor dos dados para o próximo na lista.
MainActivity.java ....
public static class Dice {
public enum Face {
BLANK,
MAGNIFY,
STAR
}
public static Random random = new Random();
boolean hold = false;
Face diceVal;
Dice() {
roll();
}
public void roll() {
int num = random.nextInt(4);
if(num == 0) { //25% magify
this.diceVal = Face.MAGNIFY;
} else {
//37.5% star, 37.5% blank
if(random.nextBoolean()) {
this.diceVal = Face.BLANK;
} else {
this.diceVal = Face.STAR;
}
}
}
public void toggleHold() {
hold = !hold;
}
public void nextValue() {
int index = diceVal.ordinal();
index = (index+1) % Face.values().length;
diceVal = Face.values()[index];
}
}
Nesta etapa, o botão do mapa de cliques para a lógica. A plataforma Android oferece algumas maneiras de fazer isso. Uma maneira é especificar um atributo do arquivo de layout. Outra é definir programaticamente o onClickListener . Em nosso aplicativo, use a abordagem de atributo para os três botões de nível superior e defina programaticamente o ouvinte para os botões da linha.
addDice se a contagem de dados for menor que 25, adicionar um novo objeto de dados à lista de dados.
Projeto
activity_main.xml ....
<Button
android:id="@+id/add_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_button_label"
android:onClick="addDice"/>
....
Lógica
MainActivity.java ....
public void addDice(View view) {
if(diceList.size()< MAX_DICE_COUNT) {
diceAdapter.add(new Dice());
}
}
....
removeDice Se a lista de dados não estiver vazia, remove os últimos dados da lista
Projeto
activity_main.xml ....
<Button
android:id="@+id/rem_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rem_button_label"
android:onClick="removeDice"/>
....
Lógica
MainActivity.java ....
public void removeDice(View view) {
if(!diceList.isEmpty()) {
int lastIndex = diceList.size() - 1;
diceAdapter.remove(diceAdapter.getItem(lastIndex));
}
}
....
rollDice reerrole o valor de todos os dados em sua lista que não foi marcada para segurar.
Projeto
activity_main.xml ....
<Button
android:id="@+id/roll_dice_button"
android:layout_weight="1"
android:layout_gravity = "center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/roll_button_label"
android:onClick="rollDice"/>
....
Lógica
MainActivity.java ....
public void rollDice(View view) {
//roll all dice
for(Dice dice : diceList) {
if(!dice.hold)
dice.roll();
}
//notify adapter to update view
diceAdapter.notifyDataSetChanged();
}
....
notifyDataSetChanged ? O botão Roll altera o DiceValue para o objeto DICE correspondente. Devido à separação de padrões de adaptador de visualização/dados, o layout da linha DICE não renderiza automaticamente a menos que seja acionado. Chamar notifyDataSetChanged com o REDRAWS A VISTA.
Clicar em Hold Botão definirá o sinalizador de espera dos dados.
MainActivity.java ....
Button holdButton = convertView.findViewById(R.id.dice_hold_button);
holdButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Dice dice = diceList.get(position);
dice.toggleHold();
}
});
....
Clicar em Hold Botão alterará o valor dos dados e atualizará a interface.
MainActivity.java ....
Button changeButton = convertView.findViewById(R.id.dice_change_button);
changeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Dice dice = diceList.get(position);
dice.nextValue();
diceAdapter.notifyDataSetChanged();
}
});
....