0%

Android-栏目管理

前言

最近app准备大改版,整体视觉全部换新。栏目管理功能也需要进行全新设计和改变,新版中加了不少动画效果。因为老版架构使用GridView进行栏目展示,但是GridView在栏目增加和删除时播放动画比较困难,而且老架构中栏目管理功能代码混乱,到底是重构还是重写?经过认真评估,最终决定重写该功能。

设计师要求的效果图如下:

分析

该模块主要有三个部分:

  • 栏目添加、删除
  • 栏目排序(拖拽)
  • 动画

实现方案

在具体实现之前,有不少问题是需要确认或者是探索的。

问题1 GridView or RecyclerView ?

首先,对于栏目元素的展示,用GridView、RecyclerView都可以,但是GridView做添加删除动画比较困难,而RecyclerView本身就支持添加删除动画,因此这里选RecyclerView作为栏目元素的展示。

问题2 一个RecylerView or 三个RecylerView ?

一开始,我把三个模块(我的栏目、最近删除、更多栏目)都放在一个RecylerView上,后来发现把所有的逻辑都放在一个RecylerView和一个Adapter中是非常不明智的:1、会使代码更加臃肿;2、需要不断计算位置来判断某个栏目是属于我的栏目、最近删除还是更多栏目,逻辑十分复杂;3、代码扩展性不高,如果哪天产品说不想要最近删除这个功能了,又得大改。基于如上几个因素,选用三个RecylerView。

问题3 动画如何实现?

对于添加和删除动画,RecylcerView无缝支持,使用notifyItemInserted()和nofifyItemRemove()即可,对于漂移动画,就有点棘手了。一个比较简单的思路是:动画之前,先获取View截图,计算出起始坐标和终止坐标,然后添加到Activity根布局(contentView)中,做一个位移动画,然后再把该View截图从根布局中移除即可。

问题4 拖拽排序功能如何实现?

类似于ListView、GridView的ViewDragHelper,RecyclerView也有对应的拖拽帮助类ItemTouchHelper,只需要实现Callback,并重写一些简单的方法,便可以轻松实现拖拽排序功能。

想清楚了上面几个问题,完成整个功能已经不在话下了。

实现

具体实现效果如下(为了压缩gif,丢了不少帧,真机效果要流畅很多):

主要功能已经实现了,剩下的就是迁移业务逻辑了。

代码架构如下:

adapter作为数据适配器而存在,为了更好地灵活的区分不同点击事件的不同行为,抽象出clickhandler专门处理栏目点击事件,每个adapter都持有一个clickhandler对象。clickhandler代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  /**
* Created by jasonkent27 on 2017/9/14.
*
*/
public interface ClickHandler {
/**
* Call when item click
* @param holder which holder that user click.
* @param isForbidMove true if this holder can be moved, false otherwise.
* @param isInEditMode true if Adapter in Edit Mode, false otherwise.
*/
void onItemClick(ColumnBaseAdapter.ItemViewHolder holder, boolean isForbidMove, boolean isInEditMode);
/**
* Call when item long click
* @param holder which holder that user click.
* @param isForbidMove true if this holder can be moved, false otherwise.
* @param isInEditMode true if Adapter in Edit Mode, false otherwise.
*/
boolean onItemLongClick(ColumnBaseAdapter.ItemViewHolder holder, boolean isForbidMove, boolean isInEditMode);
}

但是具体的点击事件单单由clickhandler来处理的话,需要传很多参数进去(如topRecyclerVIew,deleteRecyclerVIew,moreRecyclerView等),因此抽象出一个Controller,用来代理处理具体业务请求,Cotroller代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
  /**
* Created by jasonkent27 on 2017/9/13.
*/
public interface ColumnController {
int COLUMN_TYPE_TOP = 1;
int COLUMN_TYPE_RECENT_DELETED = 2;
int COLUMN_TYPE_MORE = 3;
void moveMoreToTop(ColumnBaseAdapter.ItemViewHolder holder);
void gotoColumn(int columnType, ColumnBaseAdapter.ItemViewHolder holder);
void moveRecentDeletedToTop(ColumnBaseAdapter.ItemViewHolder holder);
void startDrag(ColumnBaseAdapter.ItemViewHolder holder);
void moveTopToRecentDeleted(ColumnBaseAdapter.ItemViewHolder holder);
}

显然,clickhandler也不需要做太多工作,比如MoreClickHandler代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  /**
* Created by jasonkent27 on 2017/9/14.
*/
public class MoreClickHandler implements ClickHandler {
private ColumnController mController;
public MoreClickHandler(ColumnController controller) {
mController = controller;
}
@Override
public void onItemClick(ColumnBaseAdapter.ItemViewHolder holder, boolean isForbidMove, boolean isInEditMode) {
if (isInEditMode) {
// step 1: remove more data and animation.
// step 2: start translate animation.
// step 3: add top data and animation.
mController.moveMoreToTop(holder);
} else {
mController.gotoColumn(ColumnController.COLUMN_TYPE_MORE, holder);
}
}
@Override
public boolean onItemLongClick(ColumnBaseAdapter.ItemViewHolder holder, boolean isForbidMove, boolean isInEditMode) {
return true;
}
}

而真正具体的逻辑(添加、删除栏目、位移动画等)由谁来做比较合适呢?显然是ColumnTopEditFragment,因为ColumnTopEditFragment本身就持有topRecyclerVIew,deleteRecyclerVIew,moreRecyclerView这些对象,通过这些对象来实现具体逻辑是比较合适的。当然如果以后ColumnTopEditFragment代码越来越多,可以考虑把写另一个类实现ColumnController接口,并把需要的参数(topRecyclerVIew,deleteRecyclerVIew,moreRecyclerView)传进去,这样就可以把添加、删除、位移动画这部分逻辑挪出来。因此这里抽象出ColumnController是非常明智的,如果改天产品说更多栏目砍掉,现有代码框架也不需要做太多调整。

源码下载

ColumnManager