适配什么? - 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());
  }

总结

SimpleTreeAdapterTreeView 实现了一个默认的适配器,只是显示字符。当你需要自定义的时候,你也可以自己实现 Adapter,只要继承 TreeAdapter。得益于 ListView 自带的 Adpater 模式,TreeView 实现了高度的自定义(大雾)。

下集预告?

可以看到上面的 TreeView 代码里有一句 this.root.setExpanded(true);。这是很重要的,我为了算法的效率,只遍历 expanded(已展开) 的节点,所以这里设置根节点为 expanded,才能在视图中显示根节点。还有一个默认的点击事件是:比如你点击了根节点,那么又把她的子节点们设置为 expanded,再更新一下 TreeView,子节点就显示出来了,也算是一个 LazyLoad。

至于 DefaultTreeNode 的细节和算法(比如节点的深度是怎么获取的),还有为了让树可以“显示出来”,我们需要遍历树的每一个节点,并且把她转化成一个数组,这样才能与 Adapter 匹配,再说 ListView 里的子 View 们也是保存在一个数组里的,为了使 View 的操作和数据的操作相对应,于是就有了这样的转化。请看下集。