Чему научит данная статься?
Статья окажется полезной для первопроходцев и для веб-разработчиков, это полная инструкция для разработки с нуля и публикации. Разбирается реальное приложение «Учет расходов», размещенное в google play. Это мое первое приложения, передача знаний от новичка к заблудшему.
- Понимаем азы
- Работаем с базой данных
- Делаем верстку
- Программируем
Как это произошло?
Написание данного приложения, было спровоцировано отсутствием необходимого функционала у имеющихся аналогов.
Есть множество подобных приложений, но к сожалению, они перегружены ненужными функциями.
Что требовалось и получилось?
Требования к моему приложению были следующие:
- Учет расхода за текущий месяц
- Ввод расхода, используя крупную клавишу «ввести расход»
- Обзор сумм расхода за этот месяц
- Обзор общей суммы затрат за данный месяц
- Обзор суммы за выбранный месяц
Пару слов о рабочем процессе
Готов проклинать вечно, верстку интерфейса с использование xml. На html/css я могу сверстать все что угодно, но когда дошло до подобной верстки, боевой настрой улетучился. Не хватаем в сети вводного материала на тему: xml интерфейсы для тех кто знает html/css. Я надеюсь ситуация в скором времени измениться. Не понятна логика размещения элементов, все перекашивается и не слушается. Убивает не способность задавать отдельный «бордер» (ввер, левый) для элементов интерфейса.
Разработка
Вы установили eclipse, плагины и вам удалось (после 10 минутных потугов на топовом ПК) запустить эмулятор? Теперь перед вами открываются возможность разработки под Android, одну из самых популярных операционных систем в мобильном мире.
Для начала создадим наш первый activity, который будет точкой входа в приложение activity_main.xml. Нам предлагается некая MVC структура:
com.laguna.sa — название моего package
Логика располагается в: название_проекта/src/com.laguna.sa/ *
Представления располагаются в: res/layout/ *
При создании activity, формируется файл логики и представления. При верстке нам предлагают множество непонятных элементов, из которых я выбрал LinearLayout. LinearLayout позволяет размешать в себе элементы. На выбор вертикаль и горизонталь. Указывается так android:orientation=«vertical». Для моих потребностей в данном приложении этого хватает, я даже border эмитировал используя LinearLayout высотой 3dp. Что то на подобие div в html.
activity_main.xml листинг часть 1:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#6c6c75" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity" > <LinearLayout android:layout_width="fill_parent" android:layout_height="3dp" android:background="#99CC00" android:orientation="horizontal" > </LinearLayout> </LinearLayout>
Сейчас тут содержится первый родительский слой с атрибутами android:layout_width и android:layout_height которые заданы как fill_parent, что указывает слою заполнить все и вся. Так же мы задаем расположения всего содержимого по центру android:gravity=«center».
Далее я создаю сексуальную зеленую полосу используя LinearLayout с высотой 3dp. Конечно не для этого задумывался данный элемент, но 1 раз так согрешить можно.
<LinearLayout android:layout_width="fill_parent" android:layout_height="3dp" android:background="#99CC00" android:orientation="horizontal" > </LinearLayout>
На экране это выглядит так:
Следом за полосой, размещаю два поля для текста. Размещаю в новом LinearLayout, который делаю horizontal.
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#FFf" android:orientation="horizontal" > <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="3dp" android:layout_marginLeft="10dp" android:layout_marginRight="5dp" android:layout_marginTop="6dp" android:text="@string/text2" android:textSize="16sp" /> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="3dp" android:layout_marginTop="6dp" android:text="date" android:textSize="16sp" /> </LinearLayout>
Тут все логически понятно, но стоит обратить внимание на заполнение текстом данных элементов. В нашем проекте есть папка values в которой хранится файл strings.xml. Данный файл содержит строки, который благодаря android:text="@string/text2" подгружает выше приведенный TextView. Текст можно указать непосредственно в коде, но в таком случае, будет мешать надоедливая табличка с ошибкой.
Для файла строк существует визуальный редактор, который позволяет редактировать строки не копаясь в коде. Позволительно добавлять не только строки но и прочие ресурсы. Но как понял я, строки и стили хранить нужно отдельно.
Естественно элементами интерфейса можно управлять программно. Приведу пример из проекта, где в тестовое поле вставляется актуальная дата.
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); String currentDateandTime = sdf.format(new Date()); TextView textView1 = (TextView) findViewById(R.id.textView1); textView1.setText(currentDateandTime);
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#6c6c75" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity" > <LinearLayout android:layout_width="fill_parent" android:layout_height="3dp" android:background="#99CC00" android:orientation="horizontal" > </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#FFf" android:orientation="horizontal" > <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="3dp" android:layout_marginLeft="10dp" android:layout_marginRight="5dp" android:layout_marginTop="6dp" android:text="@string/text2" android:textSize="16sp" /> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="3dp" android:layout_marginTop="6dp" android:text="date" android:textSize="16sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="0dp" android:layout_marginRight="0dp" android:layout_marginTop="0dp" android:background="#FFf" android:gravity="bottom" android:orientation="vertical" > <EditText android:id="@+id/amount" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20sp" android:ems="10" android:hint="@string/amount_of_expense" > </EditText> <Button android:id="@+id/button2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:background="@drawable/mybtn_style_selector" android:onClick="makebutton_Click" android:text="@string/makebutton" /> <Button android:id="@+id/button3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_marginTop="2dp" android:background="@drawable/mybtn_style_selector" android:onClick="costs_Click" android:text="@string/costs" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="80sp" android:layout_gravity="center_vertical" android:layout_marginTop="0dp" android:background="#fff" android:orientation="horizontal" > <TextView android:id="@+id/amount_per_month_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="20dp" android:layout_marginTop="20dp" android:text="За этот месяц:" android:textSize="20sp" /> <Button android:id="@+id/amount_per_month" android:layout_width="140dp" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_marginTop="40dp" android:onClick="reload_Click" android:background="@drawable/button321" android:text="" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="80sp" android:layout_gravity="center_vertical" android:layout_marginTop="0dp" android:background="#fff" android:orientation="horizontal" > <TextView android:id="@+id/amount_per_month_text2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="20dp" android:layout_marginTop="0dp" android:text="Выбор месяца:" android:textSize="20sp" /> <Spinner android:id="@+id/spinner_month" android:layout_width="140dp" android:layout_height="wrap_content" android:layout_marginTop="20dp" > </Spinner> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="fill_parent" android:layout_marginTop="0dp" android:background="#FFf" android:gravity="bottom" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/mybtn_style_selector" android:onClick="howtousebutton_Click" android:text="@string/howtousebutton" /> </LinearLayout> </LinearLayout>
package com.laguna.sa; // тут импорт @SuppressLint("SimpleDateFormat") public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); String currentDateandTime = sdf.format(new Date()); TextView textView1 = (TextView) findViewById(R.id.textView1); textView1.setText(currentDateandTime); // общая сумма за этот месяц WorkWithDatabase wwd = new WorkWithDatabase(this); Cursor cursor = wwd.total_amount_for_this_month(); if(cursor.moveToFirst()) { Button amount_per_month = (Button ) findViewById(R.id.amount_per_month); amount_per_month.setText(""+cursor.getInt(0)+""); } String[] data = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}; // адаптер ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, data); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); final Spinner spinner = (Spinner) findViewById(R.id.spinner_month); spinner.setAdapter(adapter); // заголовок spinner.setPrompt("месяц"); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long l) { String month = spinner.getSelectedItem().toString(); set_selected_mont(month); } public void onNothingSelected(AdapterView<?> adapterView) { return; } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } public void makebutton_Click(View v){ // получаем дату TextView date = (TextView) findViewById(R.id.textView1); // получаем суммц EditText amount = (EditText) findViewById(R.id.amount); if(amount.toString() != "") { WorkWithDatabase wwd = new WorkWithDatabase(this); wwd.entry_costs(date, amount); // удаление цифр из поля amount.setText(""); reload_Click(v); } } // обнавление общей суммы за этот месяц public void reload_Click(View v){ WorkWithDatabase wwd = new WorkWithDatabase(this); Cursor cursor = wwd.total_amount_for_this_month(); if(cursor.moveToFirst()) { Button amount_per_month = (Button ) findViewById(R.id.amount_per_month); amount_per_month.setText(""+cursor.getInt(0)+""); } } public void set_selected_mont(String month){ WorkWithDatabase wwd = new WorkWithDatabase(this); Cursor cursor = wwd.total_amount_for_selected_month(month); if(cursor.moveToFirst()) { Button amount_per_month = (Button ) findViewById(R.id.amount_per_month); amount_per_month.setText(""+cursor.getInt(0)+""); } } // переход на просмотр расходов public void costs_Click(View v){ Intent intent = new Intent(MainActivity.this,CostsActivity.class); startActivity(intent); } // переход к инструкции public void howtousebutton_Click(View v){ Intent intent = new Intent(MainActivity.this, HowtouseActivity.class); startActivity(intent); } }
Особо сложных действий которые нуждаются в объяснениях тут нет, единственное обращу внимание на способ навигации в приложении:
// переход на просмотр расходов public void costs_Click(View v){ Intent intent = new Intent(MainActivity.this,CostsActivity.class); startActivity(intent); }
Осуществляется переход от одного Activity к другому.
База данных и работа с ней
В Android для хранения локальных данных я использовал самое простое решение — SQLite и класс помощник SQLiteOpenHelper, который выполняет за меня всю грязную работу. Для тех, кто работал, к примеру с MySql, не должно возникнуть особых трудностей в освоении и понимании.
SQLiteOpenHelper также контролирует первоначальное создание базы данных в файлов системе, при необходимости делает «апгрейд».
package com.laguna.sa; import java.text.SimpleDateFormat; import java.util.Date; import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.EditText; import android.widget.TextView; public class WorkWithDatabase extends SQLiteOpenHelper { // константы для конструктора private static final String DATABASE_NAME = "costs_database.db"; private static final int DATABASE_VERSION = 1; //название таблицы и столбцы public static final String TABLE_NAME = "costs"; public static final String UID = "_id"; public static final String DATE = "date"; public static final String AMOUNT = "amount"; public static final String MONTH = "month"; public WorkWithDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); // TODO Auto-generated constructor stub } // запрос для создания private static final String SQL_CREATE_ENTRIES = "create table if not exists " + TABLE_NAME + "( " + UID + " integer primary key autoincrement, " + DATE + " text not null, " + AMOUNT + " integer not null, " + MONTH + " integer not null);"; // запрос для удаления private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME; @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Удаляем предыдущую таблицу при апгрейде db.execSQL(SQL_DELETE_ENTRIES); // Создаём новый экземпляр таблицы onCreate(db); } // ------------------------------------------------------------------------------- // public void entry_costs(TextView date, EditText amount) { SQLiteDatabase wwd = this.getReadableDatabase(); // получение месяца SimpleDateFormat sdf = new SimpleDateFormat("MM"); String month = sdf.format(new Date()); // внесение данных ContentValues values = new ContentValues(); values.put("date", date.getText().toString()); values.put("amount", amount.getText().toString()); values.put("month", month.toString()); wwd.insert("costs", null, values); wwd.close(); } @SuppressLint("SimpleDateFormat") public Cursor obtaining_costs_for_this_month() { SQLiteDatabase wwd = this.getReadableDatabase(); SimpleDateFormat sdf = new SimpleDateFormat("MM"); String month = sdf.format(new Date()); String query = "SELECT * FROM costs WHERE month = "+ month +" ORDER BY _id DESC"; Cursor cursor = wwd.rawQuery(query, null); return cursor; } public Cursor total_amount_for_this_month() { SQLiteDatabase wwd = this.getReadableDatabase(); SimpleDateFormat sdf2 = new SimpleDateFormat("MM"); String month = sdf2.format(new Date()); String query = "SELECT SUM(amount) FROM costs WHERE month=" + month + ""; Cursor cursor = wwd.rawQuery(query, null); return cursor; } public Cursor total_amount_for_selected_month(String month) { SQLiteDatabase wwd = this.getReadableDatabase(); String query = "SELECT SUM(amount) FROM costs WHERE month=" + month + ""; Cursor cursor = wwd.rawQuery(query, null); return cursor; } }
Обратите внимание, что для вставки данных используется:
ContentValues values = new ContentValues();
Вставка данных про принципу столбец -> значение.
Получение данных, процесс не сложный. Стоит прочитать отдельно про Cursor. В моем случае, я скармливаю сырые запросы rawQuery и работаю с Cursor’ом.
Тут пример вывода данных, листинг файла который выводит суммы на отдельном экране.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:background="#fff" tools:context=".CostsActivity" > <ScrollView android:id="@+id/scrollView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:layout_alignParentTop="true" > <LinearLayout android:id="@+id/costslist" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > </LinearLayout> </ScrollView> </RelativeLayout>
package com.laguna.sa; import android.os.Build; import android.os.Bundle; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActionBar.LayoutParams; import android.app.Activity; import android.database.Cursor; import android.view.Menu; import android.widget.LinearLayout; import android.widget.TextView; public class CostsActivity extends Activity { @SuppressWarnings("deprecation") @SuppressLint("NewApi") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_costs); LinearLayout linearLayout = (LinearLayout)findViewById(R.id.costslist); int sdk = android.os.Build.VERSION.SDK_INT; WorkWithDatabase wwd = new WorkWithDatabase(this); Cursor cursor = wwd.obtaining_costs_for_this_month(); while (cursor.moveToNext()) { int amount = cursor.getInt(cursor.getColumnIndex("amount")); String date = cursor.getString(cursor.getColumnIndex("date")); TextView dateTv = new TextView(this); LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); llp.setMargins(0, 1, 0, 1); // llp.setMargins(left, top, right, bottom); //------------------------------------------------------------ if(sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) { dateTv.setBackgroundDrawable(getResources().getDrawable(R.drawable.test)); } else { dateTv.setBackground(getResources().getDrawable(R.drawable.test)); } //------------------------------------------------------------ dateTv.setLayoutParams(llp); dateTv.setPadding(4, 1, 2, 1); dateTv.setText(date + " - потрачено: " + amount); linearLayout.addView(dateTv); } cursor.close(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.costs, menu); return true; } }
В цикле проходим по всем значениям и получаем нужные. Там же формируем необходимое количество TextView, добавляем стилей и готово.
Программа жаловалась на методы, не поддерживаемые некоторыми версиями ОС. Пришлось прибегать к проверкам версий, для задания Background. Буду благодарен если в комментариях направят на путь истинный касательно setBackground.
Публикация и результат.
Публикация мне понравилась, все произошло быстро и без лишних хлопот. Великолепная консоль разработчика, интуитивно понятные настройки. Приложение размещенное вечером, было доступно уже ночью.
Само приложение:
ссылка на оригинал статьи http://habrahabr.ru/post/208344/
Добавить комментарий