I have decided to work on an Android app that uses very similar technology to that of an app I have seen before. I wanted to string together multiple button presses to equat
I tried to code with a method that similar to the suggestion by aptyp:
MainActivity.java:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
static long DELAY_TIME_INPUT = 500;
static int INPUT_TYPE_NORMAL = 0;
static int INPUT_TYPE_CAP = 1;
static int INPUT_TYPE_NUM = 2;
TextView txtMessage;
int[] mAlphabetTable1 = new int[]{1, 3, 9, 25, 17, 11, 27, 19, 10, 26,
5, 7, 13, 29, 21, 15, 31, 23, 14, 30,
37, 39, 58, 45, 61, 53};
int[] mSymbolTable1 = new int[]{2, 6, 4, 18, 36, 40, 50, 22, 38, 52, 54, 12};
/* Note: The value used below {8, 16, 20, 24} are just an example.
I choose these values because they are not defined on the above tables.
*/
int[] mSpecialTable1 = new int[]{8, 16, 20, 24};
// char[] mAlphabetTable2 = new char[]{};
char[] mNumberTable2 = new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
char[] mSymbolTable2 = new char[]{',', ';', '\'', ':','-', '.', '.', '!', '“', '”','(','/'};
int mCurrentAlphabet = 0;
int mCurrentInputType = 0;
long mLastTimeStamp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Window window = this.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
// Init GUI
txtMessage = (TextView) findViewById(R.id.txtMesssage);
Button buttonOne = (Button) findViewById(R.id.block1);
Button buttonTwo = (Button) findViewById(R.id.block2);
Button buttonThree = (Button) findViewById(R.id.block3);
Button buttonFour = (Button) findViewById(R.id.block4);
Button buttonFive = (Button) findViewById(R.id.block5);
Button buttonSix = (Button) findViewById(R.id.block6);
// Attached Click Listener
buttonOne.setOnClickListener(this);
buttonTwo.setOnClickListener(this);
buttonThree.setOnClickListener(this);
buttonFour.setOnClickListener(this);
buttonFive.setOnClickListener(this);
buttonSix.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.block1: mCurrentAlphabet |=1; break;
case R.id.block2: mCurrentAlphabet |=2; break;
case R.id.block3: mCurrentAlphabet |=4; break;
case R.id.block4: mCurrentAlphabet |=8; break;
case R.id.block5: mCurrentAlphabet |=16; break;
case R.id.block6: mCurrentAlphabet |=32; break;
}
view.setBackgroundColor(Color.BLACK);
Button btView = (Button) view;
btView.setTextColor(Color.WHITE);
mLastTimeStamp = System.currentTimeMillis();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
long currentTimeStamp = System.currentTimeMillis();
if(currentTimeStamp - mLastTimeStamp > DELAY_TIME_INPUT){
genNewBrailleAlphabet();
}
}
}, DELAY_TIME_INPUT + 10);
}
public void genNewBrailleAlphabet(){
if(mCurrentAlphabet == 32 || mCurrentAlphabet == 60){ // Check if input is Cap or Num sign?
if(mCurrentAlphabet == 32){ // Input is Cap sign.
mCurrentInputType = INPUT_TYPE_CAP;
TextView txtCap = (TextView) findViewById(R.id.cap);
txtCap.setBackgroundColor(Color.GREEN);
} else { // Input is Num sign.
TextView txtNum = (TextView) findViewById(R.id.num);
if(mCurrentInputType == INPUT_TYPE_NUM){
mCurrentInputType = INPUT_TYPE_NORMAL; // Turn off Num sign.
txtNum.setBackgroundColor(Color.TRANSPARENT);
} else {
mCurrentInputType = INPUT_TYPE_NUM; // Turn on Num sign.
txtNum.setBackgroundColor(Color.GREEN);
}
}
} else { // Input is not Cap or Num sign.
byte currentAlphabetIndex = -1;
char newAlphabet = 0;
for (int i = 0; i < mAlphabetTable1.length; i++) {
if (mAlphabetTable1[i] == mCurrentAlphabet) {
currentAlphabetIndex = (byte) i;
break;
}
}
if(currentAlphabetIndex != -1) { // Check if input is Numbers or Alphabets?
if (mCurrentInputType == INPUT_TYPE_NUM) { // Input is Numbers.
if(currentAlphabetIndex < 10) {
newAlphabet = mNumberTable2[currentAlphabetIndex];
}
} else if (mCurrentInputType == INPUT_TYPE_CAP) // Input is Alphabets.
newAlphabet = (char) (currentAlphabetIndex + 'A');
else newAlphabet = (char) (currentAlphabetIndex + 'a');
String msg = txtMessage.getText().toString() + newAlphabet;
txtMessage.setText(msg);
} else { // Input is not Numbers or Alphabets.
for (int i = 0; i < mSymbolTable1.length; i++) {
if (mSymbolTable1[i] == mCurrentAlphabet) {
currentAlphabetIndex = (byte) i;
break;
}
}
if(currentAlphabetIndex != -1) { // Check if input is Punctuations?
newAlphabet = mSymbolTable2[currentAlphabetIndex];
if(currentAlphabetIndex == 8){ // Open Quote, Question Mark have the same pattern.
String tmpString = txtMessage.getText().toString();
if(tmpString.length() > 0 && !tmpString.endsWith(" ")){
// Last typed alphabet is not space, so this is Question Mark.
newAlphabet = '?';
}
}
String msg = txtMessage.getText().toString() + newAlphabet;
txtMessage.setText(msg);
} else { // Input is not Punctuations, so it is Special Action or undefined.
for (int i = 0; i < mSpecialTable1.length; i++) {
if (mSpecialTable1[i] == mCurrentAlphabet) {
currentAlphabetIndex = (byte) i;
break;
}
}
if(currentAlphabetIndex != -1) { // Check if input is Special Action?
String msg = txtMessage.getText().toString();
// Input is Special Action
switch (currentAlphabetIndex) {
case 0: // Change focus here
// Change focus code
/* if (txtNumber.hasFocus()) {
txtMessage.requestFocus();
} else {
txtNumber.requestFocus();
} */
break;
case 1: // BackSpace
msg = msg.substring(0, msg.length() - 1);
txtMessage.setText(msg);
break;
case 2: // Space
msg = msg + " ";
txtMessage.setText(msg);
break;
case 3: // New Line
msg = msg + "\n";
break;
}
txtMessage.setText(msg);
} else { // Input not defined.
Toast.makeText(getApplicationContext(), "Clicked button combination not defined!!", Toast.LENGTH_SHORT).show();
}
}
}
if(mCurrentInputType == INPUT_TYPE_CAP){
TextView txtCap = (TextView) findViewById(R.id.cap);
txtCap.setBackgroundColor(Color.TRANSPARENT);
mCurrentInputType = INPUT_TYPE_NORMAL;
}
}
// Reset button views ana variable for next alphabet.
Button buttonOne = (Button) findViewById(R.id.block1);
Button buttonTwo = (Button) findViewById(R.id.block2);
Button buttonThree = (Button) findViewById(R.id.block3);
Button buttonFour = (Button) findViewById(R.id.block4);
Button buttonFive = (Button) findViewById(R.id.block5);
Button buttonSix = (Button) findViewById(R.id.block6);
buttonOne.setBackgroundColor(Color.WHITE);
buttonTwo.setBackgroundColor(Color.WHITE);
buttonThree.setBackgroundColor(Color.WHITE);
buttonFour.setBackgroundColor(Color.WHITE);
buttonFive.setBackgroundColor(Color.WHITE);
buttonSix.setBackgroundColor(Color.WHITE);
buttonOne.setTextColor(Color.BLACK);
buttonTwo.setTextColor(Color.BLACK);
buttonThree.setTextColor(Color.BLACK);
buttonFour.setTextColor(Color.BLACK);
buttonFive.setTextColor(Color.BLACK);
buttonSix.setTextColor(Color.BLACK);
mCurrentAlphabet = 0;
}}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_weight="4.0"
android:background="@color/lightgrey"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/txtMesssage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColor="@color/darkbrown" >
</TextView>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:orientation="horizontal"
android:baselineAligned="false"
android:layout_marginTop="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:background="#000000"
android:orientation="vertical" >
<TextView
android:text="CAP"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/cap"
android:layout_weight="1.3"
android:gravity="center"
android:textSize="20sp" />
<Button
android:id="@+id/block1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:background="#ffffff"
android:text="Button one" />
<Button
android:id="@+id/block2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:background="#ffffff"
android:text="Button two" />
<Button
android:id="@+id/block3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:background="#ffffff"
android:text="Button three" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:orientation="vertical"
android:background="#000000">
<TextView
android:text="Num"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/num"
android:layout_weight="1.3"
android:gravity="center"
android:textSize="20sp" />
<Button
android:id="@+id/block4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:background="#ffffff"
android:text="Button four" />
<Button
android:id="@+id/block5"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:background="#ffffff"
android:text="Button five" />
<Button
android:id="@+id/block6"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:background="#ffffff"
android:text="Button six" />
</LinearLayout>
</LinearLayout>
Please note that I am using handler instead of timer. Hope it help!
I don't write a lot of java, so I can only give you psuedocode, but I'll point you in the right direction.
To finish this you only have about 3 things to do: 1. Collect your input data (the button taps), which you've almost done. 2. Know when it's time to process the input. 3. Process the input to the right output.
Collect your input.
At this point you want to store those presses in an array of id's, switch not necessary. After a few presses you should have array of inputs.
inputs[R.id.block1, R.id.block2, R.id.block2];
Know when it's time to process.
In your button press handler, add a countdown timer. With every button press, cancel the timer and start a new one.
Timer processTimer = new Timer().schedule(new TimerTask() {
public void run() { processInput(); }
}, 500); // Delay before processing.
processTimer.cancel();
processtimer.schedule(new TimerTask() {
public void run() { processInput(); }
}, 500);
When processInput() is called you know the user has stopped pressing buttons.
Process the Input.
Create key/value pairs for your outcomes.
map = {[R.id.block1],'A',
[R.id.block1, R.id.block2],'B',
[R.id.block1, R.id.block2, R.id.block2],'C',
etc...
}
Search it for the user's input and get your value.
That's basically all there is to it.
Alright so I have given this a go, and I believe this will help you get down the path you are attempting given my current understanding of your requirements. I have been quite verbose in my commenting of the class, what I am doing, and why. I hope it is helpful and doesn't feel like pandering. It sounded maybe you were pretty new to Java and Android and I felt the added explanation might be useful.
I am sure there are edge cases and bugs that have been missed, but should be a good start.
Android Activity Class -- MainActivity.java
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
/**
* TAG is typically used for the Log class if you want to see debug/error information in logcat
*/
public static final String TAG = MainActivity.class.getSimpleName();
/**
* Constant to delay before attempting to resolve button presses to an input
*/
public static final int INPUT_HANDLE_DELAY_MS = 500;
/**
* Buttons have naming convention button<column><row>
*/
private Button mButton00;
private Button mButton10;
private Button mButton01;
private Button mButton11;
private Button mButton02;
private Button mButton12;
/**
* Where we are going to store the input generated by button presses
*/
private EditText mEditText;
/**
* Where the lookup based on ids is going to happen and convert it to a character
*/
private static Map<String, String> mLookupMap;
/**
* I am not 100% sure if this is needed, but because we will be attempting to write to the mInputQueue,
* and read from it, i've created a lock
*/
private static final Object mLock = new Object();
/**
* Where we are going to store the input generated by button presses
*/
private Queue<Integer> mInputQueue= new LinkedList<>();
/**
* Android Handler class to help with the timer, and executing our runnable to handle the
* button resolution
*/
private Handler mHandler = new Handler();
/**
* Runnable to cause the application to check if there is valid input
*/
private Runnable mHandleInputRunnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.buttons);
// method call to initialize the lookup map
initializeLookup();
// Obtain all the references from the layout that we are going to need to use
mEditText = (EditText) findViewById(R.id.edit_text);
mButton00 = (Button) findViewById(R.id.button_0_0);
mButton10 = (Button) findViewById(R.id.button_1_0);
mButton01 = (Button) findViewById(R.id.button_0_1);
mButton11 = (Button) findViewById(R.id.button_1_1);
mButton02 = (Button) findViewById(R.id.button_0_2);
mButton12 = (Button) findViewById(R.id.button_1_2);
// above, the activity implements the OnClickListener interface, set the activity to handle
// the clicks of all of the buttons
mButton00.setOnClickListener(this);
mButton10.setOnClickListener(this);
mButton01.setOnClickListener(this);
mButton11.setOnClickListener(this);
mButton02.setOnClickListener(this);
mButton12.setOnClickListener(this);
// initialize the Runnable to do what we need it to do when we get a 'tick'
mHandleInputRunnable = new Runnable() {
@Override
public void run() {
handleAlarmTrigger();
}
};
}
@Override
public void onClick(View v) {
if (null != mInputQueue) {
synchronized (mLock) {
mInputQueue.add(v.getId());
}
}
resetHandler();
}
/**
* Helper method to do the initialization of the map. Sorry about the order for the button
* presses. They are a little sporadic, no real rhyme or reason.
*/
private void initializeLookup() {
if (null != mLookupMap) {
return;
}
mLookupMap = new HashMap<>();
/*
1 button characters, use String.valueOf() instead of buildStringFromIds() as there is only
one id
*/
mLookupMap.put(String.valueOf(R.id.button_0_0), "A");
mLookupMap.put(String.valueOf(R.id.button_1_0), "B");
mLookupMap.put(String.valueOf(R.id.button_0_1), "C");
mLookupMap.put(String.valueOf(R.id.button_1_1), "D");
mLookupMap.put(String.valueOf(R.id.button_0_2), "E");
mLookupMap.put(String.valueOf(R.id.button_1_2), "F");
/*
2 button characters
*/
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_1_0), "G");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_0_1), "H");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_1_1), "I");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_0_2), "J");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_1_2), "K");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_0_0), "L");
/*
3 button characters
*/
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_1_0, R.id.button_0_1), "M");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_0_1, R.id.button_0_2), "N");
mLookupMap.put(buildStringFromIds(R.id.button_1_0, R.id.button_1_1, R.id.button_1_2), "O");
mLookupMap.put(buildStringFromIds(R.id.button_1_0, R.id.button_0_1, R.id.button_1_2), "P");
mLookupMap.put(buildStringFromIds(R.id.button_1_2, R.id.button_1_2, R.id.button_0_1), "Q");
mLookupMap.put(buildStringFromIds(R.id.button_1_2, R.id.button_1_2, R.id.button_0_1), "R");
/*
4 button characters
*/
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_0_0, R.id.button_0_0, R.id.button_0_0), "S");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_1_0, R.id.button_0_1, R.id.button_1_0), "T");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_0_1, R.id.button_0_2, R.id.button_0_1), "U");
mLookupMap.put(buildStringFromIds(R.id.button_1_0, R.id.button_1_1, R.id.button_1_2, R.id.button_1_1), "V");
mLookupMap.put(buildStringFromIds(R.id.button_1_0, R.id.button_0_1, R.id.button_1_2, R.id.button_0_2), "W");
mLookupMap.put(buildStringFromIds(R.id.button_1_2, R.id.button_1_2, R.id.button_0_1, R.id.button_1_2), "X");
/*
5 button characters
*/
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_0_0, R.id.button_0_0, R.id.button_0_0, R.id.button_0_0), "Y");
mLookupMap.put(buildStringFromIds(R.id.button_0_0, R.id.button_1_0, R.id.button_0_1, R.id.button_1_0, R.id.button_1_1), "Z");
}
/**
* Helper method to poll all of the values out of the queue, and create a key. This may or may
* not be a valid key into the map. It depends on the button presses.
*
* @param queue the input queue where we store the id correlating to the button that was pressed
* @return String representing a key into the map
*/
private String buildStringFromQueue(Queue<Integer> queue) {
StringBuilder sb = new StringBuilder();
if (null != queue) {
Integer pollValue;
synchronized (mLock) {
while ((pollValue = queue.poll()) != null) {
sb.append(pollValue);
}
}
}
return sb.toString();
}
/**
* Helper method to turn 1 to many R.id.button* into a string for a key as a lookup for a character
* value
*
* The "..." means that the method can accept 1 or more values of the defined type
*
* @param ids 1 or more ids
* @return String representing the ids as key into the map
*/
private String buildStringFromIds(int... ids) {
StringBuilder sb = new StringBuilder();
for (int id : ids) {
sb.append(id);
}
return sb.toString();
}
/**
* Helper method that is called each time there is a button click to either start or re-start
* the time before resolving input
*/
private void resetHandler() {
// null checks for the values we will be operating on
if (null != mHandler && null != mHandleInputRunnable) {
/**
* remove the current runnable the handler may/may not have set, and reset it with the
* delay. This is essentially resetting the time before the app takes the button press
* and tries to do a lookup in the map.
*/
mHandler.removeCallbacks(mHandleInputRunnable);
mHandler.postDelayed(mHandleInputRunnable, INPUT_HANDLE_DELAY_MS);
}
}
/**
* Helper method that is called from the Runnable to attempt to handle the input
*/
private void handleAlarmTrigger() {
// just to be safe, always check the queue for null, and don't handle if empty. Also
// we will be setting values on the EditText, ensure it is non-null as well
if (null != mEditText &&
null != mInputQueue &&
!mInputQueue.isEmpty()) {
// Obtain the key from the input we have stored. This will provide a look up into the
// map
String mapKey = buildStringFromQueue(mInputQueue);
// only try and append this if it was a valid set of button presses, and the map
// actually has a value
if (null != mapKey &&
!mapKey.isEmpty() &&
mLookupMap.containsKey(mapKey)) {
mEditText.append(mLookupMap.get(mapKey));
}
// remove all stored values from the queue so we may restart the button presses
synchronized (mLock) {
mInputQueue.clear();
}
}
}
}
Android Layout -- buttons.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<GridLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:columnCount="2"
android:rowCount="3">
<Button android:id="@+id/button_0_0"
android:layout_column="0"
android:layout_row="0"
android:layout_gravity="fill"
android:layout_columnWeight=".5"
android:layout_rowWeight=".5"
android:background="@android:color/holo_red_dark"/>
<Button android:id="@+id/button_1_0"
android:layout_column="1"
android:layout_row="0"
android:layout_gravity="fill"
android:layout_columnWeight=".5"
android:layout_rowWeight=".5"
android:background="@android:color/holo_blue_bright"/>
<Button android:id="@+id/button_0_1"
android:layout_column="0"
android:layout_row="1"
android:layout_gravity="fill"
android:layout_columnWeight=".5"
android:layout_rowWeight=".5"
android:background="@android:color/holo_green_dark"/>
<Button android:id="@+id/button_1_1"
android:layout_column="1"
android:layout_row="1"
android:layout_gravity="fill"
android:layout_columnWeight=".5"
android:layout_rowWeight=".5"
android:background="@android:color/holo_orange_dark"/>
<Button android:id="@+id/button_0_2"
android:layout_column="0"
android:layout_row="2"
android:layout_gravity="fill"
android:layout_columnWeight=".5"
android:layout_rowWeight=".5"
android:background="@android:color/holo_purple"/>
<Button android:id="@+id/button_1_2"
android:layout_column="1"
android:layout_row="2"
android:layout_gravity="fill"
android:layout_columnWeight=".5"
android:layout_rowWeight=".5"
android:background="@android:color/darker_gray"/>
</GridLayout>