Scalaを使ってAndroid開発をしてみよう、ArrayAdapter編です。
ArrayAdapterの仕様も忘れかけてたので、そこら辺も一緒に説明していきます。
ArrayAdapter(Java)
自作のクラスを使ったArrayAdapterは以下のように継承クラスを作る必要があります。
ソース引用元
public class UsersAdapter extends ArrayAdapter<User> {
// View lookup cache
private static class ViewHolder {
TextView name;
TextView home;
}
public UsersAdapter(Context context, ArrayList<User> users) {
super(context, R.layout.item_user, users);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
User user = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
ViewHolder viewHolder; // view lookup cache stored in tag
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.item_user, parent, false);
viewHolder.name = (TextView) convertView.findViewById(R.id.tvName);
viewHolder.home = (TextView) convertView.findViewById(R.id.tvHome);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
// Populate the data into the template view using the data object
viewHolder.name.setText(user.name);
viewHolder.home.setText(user.hometown);
// Return the completed view to render on screen
return convertView;
}
}
Viewの生成
まずはViewを生成している所をScalaで書き直してみましょう。
コードを見るとgetViewの引数であるconvertViewはnullの場合があるみたいですね。
ソースの引用元にも解説は載っていますがざっくり説明すると
Androidでのリストのアイテム部分となるViewは初回時は生成する必要がありますが、スクロールなどした場合Viewは使いまわされるためです。
Javaでは単純にnullチェックでif分岐しているようですが、Scalaでnullチェックなんてナンセンスなので修正してみます。
val view = Option(convertView) getOrElse LayoutInflater.from(getContext).inflate(R.layout.item_user, parent, false)
1行にまとまりましたね。
とりあえずconvertViewはOptionで包んでnullの場合はgetOrElseで処理するようにしちゃいます。
以降の処理ではこのviewを使っていきます。
ViewHolder
これはソースコード見て思い出しました、そういえばそんなのもありましたねぇ・・・。
前述にある通りはViewは使いまわされるので一度findViewByIdしたのを毎回取得しなおすのはコストの無駄じゃない?って考えだったはずです。
なので初回時にViewHolderと呼ばれるクラスに格納してsetTagでView自体に紐づけして次回以降はgetTagで取得しているわけですね。
これもサクッとScalaにしちゃいましょう。
まずはViewHolderクラスを定義します。
case class ViewHolder(text1: TextView, text2: TextView)
case classは偉大ですね、格納用のクラス定義が楽です。
で、ViewHolderの生成部分を書きます。
val viewHolder = Option(view.getTag.asInstanceOf[ViewHolder]) getOrElse ViewHolder(view.findViewById(R.id.tvName).asInstanceOf[TextView], view.findViewById(R.id.tvHome).asInstanceOf[TextView])
ちょっと横に長いですがこんな感じです・・・、implicitとか使えばもう少し綺麗になるかもですね。
さっきと一緒でOptionで包んで処理しています。
Javaではクラスのキャスト時にnullだと例外発生しちゃうんで、nullチェックが面倒だった記憶があるのですが
asInstanceOfはモナドになっているみたい?なのでnullでもオッケーです。
ArrayAdapter(Scala)
ということで以下が完成版です。
class UsersAdapter(context: Context, users: java.util.List[User]) extends ArrayAdapter[User](context, R.layout.item_user, users) {
case class ViewHolder(text1: TextView, text2: TextView)
override def getView(position: Int, convertView: View, parent: ViewGroup): View = {
val view = Option(convertView) getOrElse LayoutInflater.from(getContext).inflate(R.layout.item_user, parent, false)
val viewHolder = Option(view.getTag.asInstanceOf[ViewHolder]) getOrElse ViewHolder(view.findViewById(R.id.tvName).asInstanceOf[TextView], view.findViewById(R.id.tvHome).asInstanceOf[TextView])
viewHolder.text1.setText(getItem(position).name)
viewHolder.text1.setText(getItem(position).hometown)
view.setTag(viewHolder)
view
}
}
空白行を除いてJavaが27行、Scalaが10行ぐらい
圧倒的じゃないか
何?見づらい?
慣れです!