Пример приложения с использованием библиотеки AQuery
Нас постоянноспрашивают, почему мы используем библиотеку AQuery в своих проектах. В конце концов нам надоело отвечать и мы решили показать, на что способна AQuery в бою.Но писать какой-то странный псевдокод в духе hello world скучно и неинтересно и поэтому мы решили сделать какое-нибудь небольшое, но полезное приложение. Недавно от Хабра отделился проект Мегамозг и в комментариях к новости высказывали предложение объединить RSS поток со всех ресурсов. Этим мы и займемся.
В конце получится такой прототип приложения IT News (rss с хабра, гиктаймс, мегамозга и с силиконруса/роем упорядоченные по дате):
Ссылки для торопыжек: 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 и так далее
великолепная документация с кучей примеров
Но это все слова, давайте попробуем в деле. Создаем пустой проект со следующими зависимостями:
Начнем с объявления 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»/>
Проверим, что получилось, должно быть примерно так:
Теперь начинаем магию с 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 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 (» }
В класс AjaxStatus приходит детальная информация о результатах выполнения запроса + в AQuery встроен простенький парсер XML. Нет необходимости беспокоиться о наличии активити на момент завершения запроса, AQuery сделает это за нас. Плюс модуль HTTP запросов гораздо гибче чем в примере выше, можно кастомизировать все, от хидеров до метода выполнения запроса. А если вам необходимо, например, закешировать запрос — просто добавляете параметр fileCache=true и время, на которое запрос должен быть закеширован. Есть функционал для инвалидации кеша в случае ошибки, например, и так далее.Мы же пока вернемся к адаптеру, и обогатим rss поток функционалом отображения картинок. Тем более что с AQuery это не просто, а очень просто:
public class AdapterMain extends RecyclerView.Adapter private ArrayList public AdapterMain (Activity activity, ArrayList 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