Пример приложения с использованием библиотеки AQuery

Нас постоянноспрашивают, почему мы используем библиотеку AQuery в своих проектах. В конце концов нам надоело отвечать и мы решили показать, на что способна AQuery в бою.Но писать какой-то странный псевдокод в духе hello world скучно и неинтересно и поэтому мы решили сделать какое-нибудь небольшое, но полезное приложение. Недавно от Хабра отделился проект Мегамозг и в комментариях к новости высказывали предложение объединить RSS поток со всех ресурсов. Этим мы и займемся.

В конце получится такой прототип приложения IT News (rss с хабра, гиктаймс, мегамозга и с силиконруса/роем упорядоченные по дате):

image

Ссылки для торопыжек: github: github.com/recoilme/itnewsgoogle play: play.google.com/store/apps/details? id=org.freemp.itnewsСначала пара слов о самой библиотеке.

Библиотека предназначена в первую очередь для: — манипулирования UI элементами— работы с сетью— работы с изображениями

Это только то, что на поверхности.

Крохотная и без внешних зависимостей. Не навязывает свое использование, не конфликтует с другими библиотеками и не навязывает какого-то стиля при программировании. Вы просто закидываете jar файл и всё.Итак, по порядку:

Манипулирование UI элементами: пишите меньше, пишите быстрееКод без AQuery

public void renderContent (Content content, View view) { ImageView tbView = (ImageView) view.findViewById (R.id.icon); if (tbView!= null){ tbView.setImageBitmap (R.drawable.icon); tbView.setVisibility (View.VISIBLE); tbView.setOnClickListener (new OnClickListener () { @Override public void onClick (View v) { someMethod (v); } }); } TextView nameView = (TextView) view.findViewById (R.id.name); if (nameView!= null){ nameView.setText (content.getPname ()); } TextView timeView = (TextView) view.findViewById (R.id.time); if (timeView!= null){ long now = System.currentTimeMillis (); timeView.setText (FormatUtility.relativeTime (now, content.getCreate ())); timeView.setVisibility (View.VISIBLE); } TextView descView = (TextView) view.findViewById (R.id.desc); if (descView!= null){ descView.setText (content.getDesc ()); descView.setVisibility (View.VISIBLE); } } Код с AQuery

public void renderContent (Content content, View view) { AQuery aq = new AQuery (view); aq.id (R.id.icon).image (R.drawable.icon).visible ().clicked (this, «someMethod»); aq.id (R.id.name).text (content.getPname ()); aq.id (R.id.time).text (FormatUtility.relativeTime (System.currentTimeMillis (), content.getCreate ())).visible (); aq.id (R.id.desc).text (content.getDesc ()).visible (); } Причем никто не запрещает тут же рядом писать findviewbyid — миксуйте как Вам нравится. Код становится более лаконичным и легко читаемым как будто пишешь не на Яве, а на каком-то Groovy или Kotlin.

Работа с сетью. Гет, пост, мультипарт запросы. Динамическое связывание с активити. Гибкая система кеширования из коробки

AsyncAPI (далее я буду давать ссылки на wiki, чтобы не плодить энтропию)

Загрузка изображений. Кеширование, анимация, downsampling, манипулирование соотношением сторон — просто забудьте о проблемах с памятью и займитесь делом

ImageLoading

А также аутентификация через кучу ресурсов от фейсбука до твитера. Работа с локейшенами. Куча утилит для дебага, парсинг XML и так далее

великолепная документация с кучей примеров

Но это все слова, давайте попробуем в деле. Создаем пустой проект со следующими зависимостями: da72184aaf2f45e4942bbf9b5f41c3f9.png

Начнем с объявления AQuery, проверяем что все подключилось:

public class ActivityMain extends Activity {

private AQuery aq; private Activity activity;

@Override public void onCreate (Bundle savedInstanceState) { super.onCreate (savedInstanceState); setContentView (R.layout.main);

activity = this; aq = new AQuery (activity); AQUtility.setDebug (true);

} } Теперь добавим карточки, и проверим как все работает:

public class ActivityMain extends Activity {

private AQuery aq; private Activity activity; private RecyclerView gridView; private StaggeredGridLayoutManager mLayoutManager; private AdapterMain adapter;

@Override public void onCreate (Bundle savedInstanceState) { super.onCreate (savedInstanceState);

activity = this; aq = new AQuery (activity); AQUtility.setDebug (true);

gridView = new RecyclerView (activity); gridView.setHasFixedSize (true); mLayoutManager = new StaggeredGridLayoutManager (1, StaggeredGridLayoutManager.VERTICAL); gridView.setLayoutManager (mLayoutManager); gridView.setItemAnimator (new DefaultItemAnimator ()); getWindow ().setContentView (gridView);

adapter = new AdapterMain (activity, new String[]{»123»,»456»}); gridView.setAdapter (adapter); } } Адаптер

/** * Created by recoilme on 23/01/15. */ public class AdapterMain extends RecyclerView.Adapter {

private String[] data; private AQuery aq; private Activity activity;

public AdapterMain (Activity activity, String[] data) { this.activity = activity; this.data = data; aq = new AQuery (activity); } public static class ViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ViewHolder (View v) { super (v);

mTextView = (TextView) v.findViewById (R.id.articleTitle); } }

@Override public AdapterMain.ViewHolder onCreateViewHolder (ViewGroup parent, int viewType) { View v = LayoutInflater.from (parent.getContext ()) .inflate (R.layout.card, parent, false);

ViewHolder vh = new ViewHolder (v); return vh; }

@Override public void onBindViewHolder (ViewHolder viewHolder, int i) { aq.id (viewHolder.mTextView).text (data[i]); }

@Override public int getItemCount () { return data.length; } }

Лэйаут карточки:

android: textSize=»20dp» android: id=»@+id/siteurl» android: visibility=«gone»/>

Проверим, что получилось, должно быть примерно так: 98561c1b8a2047f993710daefad173be.jpg Теперь начинаем магию с AQuery — дергаем rss и парсим его: просто пишем aq.ajax (url, XmlDom.class, this, «onRequest») — AQuery сделает все остальное

public void request (String url) { aq.ajax (url, XmlDom.class, this, «onRequest»); }

public void onRequest (String url, XmlDom xml, AjaxStatus status) { if (status.getCode ()==200) { String logo = »; try { logo = xml.tags («url»).get (0).text (); } catch (Exception e) { e.printStackTrace (); }

List xmlItems = xml.tags («item»);

for (XmlDom xmlItem: xmlItems){ ClassItem item = new ClassItem (); String description = xmlItem.tag («description»).text (); item.setLogo (logo); item.setAuthor (xmlItem.tag («author»).text ()); item.setTitle (xmlItem.tag («title»).text ()); item.setDescription (description); item.setLink (xmlItem.tag («link»).text ()); String pubDate = xmlItem.tag («pubDate»).text (); Date date = new Date (); try { date = formatter.parse (pubDate); } catch (Exception e) { AQUtility.debug («errorParsingDate», e.toString ()); } item.setDate (date); String src = »; try { src = new XmlDom (»»+description+»»).tag («img»).attr («src»); if (src.startsWith (»//»)) { src = «http:»+src; } } catch (Exception e) { e.printStackTrace (); } item.setImg (src); items.add (item); } adapter.notifyDataSetChanged (); }

} В класс AjaxStatus приходит детальная информация о результатах выполнения запроса + в AQuery встроен простенький парсер XML. Нет необходимости беспокоиться о наличии активити на момент завершения запроса, AQuery сделает это за нас. Плюс модуль HTTP запросов гораздо гибче чем в примере выше, можно кастомизировать все, от хидеров до метода выполнения запроса. А если вам необходимо, например, закешировать запрос — просто добавляете параметр fileCache=true и время, на которое запрос должен быть закеширован. Есть функционал для инвалидации кеша в случае ошибки, например, и так далее.Мы же пока вернемся к адаптеру, и обогатим rss поток функционалом отображения картинок. Тем более что с AQuery это не просто, а очень просто:

public class AdapterMain extends RecyclerView.Adapter {

private ArrayList data; private AQuery aq; private Activity activity; private DateFormat formatter = new SimpleDateFormat («EEE, dd MMM yyyy HH: mm», Locale.getDefault ());

public AdapterMain (Activity activity, ArrayList data) { this.activity = activity; this.data = data; aq = new AQuery (activity); }

public static class ViewHolder extends RecyclerView.ViewHolder { private ImageView stgvImageView; private ImageView userAva; private TextView siteurl; private TextView userFullname; private TextView articleTitle; public ViewHolder (View holderView) { super (holderView); stgvImageView = (ImageView) holderView.findViewById (R.id.stgvImageView); siteurl = (TextView) holderView.findViewById (R.id.siteurl); userAva = (ImageView) holderView.findViewById (R.id.userAva);

userFullname = (TextView) holderView.findViewById (R.id.userFullname); articleTitle = (TextView) holderView.findViewById (R.id.articleTitle); } }

@Override public AdapterMain.ViewHolder onCreateViewHolder (ViewGroup parent, int viewType) { View v = LayoutInflater.from (parent.getContext ()) .inflate (R.layout.card, parent, false);

ViewHolder vh = new ViewHolder (v); return vh; }

@Override public void onBindViewHolder (ViewHolder viewHolder, int i) { ClassItem item = data.get (i); aq.id (viewHolder.articleTitle).text (item.getTitle ()); aq.id (viewHolder.siteurl).text (item.getLink ()); aq.id (viewHolder.userAva).image (item.getLogo ()); aq.id (viewHolder.userFullname).text (item.getAuthor () + » » + formatter.format (item.getDate ())); if (TextUtils.equals (item.getImg (),»)) aq.id (viewHolder.stgvImageView).gone (); else { aq.id (viewHolder.stgvImageView).visible ().image (item.getImg (), true, false, 640, 0, null, AQuery.FADE_IN, AQuery.RATIO_PRESERVE); } }

@Override public int getItemCount () { return data.size (); } } Буквально одной строкой мы отдаунскейлили картинку, включили кеширование её в памяти и отскейлили до нужного соотношения сторон, попутно включив анимацию отображения.

aq.id (viewHolder.stgvImageView).visible ().image (item.getImg (), true, false, 640, 0, null, AQuery.FADE_IN, AQuery.RATIO_PRESERVE); Если заглянуть в код библиотеки, то можно увидеть что вся логика работы с изображениями построена на weakReference, применяется вытесняющий LRU кэш, скейлинг производится с использованием оптимизированных методов inSampleSize и так далее. Более того, можно вручную управлять параметрами кеширования от размеров кеша под различные типы картинок (маленькие, большие, средние) до методики кеширования и количества картинок, одновременно хранящихся в кеше.

Пример конфига с отключенным кешированием на файловой системе (фрагмент application)

@Override public void onLowMemory (){

//clear all memory cached images when system is in low memory //note that you can configure the max image cache count, see CONFIGURATION BitmapAjaxCallback.clearCache (); }

@Override public void onCreate () {

//Config cache BitmapAjaxCallback.setDelayWrite (true); BitmapAjaxCallback.setPixelLimit (640×800); BitmapAjaxCallback.setMaxPixelLimit (4096000); } В данном случае картинки не будут скачиваться во временный файл, что может быть удобно при единовременной загрузке сотен изображений.

А вот так выглядит принудительная очистка кэша, например, при перезагрузке страницы:

BitmapAjaxCallback.clearCache (); А в нашем примере, собственно, осталось добавить обновление страницы:

@Override public void onCreate (Bundle savedInstanceState) { super.onCreate (savedInstanceState);

… swipeRefreshLayout = new SwipeRefreshLayout (activity);

swipeRefreshLayout.setOnRefreshListener (this); swipeRefreshLayout.setColorScheme (android.R.color.holo_blue_bright, android.R.color.holo_green_light, android.R.color.holo_orange_light, android.R.color.holo_red_light);

@Override public void onRefresh () { getFeeds (); } Ну и расширим список фидов:

private final String[] FEEDS = new String[]{«http://roem.ru/rss/», «http://siliconrus.com/feed/», «http://habrahabr.ru/rss/», «http://megamozg.ru/rss/», «http://geektimes.ru/rss/»};

В результате получилось приложение для чтения rss лент с пула основных it ресурсов, которое благодаря AQuery удалось написать буквально за 5 часов, без танцев с подключением кучи библиотек, сосредоточившись, собственно, на коде, а не на процессе. За что мы и любим AQuery &)

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

P.S.: Я выложил на github получившийся rss reader. Конечно, это только прототип, но вполне рабочий: github.com/recoilme/itnewsgoogle play: play.google.com/store/apps/details? id=org.freemp.itnews

© Habrahabr.ru