2014年5月10日土曜日

Preference に指定された Fragment に遷移したときのレイアウトについて

0. Preferenceandroid:fragment 属性で Fragment を指定すると、タップしたときにそのFragmentに遷移する


1. PreferenceActivity のレイアウトは com.android.internal.R.layout.preference_list_content である。

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/layout/preference_list_content.xml ... <android.preference.PreferenceFrameLayout android:id="@+id/prefs" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" /> ...

2. PreferenceActivity では、EXTRA_SHOW_FRAGMENT というキーに Fragment の完全修飾名を入れることで、起動時に表示する Fragment を指定できる。

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/preference/PreferenceActivity.java#512 512 @Override 513 protected void onCreate(Bundle savedInstanceState) { 514 super.onCreate(savedInstanceState); 515 516 setContentView(com.android.internal.R.layout.preference_list_content); 517 ... 522 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); ... 526 527 if (savedInstanceState != null) { ... 540 } else { 541 if (initialFragment != null && mSinglePane) { 542 // If we are just showing a fragment, we want to run in 543 // new fragment mode, but don't need to compute and show 544 // the headers. 545 switchToHeader(initialFragment, initialArguments); ... 656 }

3. EXTRA_SHOW_FRAGMENT で指定された Fragment は com.android.internal.R.id.prefs という id のコンテナに replace される

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/preference/PreferenceActivity.java#1176 1176 private void switchToHeaderInner(String fragmentName, Bundle args, int direction) { 1177 getFragmentManager().popBackStack(BACK_STACK_PREFS, 1178 FragmentManager.POP_BACK_STACK_INCLUSIVE); 1179 if (!isValidFragment(fragmentName)) { 1180 throw new IllegalArgumentException("Invalid fragment for this activity: " 1181 + fragmentName); 1182 } 1183 Fragment f = Fragment.instantiate(this, fragmentName, args); 1184 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1185 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 1186 transaction.replace(com.android.internal.R.id.prefs, f); 1187 transaction.commitAllowingStateLoss(); 1188 } 1189 1190 /** 1191 * When in two-pane mode, switch the fragment pane to show the given 1192 * preference fragment. 1193 * 1194 * @param fragmentName The name of the fragment to display. 1195 * @param args Optional arguments to supply to the fragment. 1196 */ 1197 public void switchToHeader(String fragmentName, Bundle args) { 1198 setSelectedHeader(null); 1199 switchToHeaderInner(fragmentName, args, 0); 1200 } 1.で見たように、com.android.internal.R.id.prefs は PreferenceFrameLayout である。


4. PreferenceFrameLayout は子ビューの追加時にスタイル属性で指定された値分だけパディングを追加する

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/preference/PreferenceFrameLayout.java 44 public PreferenceFrameLayout(Context context, AttributeSet attrs) { 45 this(context, attrs, com.android.internal.R.attr.preferenceFrameLayoutStyle); 46 } 47 48 public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyle) { 49 super(context, attrs, defStyle); 50 TypedArray a = context.obtainStyledAttributes(attrs, 51 com.android.internal.R.styleable.PreferenceFrameLayout, defStyle, 0); 52 53 float density = context.getResources().getDisplayMetrics().density; 54 int defaultBorderTop = (int) (density * DEFAULT_BORDER_TOP + 0.5f); 55 int defaultBottomPadding = (int) (density * DEFAULT_BORDER_BOTTOM + 0.5f); 56 int defaultLeftPadding = (int) (density * DEFAULT_BORDER_LEFT + 0.5f); 57 int defaultRightPadding = (int) (density * DEFAULT_BORDER_RIGHT + 0.5f); 58 59 mBorderTop = a.getDimensionPixelSize( 60 com.android.internal.R.styleable.PreferenceFrameLayout_borderTop, 61 defaultBorderTop); 62 mBorderBottom = a.getDimensionPixelSize( 63 com.android.internal.R.styleable.PreferenceFrameLayout_borderBottom, 64 defaultBottomPadding); 65 mBorderLeft = a.getDimensionPixelSize( 66 com.android.internal.R.styleable.PreferenceFrameLayout_borderLeft, 67 defaultLeftPadding); 68 mBorderRight = a.getDimensionPixelSize( 69 com.android.internal.R.styleable.PreferenceFrameLayout_borderRight, 70 defaultRightPadding); 71 72 a.recycle(); 73 } ... 83 @Override 84 public void addView(View child) { 85 int borderTop = getPaddingTop(); 86 int borderBottom = getPaddingBottom(); 87 int borderLeft = getPaddingLeft(); 88 int borderRight = getPaddingRight(); 89 90 android.view.ViewGroup.LayoutParams params = child.getLayoutParams(); 91 LayoutParams layoutParams = params instanceof PreferenceFrameLayout.LayoutParams 92 ? (PreferenceFrameLayout.LayoutParams) child.getLayoutParams() : null; 93 // Check on the id of the child before adding it. 94 if (layoutParams != null && layoutParams.removeBorders) { 95 if (mPaddingApplied) { 96 borderTop -= mBorderTop; 97 borderBottom -= mBorderBottom; 98 borderLeft -= mBorderLeft; 99 borderRight -= mBorderRight; 100 mPaddingApplied = false; 101 } 102 } else { 103 // Add the padding to the view group after determining if the 104 // padding already exists. 105 if (!mPaddingApplied) { 106 borderTop += mBorderTop; 107 borderBottom += mBorderBottom; 108 borderLeft += mBorderLeft; 109 borderRight += mBorderRight; 110 mPaddingApplied = true; 111 } 112 } 113 114 int previousTop = getPaddingTop(); 115 int previousBottom = getPaddingBottom(); 116 int previousLeft = getPaddingLeft(); 117 int previousRight = getPaddingRight(); 118 if (previousTop != borderTop || previousBottom != borderBottom 119 || previousLeft != borderLeft || previousRight != borderRight) { 120 setPadding(borderLeft, borderTop, borderRight, borderBottom); 121 } 122 123 super.addView(child); 124 } PreferenceFrameLayout のデフォルトのスタイルは preferenceFrameLayoutStyle で指定されている



■ 4.4 以降について

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/themes.xml 43 ... 906 1221 http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#2441 2441 http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/dimens.xml#104 104 105 16dp Holoテーマの preferenceFrameLayoutStyle には Widget.Holo.PreferenceFrameLayout が指定されています。
Widget.Holo.PreferenceFrameLayout では、左右のパディング用に
?attr/preferenceFragmentPaddingSide を参照しており、この値には @dimen/preference_fragment_padding_side がセットされています。

@dimen/preference_fragment_padding_side は、values/dimens.xml では 16dp が、values-600dp/dimens.xml では 24dp が、values-720dp/dimens.xml では 32dp が指定されています。

このため、Preferenceに指定されているFragmentに遷移した場合、左右に余白が入ります。

わかりやすいように、Fragmentの背景を赤にしています。




android:preferenceFragmentPaddingSide を指定すれば左右の余白値を変えられそうですが、public なテーマ属性ではないため、 のように指定してもコンパイルエラーになります。
次のように * をつけてテーマ属性が存在しているかどうかを無視するようにするとコンパイルできます。



■ 4.4 未満について

4.4 未満ではテーマ属性に preferenceFragmentPaddingSide がありません。

http://tools.oesf.biz/android-4.3.1_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#2420 2420 左右のパディング用には直接 @dimen/preference_fragment_padding_side が指定されています。

preferenceFragmentPaddingSide が使えないため、左右の余白を変えるには preferenceFrameLayoutStyle を上書きするしかなさそうです。
preferenceFrameLayoutStyle も public なテーマではないため、上書きするには * をつける必要があります。
ただし、4.4以降では上書きした preferenceFrameLayoutStyle の値が利用されますが、4.4 未満では指定しても挙動が変わりませんでした。残念。


■ 4.1.2 未満について

4.1.2 未満では Theme.Holo.Light に preferenceFrameLayoutStyle の指定がありません。それによって困った挙動になっています。
つまり、Theme.Holo では余白が入るのですが、Theme.Holo.Light や Theme.Holo.Light.DarkActionBar では余白が入りません。

http://tools.oesf.biz/android-4.1.1_r1.0/xref/frameworks/base/core/res/res/values/themes.xml

4.1.1 with Theme.Holo




4.1.1 with Theme.Holo.Light.DarkActionBar



「Theme.Light は parent が指定されてないので Theme を継承してるが、Theme.Holo.Light は parent に Theme.Light が指定されているので、Theme.Holo は継承されない」 ということを忘れていたのでしょうか、気をつけましょう。




■ 4.4 未満で余白をなくすには

これまで見てきたように、4.4未満ではテーマで余白サイズを変えることができません。 また、4.1.2未満では Theme.Holo.Light で余白が入らないという問題もあります。 そのため、どのバージョンのデバイスであっても余白が入らないようにしてしまうのが、この問題の現実的な解決方法でしょう。

PreferenceFrameLayoutはFragmentのコンテナになるため、親のViewGroupのpaddingを0にすることで余白を削除できます。 public class SettingsBaseFragment extends Fragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ViewParent parent = view.getParent(); ViewGroup container = (ViewGroup) parent; if (container != null) { container.setPadding(0, 0, 0, 0); } } }



0 件のコメント:

コメントを投稿