Програмуємо калькулятор на андроїд. Урок 6

Урок 6. Jптимізуємо код. Обробляємо натискання додаткових кнопок.

Подивимося на код MainActivity.java. У onCreate у нас 16 однотипних методів findViewById і 16 setOnClickListener. Оскільки ми додали ще 4 кнопки їх буде по 20. Ще візьмемо до уваги, що незабаром ми будемо міняти вид кнопок і з кожною з них нам потрібно буде проробляти однакові операції. Уявляєте на що перетвориться наш код!

Для скорочення коду ми виконаємо наступне: зберемо всі наші кнопки в масив і всі дії вже будемо виробляти з елементами масиву в циклі. Почнемо.

Спочатку оголосимо об’єкти наших нових чотирьох кнопок, а також для роботи нам знадобляться два масиви (один типу int – для id кнопок, інший типу Button – для самих об’єктів кнопок) і целочисленная змінна для зберігання кількості елементів масиву:


Button btPoint, btSquare, btRadical, btBack;
int[] bt_ids;
Button[] bt_array;
int len;

Потім видаляємо 16 рядків findViewById і 16 setOnClickListener, що відносяться до кнопок. Текстове поле і LinearLayout не чіпаємо, тільки кнопки.

Ініціалізіруем масиви, тобто задаємо значення їх елементів:


bt_ids = new int[]{
R.id.btOne, R.id.btTwo, R.id.btThree, R.id.btFour, R.id.btFive,
R.id.btSix, R.id.btSeven, R.id.btEight, R.id.btNine, R.id.btZero,
R.id.btPlus, R.id.btMinus,R.id.btMulti, R.id.btDiv, R.id.btEqual,
R.id.btClear, R.id.btPoint, R.id.btSquare, R.id.btRadical, R.id.btBack
};

bt_array = new Button[]{
btOne, btTwo, btThree, btFour, btFive, btSix, btSeven,
btEight, btNine,btZero, btPlus, btMinus, btMulti, btDiv, btEqual,
btClear, btPoint, btSquare, btRadical, btBack
};

Визначаємо кількість елементів масиву і пишемо цикл:


len = bt_array.length;
for(int i = 0; i < len; i++){
   bt_array[i] = (Button) findViewById(bt_ids[i]);
   bt_array[i].setOnClickListener(this);
}

Готово! Розберемо, що тут відбувається: з кожною ітерацією (повторенням) циклу ми збільшуємо значення i на одиницю, беремо i-тий елемент масиву bt_array і пов’язуємо його з i-тим елементом масиву bt_ids. Потім для нього встановлюємо слухача. Переходимо до наступної ітерації. Так продовжуємо, поки i буде менше, ніж кількість елементів в масиві. Думаю потрібно зауважити, що нумерація елементів масиву в Java починається з нуля.
Помітили як зменшилася кількість коду.
Якщо ви помітили, одна з додаткових кнопок – точка. Ми пізніше зробимо обробку чисел з комою. Тому нам перед виведенням числа в текстове поле нам знадобиться перевірка, чи є число дробовим, щоб цілі числа відображати без дробової частини. Цю перевірку нам потрібно буде робити перед кожним показом числа на різних ділянках коду. Щоб не писати один і той же код в декількох місцях, винесемо перевірку і висновок в текстове поле в окремий метод showNumber.


private void showNumber(double num){
if(num%1 == 0){
   tvLCD.setText(Integer.toString((int) num));
}else{
   tvLCD.setText(Double.toString(num));
   }
}

Перевіряємо, чи дорівнює дрібна частина нулю, в залежності від результату виводимо число в tvLCD одним із способів. Тепер змінимо тип змінних operand1 і operand2 на double (тому що в майбутньому будемо записувати в них дробові числа, також щоб уникнути помилок на цьому етапі – адже showNumber просить на вхід змінну типу double), а також замінимо всюди код tvLCD.setText ( …) на showNumber (…).

Ще кілька разів встречется в коді обнулення всіх змінних. Теж виносимо в окремий метод clearVariables:


private void clearVariables(){

operand1 = 0;
operand2 = 0;
result = 0;
flagAction = 0;

}

Також замінюємо всі обнулення змінних на різних ділянках коду викликом цього методу.

Тепер візьмемося за обробку натискань додаткових кнопок. У цьому уроці напишемо код обробки кнопок “Отримання кореня”, “Квадрат числа” і “Стерти останній символ”. Обробку точки залишаємо на наступний урок, тому що тема доволі об’ємна.

Запускаємо програму. Спробуйте розподіл з дробовим і цілочисельним результатом.

У switch в методі onClick додаємо:


case R.id.btPoint:
   break;
case R.id.btSquare:
if(flagAction == 0){
   result = Math.pow(operand1, 2);
   showNumber(result);
   clearVariables();
}else{
   Toast.makeText(this, R.string.other_operation,                                       
   Toast.LENGTH_LONG).show();
}
break;
case R.id.btRadical:
if(flagAction == 0){
   result = Math.sqrt(operand1);
   showNumber(result);
   clearVariables();
}else{
   Toast.makeText(this, R.string.other_operation, 
   Toast.LENGTH_LONG).show();
}
break;
case R.id.btBack:
String Temp;
if(flagAction == 0){
   if(operand1%1 == 0){
   Temp = Integer.toString((int)operand1);
   }else{Temp = Double.toString(operand1);
}
Temp = Temp.substring(0, Temp.length()-1);
if(Temp.length() > 0){
   operand1 = Double.parseDouble(Temp);
   }else{operand1 = 0;
}
showNumber(operand1);
}else{
if(operand2%1 == 0){
   Temp = Integer.toString((int)operand2);
   }else{Temp = Double.toString(operand2);
}
   Temp = Temp.substring(0, Temp.length()-1);
if(Temp.length() > 0){
   operand2 = Double.parseDouble(Temp);
   }else{operand2 = 0;
}
   showNumber(operand2);
}
break;

Перший case порожній – там буде обробка натискання точки. У другому і третьому перевіряємо чи не була натиснута кнопка іншої операції, якщо немає, здійснюємо операцію над першим операндом (зводимо в квадрат або витягаємо корінь), виводимо результат, Обнуляємо змінні, інакше повідомляємо користувачеві, що обрана інша операція. Текст повідомлення знову беремо з string.xml, де додаємо ще одну константу:


   <String name = "other_operation"> Вже задана інша операція </ string>

У четвертому стираємо останню введену цифру. Можна було б вирішити цю задачу діями з числовими змінними, але мені здалося, що код вийде великим. Тому я вирішив видаляти останній символ з проміжної строкової змінної, а потім повертати її в double. Можливо хтось вважатиме такий підхід костилем, але мені так більше подобається. Алгоритм такий – перевіряємо flagAction, якщо він дорівнює нулю, кнопка дії ще не натиснута – редагуємо перший операнд, інакше другий. Потім перевіряємо, чи є у числа дрібна частина. Залежно від результату, формуємо строкову змінну. Видаляємо з неї останній символ. Перевіряємо не порожня рядок (вона буде порожньою в разі, якщо до видалення рядок складалася з одного символу). Якщо не порожня парсимо її в double. Якщо порожня просто присвоюємо нуль.

Запускаємо в емуляторі. Тестуємо нові кнопки.

Лістинг MainActivity.java


package ru.urok.super_calc;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements OnClickListener {
//объявляем кнопки и текстовое поле
Button btOne, btTwo, btThree, btFour, btFive;
Button btSix, btSeven, btEight, btNine, btZero;
Button btPlus, btMinus, btMulti, btDiv, btEqual, btClear;
Button btPoint, btSquare, btRadical, btBack;
TextView tvLCD;

//объявляем переменные для вычислений
double operand1, operand2;
int flagAction;
double result;
SharedPreferences sp;
Boolean add_button;
String style_calc;
LinearLayout llExtra;

int[] bt_ids;
Button[] bt_array;
int len;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   sp = PreferenceManager.getDefaultSharedPreferences(this);
   tvLCD = (TextView) findViewById(R.id.tvLCD);
   llExtra = (LinearLayout) findViewById(R.id.llExtra);
bt_ids = new int[]{
   R.id.btOne, R.id.btTwo, R.id.btThree, R.id.btFour, R.id.btFive,
   R.id.btSix, R.id.btSeven, R.id.btEight, R.id.btNine, R.id.btZero,
   R.id.btPlus, R.id.btMinus,R.id.btMulti, R.id.btDiv, R.id.btEqual,
   R.id.btClear, R.id.btPoint, R.id.btSquare, R.id.btRadical, R.id.btBack
};

bt_array = new Button[]{
   btOne, btTwo, btThree, btFour, btFive, btSix, btSeven,
   btEight, btNine,btZero, btPlus, btMinus, btMulti, btDiv, btEqual,
   btClear, btPoint, btSquare, btRadical, btBack
};
   len = bt_array.length;

for(int i = 0; i < len; i++){
   bt_array[i] = (Button) findViewById(bt_ids[i]);
   bt_array[i].setOnClickListener(this);
}

//Оголошуємо змінні
clearVariables();

//Встановимо значення першого операнду в текстове поле
showNumber(operand1);
}

protected void onResume() {
   add_button = sp.getBoolean("add_button", false);
   style_calc = sp.getString("style_calc", "1");
   int flag_visiblity = View.INVISIBLE;
   if(add_button)flag_visiblity = View.VISIBLE;
   llExtra.setVisibility(flag_visiblity);
   super.onResume();
}

@Override
public boolean onCreateOptionsMenu(Menu menu){
   getMenuInflater().inflate(R.menu.main, menu);
   return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item){
   switch(item.getItemId()){
      case R.id.options_menu:
      startActivity(new Intent(this, PrefActivity.class));
   break;
   case R.id.about_menu:
      startActivity(new Intent(this, AboutActivity.class));
   break;
   }
   return super.onOptionsItemSelected(item);
}

@Override
public void onClick(View v) {
   switch(v.getId()){
   case R.id.btOne:
   ClickNumber(1);
   break;
   case R.id.btTwo:
   ClickNumber(2);
   break;
   case R.id.btThree:
   ClickNumber(3);
   break;
   case R.id.btFour:
   ClickNumber(4);
   break;
   case R.id.btFive:
   ClickNumber(5);
   break;
   case R.id.btSix:
   ClickNumber(6);
   break;
   case R.id.btSeven:
   ClickNumber(7);
   break;
   case R.id.btEight:
   ClickNumber(8);
   break;
   case R.id.btNine:
   ClickNumber(9);
   break;
   case R.id.btZero:
   ClickNumber(0);
   break;
   case R.id.btPlus:
   if(flagAction == 0)flagAction = 1;
   break;
   case R.id.btMinus:
   if(flagAction == 0)flagAction = 2;
   break;
   case R.id.btMulti:
   if(flagAction == 0)flagAction = 3;
   break;
   case R.id.btDiv:
   if(flagAction == 0)flagAction = 4;
   break;
   case R.id.btPoint:
   break;
   case R.id.btSquare:
   if(flagAction == 0){
      result = Math.pow(operand1, 2);
      showNumber(result);
      clearVariables();
      }else{
         Toast.makeText(this, R.string.other_operation, Toast.LENGTH_LONG).show();
   }
   break;
   case R.id.btRadical:
   if(flagAction == 0){
      result = Math.sqrt(operand1);
      showNumber(result);
      clearVariables();
      }else{
         Toast.makeText(this, R.string.other_operation, Toast.LENGTH_LONG).show();
      }
   break;
   case R.id.btBack:
   String Temp;
   if(flagAction == 0){
      if(operand1%1 == 0){
         Temp = Integer.toString((int)operand1);
      }else{
         Temp = Double.toString(operand1);
      }
      Temp = Temp.substring(0, Temp.length()-1);
      if(Temp.length() > 0){
          operand1 = Double.parseDouble(Temp);
      }else{operand1 = 0;
}
   showNumber(operand1);
   }else{
   if(operand2%1 == 0){
      Temp = Integer.toString((int)operand2);
   }else{Temp = Double.toString(operand2);
}
   Temp = Temp.substring(0, Temp.length()-1);
   if(Temp.length() > 0){
      operand2 = Double.parseDouble(Temp);
   }else{operand2 = 0;
}
   showNumber(operand2);
}
   break;
   case R.id.btEqual:
   switch(flagAction){
      case 1:
         result = operand1 + operand2;
   break;
   case 2:
      result = operand1 - operand2;
   break;
   case 3:
      result = operand1 * operand2;
   break;
   case 4:
      result =(double) operand1 / (double) operand2;
   break;
   default:
      Toast.makeText(this, "Операция не задана", Toast.LENGTH_LONG).show();
}

if(flagAction != 0){
   showNumber(result);
   clearVariables();
}
   break;
   case R.id.btClear:
   clearVariables();
   showNumber(operand1);
   break;
   }
}

private void ClickNumber(int num){
   if(flagAction == 0){
      operand1 = operand1*10 + num;
      showNumber(operand1);
   }else{
      operand2 = operand2*10 + num;
      showNumber(operand2);
   }
}

private void showNumber(double num){
   if(num%1 == 0){
      tvLCD.setText(Integer.toString((int) num));
   }else{
      tvLCD.setText(Double.toString(num));
   }
}

private void clearVariables(){
   operand1 = 0;
   operand2 = 0;
   result = 0;
   flagAction = 0;
   }
}