Androidアプリで設定系画面を作る場合、PreferenceActivityやPreferenceFragmentを使うと、見た目が統一された画面を簡単に実装できる。
この場合、Title と Summary でそのPreferenceの説明を記載できるが、いずれも文字列情報であるため、画像データなんかを使って、もっと分かりやすく補足したいことがある。
以下、2つの方法を試してみた。
なお、Preference右側にあたるチェックボックスなどが表示される領域については、android:widgetLayout
を指定することでカスタマイズ可能。こちらは秀逸なHowTo記事がいくつも投稿されているので、そちらを参考に。
- PreferenceActivity の CheckBox マークを変える
- 続・Android開発のちょっとしたお話
(アプローチ1) Customレイアウトを使う
android:widgetLayout
と似たようなやり方で、Preference全体についてはandroid:layout
を指定することでカスタマイズができる。
まず、Preference全体のレイアウトファイルを用意する。
一から作ると大変なので、Androidのソースコードからpreferenece.xmlを見つけてきて引用。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingEnd="?android:attr/scrollbarSize"
android:paddingRight="?android:attr/scrollbarSize"
android:background="?android:attr/selectableItemBackground" >
<!-- Move to below.
<ImageView
android:id="@+android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
-->
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dip"
android:layout_marginLeft="15dip"
android:layout_marginEnd="6dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
<TextView android:id="@+android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="@+android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignStart="@android:id/title"
android:layout_alignLeft="@android:id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="4" />
<!-- Insert here -->
<ImageView
android:id="@+android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/summary"
android:layout_alignStart="@android:id/summary"
android:layout_alignLeft="@android:id/summary" />
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@+android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical" />
</LinearLayout>
標準Preferenceでは、左側にアイコン画像の領域がある。今回は簡単のため、このImageView
をSummary領域の下に移動させてみる。もちろん、icon領域を残しつつ、新たにImageView
を追加することもできる。
Preference XMLは以下のような感じ。
android:layout
に先のレイアウトファイルを指定する。
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<ListPreference
android:defaultValue="180"
android:entries="@array/pref_sync_frequency_titles"
android:entryValues="@array/pref_sync_frequency_values"
android:key="sync_frequency"
android:negativeButtonText="@null"
android:positiveButtonText="@null"
android:layout="@layout/custom_preference"
android:icon="@android:drawable/star_on"
android:title="@string/pref_title_sync_frequency" />
: (略)
</PreferenceScreen>
なお、icon
領域を移動しただけなので、表示したい画像リソースは android:icon
で指定できるが、ImageViewを別で新規に追加する場合、attr
やらstyleable
で独自プロパティを定義することで、XML上から表示画像の指定もできる。
出来栄えはこんな感じ。
画像はうまく表示できたが、なにやら周りとフォントの大きさや表示位置が揃っていない。
今回、PreferenceのレイアウトファイルをAndroidのソースコードから引用したのだが、こうしたレイアウトファイルは機種ごとにカスタマイズされることがあるようで、この手法だと全機種で見た目を統一するのは難しそう。(やるなら、アプリ内のすべてのPreferenceのandroid:layout
を同じものに指定しなおす、など)
(アプローチ2) 独自Preferenceクラスを定義する
一旦、仕切り直し。
他のPreferenceとの見た目の調和を崩さないため、独自Preferenceクラスを定義し、ロードされたPreferenceのLayoutにImageView
を後から差し込んでみる。
以下のような独自Preferenceクラスを定義する。
package com.example.custompreferencesample;
import android.content.Context;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class PreferenceEx extends Preference {
public PreferenceEx(Context context) {
super(context);
}
public PreferenceEx(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public PreferenceEx(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
ViewGroup viewGroup = (ViewGroup) view;
// Workaround: hide icon image
ImageView iconView = (ImageView) viewGroup
.findViewById(android.R.id.icon);
iconView.setVisibility(View.GONE);
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof RelativeLayout) {
RelativeLayout relativeLayout = (RelativeLayout) child;
// Create a new image view.
ImageView image = new ImageView(getContext());
image.setImageDrawable(getIcon());
image.setPadding(20, 20, 0, 0); /* Padding */
// Insert it into the "RelativeLayout".
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.BELOW, android.R.id.summary);
relativeLayout.addView(image, params);
break;
}
}
}
}
void onBindView(View view)
はPreferenceのレイアウトがロードされたときに呼ばれるので、ここでロードされたレイアウトにImageViewを挟み込んでみる。
今回、Summary領域の下に画像を差し込むように、その親であるRelativeLayout
をfor文で探してきて挿入している。(レイアウト構成はpreference.xmlを参考に)
なお、先と同じく、簡単のためandroid:icon
で画像を指定できるようにしたいので、Preference左側に表示されるIcon領域はWorkaroundで非表示にしている。
PreferenceのXML側はこんな感じ。
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<com.example.custompreferencesample.PreferenceEx
android:title="Color setting"
android:summary="Set a color of right top corner like below figure."
android:icon="@drawable/setting_right_top"
android:key="color_right_top" />
: (略)
</PreferenceScreen>
android:icon="@drawable/setting_right_top"
で用意した画像リソースを指定している。
見た目はこんな感じ。
ちょっとわかりにくいが、フォントサイズなどが周りと揃っている。
(上図の"Sync frequency"はアプローチ1の方法で表示したもの)
今回は基本クラスであるPreference
クラスを継承した独自クラスを定義したが、ListPreference
なども同じように拡張できる。
extends以外はほとんど同じだがコードは以下。
package com.example.custompreferencesample;
import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class ListPreferenceEx extends ListPreference {
public ListPreferenceEx(Context context) {
super(context);
}
public ListPreferenceEx(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
ViewGroup viewGroup = (ViewGroup) view;
// Workaround: hide icon image
ImageView iconView = (ImageView) viewGroup
.findViewById(android.R.id.icon);
iconView.setVisibility(View.GONE);
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof RelativeLayout) {
RelativeLayout relativeLayout = (RelativeLayout) child;
// Create a new image view.
ImageView image = new ImageView(getContext());
image.setImageDrawable(getIcon());
image.setPadding(20, 20, 0, 0); /* Padding */
// Insert it into the "RelativeLayout".
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.BELOW, android.R.id.summary);
relativeLayout.addView(image, params);
break;
}
}
}
}
XXXPreferenceクラスごとにいちいちクラス拡張が必要なのがネック。