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

Урок 8. Прикрашаємо інтерфейс.

Для зміни інтерфейсу нам знадобляться: 2 зображення для загального фону програми, 4 зображення кнопок (по 2 на кожен стиль – одна в звичайному вигляді, інша в натиснутому) і два калькуляторного шрифту для текстового поля. Все це можна знайти в інтернеті або зробити самостійно. Наприклад, кнопки для цього проекту я за пару хвилин зробив в програмі 3D Button Creator. Решту взяв в павутині. (Додаються файли – a_LCDNovaObl.ttf, FSCristal.ttf, blue_bt.png, blue_bt_press.png, pink_bt.png, pink_bt_press.png, space_back.jpg, gray_back.jpg – прикладені в архіві)

Зображення нам потрібно помістити в папку res / drawable проекту. Для цього правою кнопкою натискаємо на імені папки, вибираємо Import, потім FileSystem. Вибираємо папку джерело і потрібні файли, тиснемо Finish. Шрифти потрібно покласти в папку asset / fonts (папкуfonts попередньо потрібно створити New-> Folder)

Внесемо невеликі зміни у activity_main.xml: фонове зображення ми будемо причіплювати у коді до кореневого RelativeLayout, тому йому потрібно привласнити id. Також вирівняємо текст в tvLCD по правому краю і отцентруємо вертикально


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   ......
   android:id="@+id/rlCommon" >
<LinearLayout
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical" >
<TextView
   android:id="@+id/tvLCD"
   .......
   android:gravity="right|center_vertical"/>

Переходимо до роботи з MainActivity.java. Оголосимо дві змінні int – для зберігання розмірів кнопок, об’єкт типу Typeface – для роботи зі шрифтами і об’єкт RelativeLayout, до якого будемо причіплювати фон.


   int buttonW, buttonH;
   Typeface CF;
   RelativeLayout rlCommon;

Пов’язуємо rlCommon з макетом в onCreate:


   rlCommon = (RelativeLayout) findViewById (R.id.rlCommon);

Переходимо до методу onResume. Всі зміни інтерфейсу будемо робити тут. Якщо зробити це в onCreate, то зміни будуть застосовані тільки при перезапуску програми, тому що при поверненні до головного вікна з екрану налаштувань onCreate системою викликатися не буде, відразу викликається onResume.

Визначимо розміри екрану: отримуємо об’єкт Display, оголошуємо DisplayMetrics, отримуємо в DisplayMetrics розміри об’єкта Display.


   Display display = getWindowManager().getDefaultDisplay();
   DisplayMetrics metricsB = new DisplayMetrics();
   display.getMetrics(metricsB);

Від розмірів екрану віднімаємо внутрішні відступи кореневого RelativeLayout (група параметрів android: padding), а також від висоти потрібно відняти висоту заголовку програми та висоту статусбара – їх можна обчислити програмно (висота заголовка, наприклад, getWindow (). FindViewById (Window.ID_ANDROID_CONTENT). getTop (), висота статусбара складніше – в кілька рядків), але я вирішив не морочитися і вирахував свідомо більшу величину – 120 пікселів (вивів емпірично). Якщо між елементами будуть відступи, то потрібно враховувати і їх. Ділимо отримані розміри на кількість елементів на екрані по ширині і висоті. По ширині – 4 кнопки, по висоті – з урахуванням текстового поля 6. Отримуємо розміри кнопок, вірніше ті розміри, які повинні бути у них на весь екран повністю.


   buttonW = (metricsB.widthPixels - 32) / 4;
   buttonH = (metricsB.heightPixels - 32 - 120) / 6;

Отримуємо параметри tvLCD. Міняємо параметр висоти і застосовуємо змінені параметри до тестового полю. Змінюємо розмір тексту в текстовому полі.


   LinearLayout.LayoutParams param_tv = (LinearLayout.LayoutParams)
   tvLCD.getLayoutParams();
   param_tv.height = (int)(buttonH/1.3);
   tvLCD.setLayoutParams(param_tv);
   tvLCD.setTextSize((float) (buttonH/2.2));

Потім у циклі для кожного елементу масиву кнопок bt_array міняємо ширину, висоту і розмір шрифту.


for(int i = 0; i < len; i++){
   LinearLayout.LayoutParams param_bt = (LinearLayout.LayoutParams)bt_array[i].getLayoutParams();
   param_bt.height = buttonH;
   param_bt.width = buttonW;
   bt_array[i].setLayoutParams(param_bt);
   bt_array[i].setTextSize((float) (buttonH/4));
}

Переходимо до зміни стилю інтерфейсу. Спочатку нам потрібно створити у папці drawable два XML файлу описують вид кнопок. Для цього натискаємо правою кнопкою на папці drawable, обираємо New-> Android XML File. Кореневий елемент обираємо selector, задаємо ім’я файлу (у нас blue_button і pink_button), тиснемо Finish. Потім нам потрібно описати всередині селектора які картинки буде зображуватися на кнопці в натиснутому і не натиснутому стані. Ось текст селекторів:

blue_button.xml


<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="@drawable/blue_bt_press" android:state_pressed="true" />
<item android:drawable="@drawable/blue_bt" />
</selector>

pink_button.xml


<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="@drawable/pink_bt_press" android:state_pressed="true" />
<item android:drawable="@drawable/pink_bt" />
</selector>

Повертаємося до MainActivity.java. Продовжуємо у методі onResume. Перевіряємо значення змінної style_calc, яку ми отримали з налаштувань. Залежно від результату, застосовуємо для кореневого лайаута фонову картинку, для текстового поля змінюємо шрифт, у циклі змінюємо селектор для елементів масиву кнопок bt_array.


if(style_calc.equals("1")){
   rlCommon.setBackgroundResource(R.drawable.gray_back);
   CF = Typeface.createFromAsset(getAssets(), "fonts/FSCristal.ttf");
   tvLCD.setTypeface(CF);
   for(int i = 0; i < len; i++){
      bt_array[i].setBackgroundResource(R.drawable.blue_button);
   }
}else{
   rlCommon.setBackgroundResource(R.drawable.space_back);
   CF = Typeface.createFromAsset(getAssets(), "fonts/a_LCDNovaObl.ttf");
   tvLCD.setTypeface(CF);
   for(int i = 0; i < len; i++){
      bt_array[i].setBackgroundResource(R.drawable.pink_button);
}

Все. Запускаємо і тестуємо.

Повний лістинг MainActivity.java без коментарів:


package ru.urok.super_calc;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.view.Display;
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.RelativeLayout;
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;
   RelativeLayout rlCommon;
   int[] bt_ids;
   Button[] bt_array;
   int len;
   Boolean flagPoint;
   int degree_before, degree_after;
   int buttonW, buttonH;
   Typeface CF;

@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);
   rlCommon = (RelativeLayout) findViewById(R.id.rlCommon);
   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);
   Display display = getWindowManager().getDefaultDisplay();
   DisplayMetrics metricsB = new DisplayMetrics();
   display.getMetrics(metricsB);
   buttonW = (metricsB.widthPixels - 32)/4;
   buttonH = (metricsB.heightPixels - 32 - 120)/6;
   LinearLayout.LayoutParams param_tv = (LinearLayout.LayoutParams) tvLCD.getLayoutParams();
   param_tv.height = (int)(buttonH/1.3);
   tvLCD.setLayoutParams(param_tv);
   tvLCD.setTextSize((float) (buttonH/2.2));
   for(int i = 0; i < len; i++){
   LinearLayout.LayoutParams param_bt = (LinearLayout.LayoutParams) bt_array[i].getLayoutParams();
   param_bt.height = buttonH;
   param_bt.width = buttonW;
   bt_array[i].setLayoutParams(param_bt);
   bt_array[i].setTextSize((float) (buttonH/4));
}
   if(style_calc.equals("1")){
      rlCommon.setBackgroundResource(R.drawable.gray_back);
      CF = Typeface.createFromAsset(getAssets(), "fonts/FSCristal.ttf");
      tvLCD.setTypeface(CF);
      for(int i = 0; i < len; i++){
         bt_array[i].setBackgroundResource(R.drawable.blue_button>);}
      }else{
         rlCommon.setBackgroundResource(R.drawable.space_back);
         CF = Typeface.createFromAsset(getAssets(), "fonts/a_LCDNovaObl.ttf");
         tvLCD.setTypeface(CF);
         for(int i = 0; i < len; i++){
            bt_array[i].setBackgroundResource(R.drawable.pink_button);
         }
      }
      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;
         clearDegree();
      }
      break;
   case R.id.btMinus:
      if(flagAction == 0){
         flagAction = 2;
         clearDegree();
      }
      break;
   case R.id.btMulti:
      if(flagAction == 0){
         flagAction = 3;
         clearDegree();
      }
      break;
   case R.id.btDiv:
      if(flagAction == 0){
         flagAction = 4;
         clearDegree();
      }
      break;
   case R.id.btPoint:
      flagPoint = true;
   break;
   case R.id.btSquare:
      if(flagAction == 0){
         result = Math.pow(operand1, 2);
   if(result%1 != 0)degree_after = 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);
   if(result%1 != 0)degree_after = 2;
      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 & degree_after == 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 & degree_after == 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);
   }

if(degree_after > 0){
   degree_after--;
   }else{
   flagPoint = false;
}
   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 = operand1 / operand2;
   break;
   default:
      Toast.makeText(this, R.string.no_operation, Toast.LENGTH_LONG).show();
   }
   if(result%1 != 0)degree_after = 2;
   if(flagAction != 0){
      showNumber(result);
      clearVariables();
   }
   break;
   case R.id.btClear:
   clearVariables();
   showNumber(operand1);
   break;
   }
}

private void ClickNumber(int num){
   if(flagAction == 0){
      if(flagPoint){
         degree_after++;
            if(degree_after <= 2){
               operand1 += num / Math.pow(10, degree_after);
            }else{
               degree_after = 2;
               Toast.makeText(this, R.string.limit_after, Toast.LENGTH_LONG).show();
         }
      }else{
         degree_before++;
         if(degree_before <= 8){
            operand1 = operand1*10 + num;
         }else{
            degree_before = 8;
               Toast.makeText(this, R.string.limit_before, Toast.LENGTH_LONG).show();
      }
}
   showNumber(operand1);
   }else{
      if(flagPoint){
      degree_after++;
      if(degree_after <= 2){
         operand2 += num / Math.pow(10, degree_after);
      }else{
         degree_after = 2;
         Toast.makeText(this, R.string.limit_after, Toast.LENGTH_SHORT).show();
      }
   }else{
      degree_before++;
      if(degree_before <= 8){
         operand2 = operand2*10 + num;
   }else{
      degree_before = 8;
      Toast.makeText(this, R.string.limit_before, Toast.LENGTH_SHORT).show();
   }
}
   showNumber(operand2);
   }
}

private void showNumber(double num){
   String corr = "";
   if(num > Integer.MAX_VALUE){
      tvLCD.setText("ERROR");
      Toast.makeText(this, R.string.degree_overflow, Toast.LENGTH_LONG).show();
   }else{
      if(num%1 == 0 & degree_after == 0){
         tvLCD.setText(Integer.toString((int) num));
   }else{
      int part_int = (int) num;
      int part_frac = (int) Math.round(num%1 * Math.pow(10, degree_after));
      if(degree_after == 2 & part_frac < 10)corr = "0";
         tvLCD.setText(part_int + "." + corr + part_frac);
      }
   }
}

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

private void clearDegree(){
   degree_before = 0;
   degree_after = 0;
   flagPoint = false;
   }
}