适配什么? - Adapt what?
首先有必要(?)讲一下 Adapter 模式,也就是适配器模式。
Adapter 模式适配的是什么呢?打个很恰当的比喻(逃),电源适配器大家应该都知道,什么,其实就是充电器嘛。手机的充电器把 220v 的交流电转化成比如说 5v 的电压。所以才可以愉快的充电。适配器模式就是把本来不匹配的接口(这样用二插三插转化器做比喻是不是好点),转化为你想要的接口,Adapter 就相当于充电器了。
看一下 UML 图会不会更好理解:
动手写一个 Adapter - How to write adapter?
就用上面那个充电器的例子,我们来写个适配器。
1
2
3
4
5
|
public class AC {
public int getVolt220() {
return 220;
}
}
|
1
2
3
|
public interface DC {
public int getVolt5();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class VoltObjAdapter implements DC {
//要被适配的对象
private AC ac;
public VoltObjAdapter(AC ac) {
this.ac = ac;
}
public int getVolt220() {
return ac.getVolt220();
}
//适配之后的输出
@Override
public int getVolt5() {
return 5;
}
}
|
以上代码很简单,甚至还有点罗嗦。VoltObjAdapter
实现了 DC
接口(也就是我们想要的接口),并且传入一个 AC
对象(也就是我们要适配的对象),最终在 getVolt5
实现适配。
适配器模式还可以用在有很多的输入类型,但是输入的类型的是统一的。可以用 Adapter 来统一输出。比如 ListView 的 Adapter 就把用户的数据和各式各样的 View 转化为统一的 ItemView
。
Tree2View 中的 Adapter - Adapter in Tree2View
那么在 Tree2View
中肯定也用到了 Adapter 模式,一个是因为 Tree2View
继承自 ListView
,加载数据必须要有一个 Adapter。二是即使不继承 ListView
,为了加载用户多种多样的 ItemView
,也就是要可以定制,我目前也只想到(学到) Adapter 模式。
抽象基类
为了实现可自定义的 ItemView
, 需要一个抽象类,这里就是 TreeAdapter
,她继承了 ListView
里的 BaseAdapter
。以后用户需要自定义的话,只需要实现这个抽象类的方法即可,主要也就是 getView()
方法。
那么还是看代码:TreeAdapter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public abstract class TreeAdapter<T> extends BaseAdapter {
//root node
protected DefaultTreeNode<T> mRoot;
protected Context mContext;
//由DefaultTreeNode转化之后的数据
//也就是从树结构转化为ArrayList
protected ArrayList<DefaultTreeNode> mNodesList;
//需要传入的xml视图id
protected int mResourceId;
//ItemView 的缩进
//不同深度的节点的缩进为 (depth + 1) * baseIndent
protected int baseIndent = 50;
//省略一些方法。。。
public TreeAdapter(Context context, DefaultTreeNode<T> root, int resourceId) {
this.mRoot = root;
this.mContext = context;
this.mResourceId = resourceId;
}
//设置缩进,你需要在 getView 里调用。
protected void setPadding(View v, int depth, int indent) {
if (v == null || depth < 0) {
throw new IllegalArgumentException("illegal params");
}
if (indent < 0) {
indent = baseIndent;
}
v.setPadding(indent * (depth + 1),
v.getPaddingTop(),
v.getPaddingRight(),
v.getPaddingBottom());
}
//Toggle your object's status
public abstract void toggle(Object ... objects);
//省略 getter 和 setter
}
|
一些重要的字段和方法,我都进行了注释。现在有一个不满意的地方是 setPadding()
和 toggle
方法需要在你实现的 Adapter 里的 getView()
中调用。因为在这时是不知道你要设置 padding 和 toggle 的 view的。不过应该有更好的实现orz。
简单实现
如上所说,``TreeAdapter是一个抽象类,也也没有实现
getView()` 方法,所以又写了一个简单的实现,如果用户不想自定义的化也可以使用这个默认的 Adapter。
SimpleTreeAdapter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
public class SimpleTreeAdapter extends TreeAdapter<String> {
public SimpleTreeAdapter(Context context, DefaultTreeNode<String> root, int resourceId) {
super(context, root, resourceId);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//dfs travel when first time called
if (mNodesList == null) {
mNodesList = TreeUtils.getVisibleNodesD(super.mRoot);
}
DefaultTreeNode node = mNodesList.get(position);
ViewHolder holder = null;
//处理缓存,来提高ListView的性能
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(mResourceId, parent, false);
holder = new ViewHolder();
holder.tv = (TextView) convertView.findViewById(R.id.default_tree_item);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tv.setText(node.getElement().toString());
int depth = node.getDepth();
//set view indent
setPadding(holder.tv, depth, -1);
//toggle
toggle(holder, node);
return convertView;
}
@Override
public void toggle(Object... objects) {
try {
DefaultTreeNode node = (DefaultTreeNode) objects[0];
ViewHolder holder = (ViewHolder) objects[1];
if (node.isHasChildren() && !node.isExpanded()) {
//set your icon
} else if (node.isHasChildren() && node.isExpanded()) {
//set your icon
}
} catch (ClassCastException e ) {
e.printStackTrace();
}
}
class ViewHolder {
TextView tv;
}
}
|
SimpleTreeAdapter 显示的就是一个TextView,所以 DefaultTreeNode
也只需要一个 String
,只需在继承的时候这样写 extends TreeAdapter<String>
,那么你的 DefaultTreeNode
也必须是 DefaultTreeNode<String>
不过这个大概无关紧要,java 的泛型也只是编译器帮你进行了类型转换的语法糖。
那么接下来看一下最最核心的 getView()
方法。这个方法申明在 Adapter
接口里,然后大家一层一层继承下来,但都不实现,最终到了 SimpleTreeAdapter
。各个参数分别是 position, convertView, parent,分别是 ItemView
在 ViewGroup 里的位置,convertView
就是需要返回的 ItemView
, parent
就是 ViewGroup,也就是 TreeView 本身。这里为每个 ItemView
设置了缩进,这也是最核心的部分,树状的树图,就是靠缩进来实现的,那么 TreeView
其实就是一个根据不同节点的深度为 ItemView
设置了不同缩进的 ListView
。这里就是一个简单的 TextView
显示每个节点的 String,再在 TreeView
里把 SimpleTreeAdapter
作为默认的 Adapter。
TreeView,java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private void init(Context context, DefaultTreeNode root) {
this.mContext = context;
if (root == null) {
return;
}
if (this.root == null || this.root != root) {
this.root = root;
//expanded root node to show it's children
this.root.setExpanded(true);
adapter = new SimpleTreeAdapter(mContext, root, R.layout.layout_tree_item);
this.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
this.setOnItemClickListener(new OnTreeItemClickListener());
}
|
总结
SimpleTreeAdapter
为 TreeView
实现了一个默认的适配器,只是显示字符。当你需要自定义的时候,你也可以自己实现 Adapter,只要继承 TreeAdapter
。得益于 ListView 自带的 Adpater 模式,TreeView 实现了高度的自定义(大雾)。
下集预告?
可以看到上面的 TreeView
代码里有一句 this.root.setExpanded(true);
。这是很重要的,我为了算法的效率,只遍历 expanded(已展开) 的节点,所以这里设置根节点为 expanded,才能在视图中显示根节点。还有一个默认的点击事件是:比如你点击了根节点,那么又把她的子节点们设置为 expanded,再更新一下 TreeView,子节点就显示出来了,也算是一个 LazyLoad。
至于 DefaultTreeNode
的细节和算法(比如节点的深度是怎么获取的),还有为了让树可以“显示出来”,我们需要遍历树的每一个节点,并且把她转化成一个数组,这样才能与 Adapter
匹配,再说 ListView
里的子 View 们也是保存在一个数组里的,为了使 View 的操作和数据的操作相对应,于是就有了这样的转化。请看下集。