Настало время переходить на Lollipop, друзья. Как бы смешно это не звучало.
Буквально вчера мы в Surfingbird обновили дизайн приложения и сегодня, по свежим следам, хотелось бы поделиться впечатлениями от перехода на material design.
Подготовительный этап.
Чтобы минимизировать количество проблем, лучше обновить все)
Внедряем RecyclerView
RecyclerView это новый ViewGroup компонент, который пришел на замену List/GridView. Но он не является их потомком, скорее это альтернативная ветвь эволюции. С одной стороны, это гораздо более гибкий и более эффективно работающий компонент, с другой — в нем из коробки отсутствуют, либо делаются по другому некоторые вещи, к которым мы привыкли в List/GridView (разделители, быстрый скролл, селекторы, хидеры и т.п.).
Во-первых, по субъективным ощущениям, скроллинг стал более плавным, чем при использовании listview+viewholder, во-вторых, появилось множество прекрасных штук, так что игра несомненно стоит свеч.
Перейти на этот компонент очень просто. Закидываем в библиотеки соответствующий sdk ▸ extras ▸ android ▸ support ▸ v7 ▸ recyclerview ▸ libs▸ android-support-v7-recyclerview.jar/подключаем вбогомерзком gradle или чем вы пользуетесь.
1. Обновляем адаптер, если вы уже использовали view-holder паттерн, то все привычно
Заменяем BaseAdapter(или что там у вас было) на RecyclerView.Adapter<AdapterMain.ViewHolder>
В onCreateViewHolder — парсим layout
где, собственно ViewHolder – привычная заглушка
и переносим логику наполнения view из getView в onBindViewHolder (обращаясь к холдеру — holder.stgvImageView и т.п.)
Удаляем ставшие ненужными методы типа getItem
2. Заменяем ListView на RecyclerView
3. Продолжаем разговор.
Работа с адаптером практически не изменилась.
Стал ненужным метод отключения адаптера на момент изменения (DataSetInvalidated), нотификация об изменении осталась без изменения
Изменился метод вычисления последнего видимого элемента (для автоматической подгрузки следующей порции). Предполагаю, что эту логику лучше перенести в адаптер, но, если очень некогда, то можно так, например:
Вообще, скролинг стал более низкоуровневым, теперь можно прямо в этом методе получать информацию куда и насколько проскролено (простите мой английский)
На этом месте у вас все должно заработать. Если, например, нужно добавить разделители, то их можно добавить перекрыв класс DividerItemDecoration, например так: (вертикальные разделители)
(Ахтунг, копипаста сами знаете с какого сайта)
Но не спешите с этим! Потому что появились прекрасные Карточки!
Внедряем CardView
Помню, когда я был еще совсем молодым android-разработчиком, вышел пинтерест и все офигели. Мы часами разглядывали, как они реализовали карточки переменной высоты, плавающие кнопки (или это было в Path?), не суть важно. Сейчас можно получить неплохо выглядящие карточки (в том числе, переменной высоты и прямо как в пинтерест) буквально в пару строк кода.
Подключаем cardview как library project/прописываем магическую строку в систему сборки, закидываем jar, не забыв обновить версию саппорт лайбрари.
По сути, карточки — это фрейм вокруг вашего лейаута с тенюшками и скругляшками, поэтому просто обрамляем ими ваш лэйаут:
Готово, Милорд!
Теперь, допустим, для планшетной версии задаем отображение в две колонки, а для телефонов в одну:
(Ахтунг, копипаста сами знаете с какого сайта)
И задаем для разных устройств разный формат отображения:
Должно получиться примерно так:
Некоторые нюансы:
Результат
Глаз разработчика «замылен», сложно сказать получилось хорошо или так себе. Я почему-то ожидал большего, если честно. Динамически падающих тенюшек при скролинге, например, больше магии. А в целом, все получилось чуть свежее. Хотя, конечно, мы еще не до конца ололлипопились. Посмотреть результат можно в маркете.
Я наверняка что-то забыл. Делитесь нюансами, рецептами и советами перехода на лоллипоп в комментариях. Тема актуальная, всем нам будет полезно и интересно.
Буквально вчера мы в Surfingbird обновили дизайн приложения и сегодня, по свежим следам, хотелось бы поделиться впечатлениями от перехода на material design.
Подготовительный этап.
Чтобы минимизировать количество проблем, лучше обновить все)
- Устанавливаем образ lollipop на свой
нексустелефон - Обновляем Java до 7 версии, если еще нет
- Обновляем IDE, мы используем Intellij Idea
- Обновляем SDK, не забудьте обновить Tools, Platform-tools, Build-tools, Sdk и Support library
Внедряем RecyclerView
RecyclerView это новый ViewGroup компонент, который пришел на замену List/GridView. Но он не является их потомком, скорее это альтернативная ветвь эволюции. С одной стороны, это гораздо более гибкий и более эффективно работающий компонент, с другой — в нем из коробки отсутствуют, либо делаются по другому некоторые вещи, к которым мы привыкли в List/GridView (разделители, быстрый скролл, селекторы, хидеры и т.п.).
Во-первых, по субъективным ощущениям, скроллинг стал более плавным, чем при использовании listview+viewholder, во-вторых, появилось множество прекрасных штук, так что игра несомненно стоит свеч.
Перейти на этот компонент очень просто. Закидываем в библиотеки соответствующий sdk ▸ extras ▸ android ▸ support ▸ v7 ▸ recyclerview ▸ libs▸ android-support-v7-recyclerview.jar/подключаем в
1. Обновляем адаптер, если вы уже использовали view-holder паттерн, то все привычно
Заменяем BaseAdapter(или что там у вас было) на RecyclerView.Adapter<AdapterMain.ViewHolder>
В onCreateViewHolder — парсим layout
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = layoutInflater.inflate(R.layout.main_adapter_griditem, null);
return new ViewHolder(view);
}
где, собственно ViewHolder – привычная заглушка
class ViewHolder extends RecyclerView.ViewHolder{
private ImageView stgvImageView;
public ViewHolder(View holderView) {
super(holderView);
stgvImageView = (ImageView) holderView.findViewById(R.id.stgvImageView);
}
}
и переносим логику наполнения view из getView в onBindViewHolder (обращаясь к холдеру — holder.stgvImageView и т.п.)
Удаляем ставшие ненужными методы типа getItem
2. Заменяем ListView на RecyclerView
public RecyclerView gridView;//здесь был Grid/ListView
public AdapterMain adapterMain;
public ArrayList<Site> rows;
//Это способ отображения recycleview. Кроме сетки с столбцами переменной высоты есть более канонические
//GridLayoutManager (Grid) и LinearLayoutManager (List)
public StaggeredGridLayoutManager mLayoutManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
aq = new AQuery(getActivity());
//Не пугайтесь это просто контейнер
final LinearLayout linearLayout = new LinearLayout(getActivity());
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setGravity(Gravity.CENTER);
gridView = new RecyclerView(getActivity());
gridView.setHasFixedSize(true);
mLayoutManager = new StaggeredGridLayoutManager(UtilsScreen.getDisplayColumns(getActivity()),StaggeredGridLayoutManager.VERTICAL);
//можно задать горизонтальную ориентацию. Будет свежо и необычно. Наверное
gridView.setLayoutManager(mLayoutManager);
gridView.setItemAnimator(new DefaultItemAnimator());
//Это новый метод для задания divider
//gridView.addItemDecoration(new DividerItemDecoration(getActivity()));
//Этих методов больше нет
//gridView.setSmoothScrollbarEnabled(true);
//gridView.setDivider(new ColorDrawable(this.getResources().getColor(R.color.gray_divider)));
//gridView.setDividerHeight(UtilsScreen.dpToPx(8));
rows = new ArrayList<Site>();
linearLayout.addView(gridView);
return linearLayout;
}
3. Продолжаем разговор.
Работа с адаптером практически не изменилась.
@Override
public void onViewCreated(View view,Bundle savedInstanceState) {
super.onViewCreated(view,savedInstanceState);
adapterMain = new AdapterMain(getActivity(),rows);
gridView.setAdapter(adapterMain);
gridView.setOnScrollListener(onScroll);
}
Стал ненужным метод отключения адаптера на момент изменения (DataSetInvalidated), нотификация об изменении осталась без изменения
adapterMain.notifyDataSetChanged();
if (page == 1) gridView.scrollToPosition(0);//точно не помню как раньше назывался этот метод
Изменился метод вычисления последнего видимого элемента (для автоматической подгрузки следующей порции). Предполагаю, что эту логику лучше перенести в адаптер, но, если очень некогда, то можно так, например:
gridView.setOnScrollListener(onScroll);
//---
private RecyclerView.OnScrollListener onScroll = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int[] visibleItems = ((StaggeredGridLayoutManager) gridView.getLayoutManager()).findLastVisibleItemPositions(null);
int lastitem=0;
for (int i:visibleItems) {
lastitem = Math.max(lastitem,i);
}
if (lastitem>0 && lastitem>adapterMain.data.size()-5 && !isRunning) {
if (!internetIsOver) {
refresh();
}
}
}
};
Вообще, скролинг стал более низкоуровневым, теперь можно прямо в этом методе получать информацию куда и насколько проскролено (простите мой английский)
На этом месте у вас все должно заработать. Если, например, нужно добавить разделители, то их можно добавить перекрыв класс DividerItemDecoration, например так: (вертикальные разделители)
(Ахтунг, копипаста сами знаете с какого сайта)
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
private Drawable mDivider;
private int offset = 0;
public DividerItemDecoration(Context context) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
offset = UtilsScreen.dpToPx(16);
a.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
drawVertical(c, parent);
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + offset;//mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, offset);//mDivider.getIntrinsicHeight());
}
}
Но не спешите с этим! Потому что появились прекрасные Карточки!
Внедряем CardView
Помню, когда я был еще совсем молодым android-разработчиком, вышел пинтерест и все офигели. Мы часами разглядывали, как они реализовали карточки переменной высоты, плавающие кнопки (или это было в Path?), не суть важно. Сейчас можно получить неплохо выглядящие карточки (в том числе, переменной высоты и прямо как в пинтерест) буквально в пару строк кода.
Подключаем cardview как library project/прописываем магическую строку в систему сборки, закидываем jar, не забыв обновить версию саппорт лайбрари.
По сути, карточки — это фрейм вокруг вашего лейаута с тенюшками и скругляшками, поэтому просто обрамляем ими ваш лэйаут:
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:contentPadding="8dp"
card_view:cardBackgroundColor="@color/primary_bgr"
card_view:cardUseCompatPadding="true"
card_view:cardCornerRadius="4dp">
<RelativeLayout
android:id="@+id/articleLayout"
android:background="@color/primary_bgr"
android:layout_width="match_parent"
android:layout_height="wrap_content">
//---
Готово, Милорд!
Теперь, допустим, для планшетной версии задаем отображение в две колонки, а для телефонов в одну:
(Ахтунг, копипаста сами знаете с какого сайта)
public static boolean isTablet(Context context) {
boolean xlarge = ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 4);
boolean large = ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE);
return (xlarge || large);
}
public static int getDisplayColumns(Activity activity) {
int columnCount = 1;
if (isTablet(activity)) {
columnCount = 2;
}
return columnCount;
}
И задаем для разных устройств разный формат отображения:
mLayoutManager = new StaggeredGridLayoutManager(UtilsScreen.getDisplayColumns(getActivity()),StaggeredGridLayoutManager.VERTICAL);
Должно получиться примерно так:
Некоторые нюансы:
- После того, как мы выложили приложение в стор, на некоторых устройствах (почему-то на нексусах) и почему-то в том числе на 4.4.4 – приложение странным образом начало падать в районе саппорт лайбрари (причем на наших телефонах (включая нексусы) все работало). Пришлось отключить proguard, это помогло но осадок остался.
- Нам не очень понравился цвет шрифта в дефолтной светлой теме. Он очееень нежен, учитывая то, что на всех андроид устройствах цветопередача нарушена разная, поэтму мы решили перекрыть цвет шрифта на чуть более темный.
- Отключить тень у акшенбара теперь можно, например, так: getSupportActionBar().setElevation(0);
- Приложение не будет работать на бете лоллипоп так же, как не работают на ней и все остальные приложения в лоллипоп дизайне (gmail, пресса)
- Иконки акшенбара стали меньше. Мы просто перенесли их в папку (xxhdpi)
- Мы пока решили забить на анимации. Перед глазами гугл-пресса и все вроде дико красиво крутится/вертится/плавает/мигает, но мы еще не готовы к такой решительной анимации.
Результат
Глаз разработчика «замылен», сложно сказать получилось хорошо или так себе. Я почему-то ожидал большего, если честно. Динамически падающих тенюшек при скролинге, например, больше магии. А в целом, все получилось чуть свежее. Хотя, конечно, мы еще не до конца ололлипопились. Посмотреть результат можно в маркете.
Я наверняка что-то забыл. Делитесь нюансами, рецептами и советами перехода на лоллипоп в комментариях. Тема актуальная, всем нам будет полезно и интересно.