Архив метки: android studio

Как использовать RecyclerView в Android

С выходом новой версии Android и привнесением в мир нового течения Material Design-а, появились и обновлённые, более современные версии старых вьюшек. Одно из таких обновлений затронуло и привычны вид отображения списка — ListView, на замену которого пришёл более быстрый и многофункциональный RecyclerView.

Главным отличием RecyclerView является создания всех элементов списка единожды, в отличии от ListView, где каждая View в списке создавалась отдельно для каждого из элементов списка, что в свою очередь приводило к огромному использованию памяти при создании достаточно больших списков.

Как начать работать с RecyclerView.

Для начала необходимо подключить библиотеку RecyclerView. Сделать это можно либо прописав зависимости в Gradle файл, либо же нажав кнопку F4 на нашем проекте и перейдя в вкладку «Dependencies». Так же подключим библиотеку CardView, так как элементы списка будем отображать в виде карточек и библиотеку Picasso, с помощью которой подгрузим изображения.

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:cardview-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'
compile 'com.squareup.picasso:picasso:2.3.2'
}

Теперь создадим отображение нашего списка, поместив в layout  активности наш RecyclerView.
activity_main.xml

    public String getUniverse(){return universe;}
public String getImage() {return image;}

}

Создавать элементы списка нам будет наш кастомный адаптер, который будет создавать представление элементов для всего списка сразу, а не для для каждого по отдельности и работа с которым не отличается от работы с кастомными адаптерами при использовании ListView.
HeroAdapter.java

package com.awesomedevelop.recyclerview;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;

import java.util.ArrayList;

/**
* Created by Taras on 30.01.2015.
*/
public class HeroAdapter extends RecyclerView.Adapter {

private ArrayList heroDataSet;
public Context mContext;
private int lastPosition = -1;
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView textName;
TextView textUniverse;
ImageView imageHero;

public MyViewHolder(View itemView){
super (itemView);
this.imageHero = (ImageView)itemView.findViewById(R.id.image);
this.textName = (TextView)itemView.findViewById(R.id.hero_name);
this.textUniverse = (TextView)itemView.findViewById(R.id.hero_universe);
}
}



public HeroAdapter(Context context, ArrayList heroes){
this.heroDataSet= heroes;
mContext=context;
}


@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.hero_card, parent, false);
MyViewHolder myViewHolder = new MyViewHolder(view);

return myViewHolder;
}




@Override
public void onBindViewHolder(final MyViewHolder holder, final int listPosition) {

final TextView textViewName = holder.textName;
final TextView textViewUniverse = holder.textUniverse;
ImageView imageViewHero = holder.imageHero;
textViewName.setText(heroDataSet.get(listPosition).getName());
textViewUniverse.setText(heroDataSet.get(listPosition).getUniverse());

String src = heroDataSet.get(listPosition).getImage();
Picasso.with(mContext)
.load("file:///android_asset/images/"+src+".jpg")
.resize(300, 300)
.into(imageViewHero);
}

@Override
public int getItemCount() {
return heroDataSet.size();
}

}

В данном примере используются изображения, хранящиеся в папке асетов, но использовать можно (по необходимости) любое расположение. Например если используя библиотеку Picasso вы укажете путь к онлайновому расположению изображений, они будут подгружаться только для тех элементов списка, которые будут отображены на экране в данный момент, а не для всего списка сразу  при его построении.

Теперь нам осталось только заполнить наш массив данных и передать его адаптеру. Выполняем это в активности, которая будет отображать список, в данном случае MainActivity.java

package com.awesomedevelop.recyclerview;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;

import java.util.ArrayList;


public class MainActivity extends ActionBarActivity {
private static ArrayList heroes;
private static RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
private static RecyclerView.Adapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);

fetchHeroes(); //Заполняем массив
adapter = new HeroAdapter(MainActivity.this,heroes); //Инициализируем наш адаптер
recyclerView.setAdapter(adapter); // Устанавливаем адаптер

}

//Заполняем массив
public void fetchHeroes(){
heroes = new ArrayList();

heroes.add(new HeroData("Зелёный Фонарь","DC comics","greenlantern"));
heroes.add(new HeroData("Джокер","DC comics","joker"));
heroes.add(new HeroData("Джона Хекс","DC comics","jonah-hex"));
heroes.add(new HeroData("Папа Миднайт","DC comics","glav"));
heroes.add(new HeroData("Ворона","DC comics","raven"));
heroes.add(new HeroData("Чёрная Вдова","Marvel","glavnaya"));
heroes.add(new HeroData("Капитан Америка","Marvel","cap_america"));
heroes.add(new HeroData("Космический Халк","Marvel","cosmic_hulk"));
heroes.add(new HeroData("Призрачный Гонщик","Marvel","ghost_rider"));


}





@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}
}

Использование RecyclerView значительно улучшает работу со списками, помимо оптимизации использования памяти его использование позволяет создавать более сложные элементы списка, которые могут быть абсолютно любыми, вплоть до создания полноценных экранов в виде элементов списка.

Проект на github.

Автор: Taras Neduiev

Собственная база данных на Android с возможностью обновления

Скорее всего вы уже находили множество примеров создания собственной базы данных, которые являются копированием друг-друга и преимущественное большинство которых просто не обновляются. Потому я решил выложить пример использование заранее созданной БД, с простой реализацией обновления данных.

Действительно, при поиске действительно рабочего примера я не нашёл ничего, все примеры найденные мною были практически одинаковые и несомненно работали, но когда возникла необходимость обновить базу данных с новой версией приложения — не произошло абсолютно ничего. 

Почему не работает обычный способ.

В примерах которые можно найти в интернете в большем обилии, при обращении к вашей БД, приходится вызывать её с помощью метода createDatabase(), которая копирует вашу заранее созданную базу в папку приложения. Но при вызове данного метода Android не обращается к методу onUpdate() , потому даже если вы измените версию базы, ничего не произойдёт, так как не произойдёт сравнение её версий. 
Метод onUpdate() вызывается только в случае обращения к базе данных с помощью методов getWritableDatabase() или getReadableDatabase(), в этом случае база создается и копируется если не существовала ранее и вызывается метод onUpdate(),если база уже существовала. Но если вы попробуете в методе onUpdate() удалить прошлую версию БД — приложение закроется с крешем, так как метод обновления будет вызван (а значит и база будет удалена) раньше чем будет создана новая, а потому получить доступ к несуществующей в этот момент базе не удастся.

Как обновить собственную базу данных.

Для того, чтобы удалить старую и обновить БД  придется вручную контролировать её версию и сделать это до вызова метода onUpdate(), чтобы избежать попытки её открытия до её фактического создания. Для этого будем использовать собственный метод обновления, а версии хранить и контролировать с помощью SharedPreferences.
Для использования собственной базы данных в Android создайте новый класс ExternalDbOpenHelper и поместите в него приведённый ниже код.



import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.preference.PreferenceManager;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
* Created by AwesomeDevelop on 24.12.2014.
*/
public class ExternalDbOpenHelper extends SQLiteOpenHelper
{
private static final String DATABASE_NAME = "db.sqlite3"; // Название файла с БД
private static final int DATABASE_VERSION = 1; //Версия БД
private static final String SP_KEY_DB_VER = "db_ver";
private final Context mContext;

public ExternalDbOpenHelper(Context context, String DB_NAME) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
initialize();
}

/**
* Инициализация БД. Создание новой если ранее не существовала.
*/
private void initialize() {
if (databaseExists()) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(mContext);
int dbVersion = prefs.getInt(SP_KEY_DB_VER, 1);
if (DATABASE_VERSION != dbVersion) {
File dbFile = mContext.getDatabasePath(DATABASE_NAME);
if (!dbFile.delete()) {
// Log.w(TAG, "Невозможно обновить БД");
}
}
}
if (!databaseExists()) {
createDatabase();
}
}

/**
* Проверка существования файла БД. Если существует - возвращает true.
* @return
*/
private boolean databaseExists() {
File dbFile = mContext.getDatabasePath(DATABASE_NAME);
return dbFile.exists();
}

/**
* Создание БД, копирование файла из Assets.
*/
private void createDatabase() {
String parentPath = mContext.getDatabasePath(DATABASE_NAME).getParent();
String path = mContext.getDatabasePath(DATABASE_NAME).getPath();

File file = new File(parentPath);
if (!file.exists()) {
if (!file.mkdir()) {
// Log.w(TAG, "Невозможно создать папку БД");
return;
}
}

InputStream is = null;
OutputStream os = null;
try {
is = mContext.getAssets().open(DATABASE_NAME);
os = new FileOutputStream(path);

byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
os.flush();
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = prefs.edit();
editor .putInt(SP_KEY_DB_VER, DATABASE_VERSION);
editor.commit();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@Override
public void onCreate(SQLiteDatabase db) {
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion,
int newVersion) {
}
}


Для работы данного класса, вам необходимо только указать название файла с созданной вами базой данных и её версию. Как только версия БД будет увеличена — старая её версия будет удалена, а новый файл БД скопирован. Но не пробуйте понизить версию, как и при обычном использовании будет получена ошибка о невозможности выполнить DownGrade и приложение будет закрыто. 
Работа с БД осуществляется стандартными для Android способами, потому вы можете работать с ней, как с стандартной Sqlite базой.
Открытие БД.

ExternalDbOpenHelper dbOpenHelper = new ExternalDbOpenHelper(this, DB_NAME);

SQLiteDatabase database= dbOpenHelper.getWritableDatabase();

Получить данные можно, к примеру, вот так:

data = new ArrayList();

Cursor dataCursor = database.query(
TABLE_NAME,
new String[] { DATA_NAME,DATA_IMAGE},
null, null, null, null,
DATA_NAME);
dataCursor.moveToFirst();


if(!dataCursor.isAfterLast()){
do {
String image =dataCursor.getString(1);
String name = dataCursor.getString(0);
data.add(new BrandData(name,image));

} while (dataCursor.moveToNext());
}
dataCursor.close();

И так, для использование заранее созданной базы данных необходимо:
  1. Создать файл базы данных Sqlite с любым наполнением, о том как это сделать можно прочитать тут, а для создания использовать Navicat .
  2. Поместить созданный файл в папку Assets.
  3. Создать класс ExternalDbOpenHelper и поместить в него код, приведённый в статье.
  4. Использовать вашу БД стандартными способами, описанными в официальной документации

Автор: Taras Neduiev

Стоит ли переходить на Android studio ?

Уже прошел год с дня, когда мир познакомился с новой IDE от Google – Android Studio, но по сей день она так и не вышла из беты. Но если еще год назад её можно было установить только для того, чтоб полюбоваться гугловскими решениями в дизайне, то сегодня уже можно серьезно задумываться – стоит ли переходить на Android Studio?


Очень долго я не мог бросить родной и привычный Eclipse и полностью отдаться Google-религии. В течении года, по мере выхода новых версий студии, я её устанавливал, кликал по кнопочкам, вертел конструктор интерфейса, но всегда что-то находил, что не устраивало, в особенности ужаснейшие тормоза в работе самого интерфейса, серьезно, в сырых версиях невозможно было слово нацарапать чтоб курсор не замер после каждого символа. Но вот, с версии 0.8., я все же поборол свою любовь к привычному и полностью перешёл на новую IDE. Дабы упростить принятие решения таким же сомневающимся как и я — приведу плюсы и минусы перехода к студии, и начну, пожалуй, с минусов.

МИНУСЫ.

1. Багоподобный Gradle.

Если раньше для меня это была всего-лишь забавная фраза, так как в Eclipse работать с ним не приходится, то сейчас я изредка попиваю чай с ромашкой. Большинство времени все работает хорошо, хотя процессы выполняются неоправданно долго, так как постоянна синхронизация с Gradle значительно замедляет компиляцию проектов, по сравнению с еклипсом. Но вот временами можно попытаться скомпилить проект и …. и пойти гулять, а вернувшись обнаружить, что работа остановилась в там же, где вы её и видели в последний раз, так как процесс просто висит.

2. Тормоза.

Для меня это оказалось самым большим недостатком, потому что при частых подвисаниях окон, тормозах при работе SpellChecker-а — просто начинаешь выходить из себя, ведь собираешься же написать что-то легендарное, а интерфейс просто не дает и слова написать.

3. Различия в управлении (при переходе с Eclipse).

Тем, кто ранее работал в IntelliJ IDEA переход будет более безболезненным, добавление библиотек осуществляется так же, компиляция и её настройки тоже на месте, да и практически все остальное. А вот уроженцев еклипса я спешу расстроить… ВСЕ НЕ ТАК! По крайней мере для меня это именно так и показалось. Добавить библиотеку? Правой кнопкой мыши? – Не, не слышал, вписываем градлу dependencies. Хотя есть способ это сделать и через интерфейс, в свойствах проекта, но как-то не атмосферненько, при живом то градле. Весь код красный? Ошибки? Всё — одна сплошная ошибка? А вот и нет, вы просто открыли проект, но не синхронизировали его с Gradle, знаем что в клипсе такого не было, усаживайтесь поудобнее и начинайте хорошенько разбираться с этим самым градлом.

На этом, с счастью, минусы для меня заканчиваются, хотя еще пол года назад их было куда больше и были они намного серьезней, чем потеря время из-за притормаживания и поиска нужных функций.

Плюсы.

1. Google inc.

Самой огромным плюсом является конечно же то, что разработчиком данной IDE является та же корпорация, которая выпускает платформу, под которую будем писать. Это означает более удобную интеграцию всех необходимых функций для новых версий Android. Уже сейчас, находясь в бете, студия получила все плюшки работы с Material Desig-ом , вернее они были добавлены сразу же, при выходе еще сырой версии Android L developers preview. Да и в будущем, кто как не сами создатели, будут делать все, для ускорения создания приложений, которые будут нести им денюжку прямо в их швейцарский карман.

2. Встроенный SDK.

Да, я знаю что существуют версии и других IDE с уже встроенным android SDK , но добавлены они туда не так тесно как в студии. К примеру вам нужно запустить старый проект, с низким API level, который по ненадобности вы просто не качали, половина классов заливаются краской, количество ошибок в проекте неумолимо растёт и вы понимаете, что чего-то тут не хватает, но чего? Какой версии api нет? нужно разбираться. Подобной ситуации при использовании студии не возникает. При использовании любого элемента, которого у вас нет, она сама определить где и чего не хватает и выкинет окошко с уведомлением. Более того, все будет выбрано за в