2010年10月2日土曜日

Android Drawable Mutations

Drawable Mutations

訳&試してみた
サンプル部分は全入れ替えですw

-----

Android の drawable はアプリを作る上でとても便利である。

Drawable は一般的に View に関連付けられている拡張可能な描画用コンテナであり、画像を表示するための BitmapDrawable や 図形やグラデーションなどを描画するための ShapeDrawable などがある。(Drawable Resources の一覧)これらを組み合わせて複雑なレンダリングを実現することができる。

Drawable を使うことで、簡単に widget のレンダリングをカスタマイズできる。実際、この機能はすごく便利なため、ほとんどの標準 Android apps と widgets は drawables を使って作られており、Android framework のコアには約700の drawables が使われている。

drawables はシステム内で非常によく使われているため、Android は resource から読み込むときにこれらを最適化している。例えば、Button を作成するたびに新しい drawable が framework resoruces (android.R.drawable.btn_default) から読み込まれる。これはアプリ中のすべてのボタンが異なる drawable instance をその背景として使うことを意味している。しかし、これらすべての drawable は "constant state" と呼ばれる通常の状態を共有している。

この通常状態は、使用する drawable のタイプによって変わるが、一般的には resource によって定義可能なすべてのプロパティを含む。Button の場合、通常状態は bitmap 画像を含む。このように、アプリ中のすべてのボタンは同じ bitmap を共有し多くのメモリを節約している。

以下は、2つの異なる view の background に同じ画像をリソースを割り当てたときに、どのエンティティが生成されるかを表したダイアグラムである。2つの drawable が生成されるが、それらは同じ constant state を共有する。



この状態共有の機能は、メモリの無駄遣いを大幅に節約できるが、個々の drawable のプロパティを編集しようとした場合に問題が起こる。

ListView の各要素に星画像★が付いているアプリがあるとする。例えば、お気に入りの場合は★を不透明にし、そうでない場合は透明にしたいとする。そのために、ListView の getView() を次のように実装したとする。
# めんどいので、position が奇数は不透明、偶数は透明にする


public View getView(int position, View convertView, ViewGroup parent)

ViewHolder holder;

if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item,
parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);

convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

Drawable mIcon = mContext.getResources()
.getDrawable(R.drawable.ic_menu_star);

if ((position & 1) == 1) {
mIcon.setAlpha(255); // opaque
} else {
mIcon.setAlpha(70); // translucent
}

holder.text.setText(DATA[position]);
holder.icon.setImageDrawable(mIcon);

return convertView;
}


残念ながら、このコードだとすべての drawables が同じ透明度になる。




この結果は constant state で説明できる。リストの各アイテムに対して新しい drawable instance を取得したとしても、BitmapDrawable の場合、constant state は同じままになる。このように、1つの drawable instance の透明度を変化させると、他のすべての instance の透明度も変化する。この問題を Android 1.0 と 1.1 で関係するのは簡単ではなかった。

Android 1.5 以上なら、mutate() を使うことで、とても簡単にこの問題を解決できる。 このメソッドを drawable に対して発行すると、drawable の constant state が多重化されるので、他の drawable へ影響させずに個々のプロパティを変えることができる。ただし、drawable を mutate した後であっても、bitmap は共有されたままになる。

drawable で mutate() を呼び出すと次のようになる。



mutate() を使って、先のコードを変更する。

Drawable mIcon = mContext.getResources()
.getDrawable(R.drawable.ic_menu_star);

if ((position & 1) == 1) {
mIcon.mutate().setAlpha(255); // opaque
} else {
mIcon.mutate().setAlpha(70); // translucent
}


mutate() は自身の drawable を返すため、chain method call が可能である。ただし、これは新しいインスタンスを生成するわけではない。これで正しく表示されるようになる。




-----
全コードはこちら


public class MainActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawablemutate);
}

static class ViewHolder {
TextView text;
ImageView icon;
}

private class MyAdapter extends BaseAdapter {
private Context mContext;
private LayoutInflater mInflater;

private String[] DATA = { "Henry IV (1)", "Henry V",
"Henry VIII", "Richard II",
"Richard III", "Merchant of Venice",
"Othello", "King Lear" };

public MyAdapter(Context context) {
mContext = context;
mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
}

public int getCount() {
return DATA.length;
}

public Object getItem(int position) {
return position;
}

public long getItemId(int position) {
return position;
}

public View getView(int position, View convertView, ViewGroup parent)

ViewHolder holder;

if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item,
parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);

convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

Drawable mIcon = mContext.getResources()
.getDrawable(R.drawable.ic_menu_star);

if ((position & 1) == 1) {
mIcon.setAlpha(255); // opaque
} else {
mIcon.setAlpha(70); // translucent
}

holder.text.setText(DATA[position]);
holder.icon.setImageDrawable(mIcon);

return convertView;
}
}

0 件のコメント:

コメントを投稿