Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cd83f64

Browse files
Refactor preferences and Android Studio lessons to use Material 3 components
This commit updates the styling of preference items and Android Studio lesson items to align with Material 3 guidelines. Specific changes include: - Renamed layout files: - `item_android_studio_lesson.xml` to `item_preference.xml` - `item_android_studio_category.xml` to `item_preference_category.xml` - Updated `AndroidStudioFragment.java`: - Inflates `item_preference.xml` and `item_preference_category.xml` directly instead of using data binding for these layouts. - Updated `LessonHolder` and `CategoryHolder` to use standard `findViewById` with new Android system IDs for title, summary, icon, etc. - `LessonHolder` now inflates `item_preference_widget_open_in_new.xml` into its `widgetFrame`. - Handles visibility and clickability of the external link button more robustly. - Updated `preferences_settings.xml`: - Assigns `item_preference_category.xml` as the layout for `PreferenceCategory`. - Assigns `item_preference.xml` as the layout for individual `Preference` and `ListPreference` items. - Assigns `widget_preference_switch.xml` as the `widgetLayout` for `SwitchPreferenceCompat`. - Assigns `item_preference_widget_open_notifications.xml` and `item_preference_widget_open_in_new.xml` as `widgetLayout` for specific preferences. - Sets `app:iconSpaceReserved="false"` for preferences to allow icon visibility to be controlled by the presence of an icon. - Created `SettingsFragment.java`: - Implements custom styling for preference items within a `RecyclerView`. - Adds `PreferenceSpacingDecoration` to manage spacing between preference items. - Dynamically updates the corner radius of `MaterialCardView` for preferences to create a grouped appearance within categories (first item has rounded top corners, last item has rounded bottom corners). - Synchronizes the visibility of icon frames and widget frames based on the visibility of their content. - Added new layout files: - `item_preference_widget_open_notifications.xml`: Layout for an icon button to open notification settings. - `item_preference_widget_open_in_new.xml`: Layout for an icon button to open links in a new window/browser. - Added `ic_arrow_outward.xml` drawable. - Modified `widget_preference_switch.xml`: - Changed ID to `@android:id/switch_widget`. - Set `clickable` and `focusable` to true. - Added `preference_list_vertical_padding` dimension in `dimens.xml`. - Added padding to the bottom of `activity_help.xml`'s ScrollView content.
1 parent 7810403 commit cd83f64

File tree

10 files changed

+425
-55
lines changed

10 files changed

+425
-55
lines changed

‎app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/AndroidStudioFragment.java‎

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
import android.view.MenuItem;
1414
import android.view.View;
1515
import android.view.ViewGroup;
16+
import android.widget.FrameLayout;
1617

1718
import androidx.annotation.NonNull;
1819
import androidx.annotation.Nullable;
19-
import androidx.appcompat.widget.AppCompatImageView;
2020
import androidx.appcompat.widget.SearchView;
2121
import androidx.core.view.MenuHost;
2222
import androidx.core.view.MenuProvider;
@@ -30,12 +30,11 @@
3030
import com.d4rk.androidtutorials.java.ads.AdUtils;
3131
import com.d4rk.androidtutorials.java.ads.views.NativeAdBannerView;
3232
import com.d4rk.androidtutorials.java.databinding.FragmentAndroidStudioBinding;
33-
import com.d4rk.androidtutorials.java.databinding.ItemAndroidStudioCategoryBinding;
34-
import com.d4rk.androidtutorials.java.databinding.ItemAndroidStudioLessonBinding;
3533
import com.google.android.gms.ads.AdListener;
3634
import com.google.android.gms.ads.LoadAdError;
3735
import com.google.android.material.button.MaterialButton;
3836
import com.google.android.material.card.MaterialCardView;
37+
import com.google.android.material.imageview.ShapeableImageView;
3938
import com.google.android.material.shape.CornerFamily;
4039
import com.google.android.material.shape.ShapeAppearanceModel;
4140
import com.google.android.material.textview.MaterialTextView;
@@ -375,13 +374,13 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
375374
adView.setNativeAdLayout(R.layout.ad_android_studio_list);
376375
return new AdHolder(adView);
377376
} else if (viewType == TYPE_CATEGORY) {
378-
ItemAndroidStudioCategoryBindingbinding = ItemAndroidStudioCategoryBinding.inflate(
379-
LayoutInflater.from(parent.getContext()), parent, false);
380-
return new CategoryHolder(binding);
377+
Viewview = LayoutInflater.from(parent.getContext())
378+
.inflate(R.layout.item_preference_category, parent, false);
379+
return new CategoryHolder(view);
381380
} else {
382-
ItemAndroidStudioLessonBindingbinding = ItemAndroidStudioLessonBinding.inflate(
383-
LayoutInflater.from(parent.getContext()), parent, false);
384-
return new LessonHolder(binding);
381+
Viewview = LayoutInflater.from(parent.getContext())
382+
.inflate(R.layout.item_preference, parent, false);
383+
return new LessonHolder(view);
385384
}
386385
}
387386

@@ -425,26 +424,38 @@ static class AdHolder extends RecyclerView.ViewHolder {
425424

426425
static class LessonHolder extends RecyclerView.ViewHolder {
427426
final MaterialCardView card;
428-
final AppCompatImageView icon;
427+
final ShapeableImageView icon;
429428
final MaterialTextView title;
430429
final MaterialTextView summary;
430+
final FrameLayout widgetFrame;
431431
final MaterialButton externalButton;
432+
final FrameLayout iconFrame;
432433

433-
LessonHolder(@NonNull ItemAndroidStudioLessonBinding binding) {
434-
super(binding.getRoot());
435-
card = binding.lessonCard;
436-
icon = binding.lessonIcon;
437-
title = binding.lessonTitle;
438-
summary = binding.lessonSummary;
439-
externalButton = binding.lessonExternalIcon;
434+
LessonHolder(@NonNull View itemView) {
435+
super(itemView);
436+
card = (MaterialCardView) itemView;
437+
icon = itemView.findViewById(android.R.id.icon);
438+
title = itemView.findViewById(android.R.id.title);
439+
summary = itemView.findViewById(android.R.id.summary);
440+
widgetFrame = itemView.findViewById(android.R.id.widget_frame);
441+
iconFrame = itemView.findViewById(android.R.id.icon_frame);
442+
LayoutInflater.from(itemView.getContext())
443+
.inflate(R.layout.item_preference_widget_open_in_new, widgetFrame, true);
444+
externalButton = widgetFrame.findViewById(R.id.open_in_new);
440445
}
441446

442447
void bind(Lesson lesson, boolean first, boolean last) {
443448
if (lesson.iconRes != 0) {
444449
icon.setImageResource(lesson.iconRes);
445450
icon.setVisibility(View.VISIBLE);
451+
if (iconFrame != null) {
452+
iconFrame.setVisibility(View.VISIBLE);
453+
}
446454
} else {
447455
icon.setVisibility(View.GONE);
456+
if (iconFrame != null) {
457+
iconFrame.setVisibility(View.GONE);
458+
}
448459
}
449460
title.setText(lesson.title);
450461
if (lesson.summary != null) {
@@ -454,10 +465,19 @@ void bind(Lesson lesson, boolean first, boolean last) {
454465
summary.setVisibility(View.GONE);
455466
}
456467
boolean showExternalButton = lesson.opensInBrowser && lesson.intent != null;
468+
widgetFrame.setVisibility(showExternalButton ? View.VISIBLE : View.GONE);
457469
externalButton.setVisibility(showExternalButton ? View.VISIBLE : View.GONE);
470+
externalButton.setEnabled(showExternalButton);
471+
externalButton.setFocusableInTouchMode(showExternalButton);
458472
if (showExternalButton) {
473+
externalButton.setClickable(true);
474+
externalButton.setFocusable(true);
475+
externalButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
459476
externalButton.setOnClickListener(v -> v.getContext().startActivity(lesson.intent));
460477
} else {
478+
externalButton.setClickable(false);
479+
externalButton.setFocusable(false);
480+
externalButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
461481
externalButton.setOnClickListener(null);
462482
}
463483
itemView.setOnClickListener(v -> {
@@ -485,9 +505,9 @@ private void applyCorners(boolean first, boolean last) {
485505
static class CategoryHolder extends RecyclerView.ViewHolder {
486506
final MaterialTextView title;
487507

488-
CategoryHolder(@NonNull ItemAndroidStudioCategoryBindingbinding) {
489-
super(binding.getRoot());
490-
title = binding.categoryTitle;
508+
CategoryHolder(@NonNull ViewitemView) {
509+
super(itemView);
510+
title = itemView.findViewById(android.R.id.title);
491511
}
492512

493513
void bind(Category category) {

‎app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/settings/SettingsFragment.java‎

Lines changed: 227 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,45 @@
44
import android.content.ClipboardManager;
55
import android.content.Context;
66
import android.content.Intent;
7+
import android.graphics.Rect;
78
import android.net.Uri;
89
import android.os.Build;
910
import android.os.Bundle;
1011
import android.provider.Settings;
1112
import android.text.TextUtils;
13+
import android.util.TypedValue;
14+
import android.view.View;
15+
import android.view.ViewTreeObserver;
16+
import android.view.ViewGroup;
1217
import android.widget.Toast;
1318

19+
import androidx.annotation.NonNull;
20+
import androidx.annotation.Nullable;
1421
import androidx.preference.ListPreference;
1522
import androidx.preference.Preference;
23+
import androidx.preference.PreferenceCategory;
1624
import androidx.preference.PreferenceFragmentCompat;
25+
import androidx.preference.PreferenceGroupAdapter;
1726
import androidx.preference.SwitchPreferenceCompat;
27+
import androidx.recyclerview.widget.RecyclerView;
1828

1929
import com.d4rk.androidtutorials.java.R;
2030
import com.d4rk.androidtutorials.java.ui.components.dialogs.RequireRestartDialog;
2131
import com.d4rk.androidtutorials.java.utils.OpenSourceLicensesUtils;
32+
import com.google.android.material.card.MaterialCardView;
33+
import com.google.android.material.shape.CornerFamily;
34+
import com.google.android.material.shape.ShapeAppearanceModel;
2235

2336
public class SettingsFragment extends PreferenceFragmentCompat {
37+
@Nullable
38+
private RecyclerView settingsList;
39+
@Nullable
40+
private RecyclerView.AdapterDataObserver preferenceAdapterObserver;
41+
@Nullable
42+
private RecyclerView.OnChildAttachStateChangeListener preferenceChildAttachListener;
43+
@Nullable
44+
private ViewTreeObserver.OnGlobalLayoutListener preferenceLayoutListener;
45+
2446
@Override
2547
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
2648
setPreferencesFromResource(R.xml.preferences_settings, rootKey);
@@ -96,4 +118,208 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
96118
});
97119
}
98120
}
99-
}
121+
122+
@Override
123+
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
124+
super.onViewCreated(view, savedInstanceState);
125+
setDivider(null);
126+
setDividerHeight(0);
127+
settingsList = getListView();
128+
RecyclerView listView = settingsList;
129+
int verticalPadding = getResources().getDimensionPixelSize(R.dimen.preference_list_vertical_padding);
130+
listView.setPadding(listView.getPaddingLeft(), verticalPadding,
131+
listView.getPaddingRight(), verticalPadding);
132+
listView.setClipToPadding(false);
133+
listView.addItemDecoration(new PreferenceSpacingDecoration(requireContext()));
134+
setupPreferenceCardStyling(listView);
135+
}
136+
137+
@Override
138+
public void onDestroyView() {
139+
if (settingsList != null) {
140+
if (settingsList.getAdapter() != null && preferenceAdapterObserver != null) {
141+
settingsList.getAdapter().unregisterAdapterDataObserver(preferenceAdapterObserver);
142+
}
143+
if (preferenceChildAttachListener != null) {
144+
settingsList.removeOnChildAttachStateChangeListener(preferenceChildAttachListener);
145+
}
146+
if (preferenceLayoutListener != null) {
147+
ViewTreeObserver observer = settingsList.getViewTreeObserver();
148+
if (observer.isAlive()) {
149+
observer.removeOnGlobalLayoutListener(preferenceLayoutListener);
150+
}
151+
}
152+
}
153+
preferenceAdapterObserver = null;
154+
preferenceChildAttachListener = null;
155+
preferenceLayoutListener = null;
156+
settingsList = null;
157+
super.onDestroyView();
158+
}
159+
160+
private void setupPreferenceCardStyling(@NonNull RecyclerView listView) {
161+
Runnable updateRunnable = () -> updatePreferenceCardShapes(listView);
162+
RecyclerView.Adapter<?> adapter = listView.getAdapter();
163+
if (adapter != null) {
164+
preferenceAdapterObserver = new RecyclerView.AdapterDataObserver() {
165+
@Override
166+
public void onChanged() {
167+
updateRunnable.run();
168+
}
169+
170+
@Override
171+
public void onItemRangeInserted(int positionStart, int itemCount) {
172+
updateRunnable.run();
173+
}
174+
175+
@Override
176+
public void onItemRangeRemoved(int positionStart, int itemCount) {
177+
updateRunnable.run();
178+
}
179+
180+
@Override
181+
public void onItemRangeChanged(int positionStart, int itemCount) {
182+
updateRunnable.run();
183+
}
184+
185+
@Override
186+
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
187+
updateRunnable.run();
188+
}
189+
};
190+
adapter.registerAdapterDataObserver(preferenceAdapterObserver);
191+
}
192+
preferenceChildAttachListener = new RecyclerView.OnChildAttachStateChangeListener() {
193+
@Override
194+
public void onChildViewAttachedToWindow(@NonNull View view) {
195+
updateRunnable.run();
196+
}
197+
198+
@Override
199+
public void onChildViewDetachedFromWindow(@NonNull View view) {
200+
updateRunnable.run();
201+
}
202+
};
203+
listView.addOnChildAttachStateChangeListener(preferenceChildAttachListener);
204+
preferenceLayoutListener = updateRunnable::run;
205+
listView.getViewTreeObserver().addOnGlobalLayoutListener(preferenceLayoutListener);
206+
listView.post(updateRunnable);
207+
}
208+
209+
private void updatePreferenceCardShapes(@NonNull RecyclerView listView) {
210+
RecyclerView.Adapter<?> adapter = listView.getAdapter();
211+
if (!(adapter instanceof PreferenceGroupAdapter)) {
212+
return;
213+
}
214+
PreferenceGroupAdapter preferenceAdapter = (PreferenceGroupAdapter) adapter;
215+
int itemCount = preferenceAdapter.getItemCount();
216+
for (int position = 0; position < itemCount; position++) {
217+
Preference preference = preferenceAdapter.getItem(position);
218+
if (preference instanceof PreferenceCategory) {
219+
continue;
220+
}
221+
RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(position);
222+
if (holder == null) {
223+
continue;
224+
}
225+
View itemView = holder.itemView;
226+
if (!(itemView instanceof MaterialCardView)) {
227+
continue;
228+
}
229+
boolean first = isFirstPreferenceInSection(preferenceAdapter, position);
230+
boolean last = isLastPreferenceInSection(preferenceAdapter, position);
231+
applyRoundedCorners((MaterialCardView) itemView, first, last);
232+
syncAccessoryVisibility(itemView);
233+
}
234+
}
235+
236+
private boolean isFirstPreferenceInSection(@NonNull PreferenceGroupAdapter adapter, int position) {
237+
for (int index = position - 1; index >= 0; index--) {
238+
Preference previous = adapter.getItem(index);
239+
if (!previous.isVisible()) {
240+
continue;
241+
}
242+
if (previous instanceof PreferenceCategory) {
243+
return true;
244+
}
245+
return false;
246+
}
247+
return true;
248+
}
249+
250+
private boolean isLastPreferenceInSection(@NonNull PreferenceGroupAdapter adapter, int position) {
251+
int itemCount = adapter.getItemCount();
252+
for (int index = position + 1; index < itemCount; index++) {
253+
Preference next = adapter.getItem(index);
254+
if (!next.isVisible()) {
255+
continue;
256+
}
257+
if (next instanceof PreferenceCategory) {
258+
return true;
259+
}
260+
return false;
261+
}
262+
return true;
263+
}
264+
265+
private void applyRoundedCorners(@NonNull MaterialCardView card, boolean first, boolean last) {
266+
float dp4 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,
267+
card.getResources().getDisplayMetrics());
268+
float dp24 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24,
269+
card.getResources().getDisplayMetrics());
270+
ShapeAppearanceModel shapeAppearanceModel = card.getShapeAppearanceModel().toBuilder()
271+
.setTopLeftCorner(CornerFamily.ROUNDED, first ? dp24 : dp4)
272+
.setTopRightCorner(CornerFamily.ROUNDED, first ? dp24 : dp4)
273+
.setBottomLeftCorner(CornerFamily.ROUNDED, last ? dp24 : dp4)
274+
.setBottomRightCorner(CornerFamily.ROUNDED, last ? dp24 : dp4)
275+
.build();
276+
card.setShapeAppearanceModel(shapeAppearanceModel);
277+
}
278+
279+
private void syncAccessoryVisibility(@NonNull View itemView) {
280+
View icon = itemView.findViewById(android.R.id.icon);
281+
View iconFrame = itemView.findViewById(android.R.id.icon_frame);
282+
if (iconFrame != null) {
283+
boolean showIcon = icon != null && icon.getVisibility() == View.VISIBLE;
284+
iconFrame.setVisibility(showIcon ? View.VISIBLE : View.GONE);
285+
}
286+
View widgetFrame = itemView.findViewById(android.R.id.widget_frame);
287+
if (widgetFrame instanceof ViewGroup) {
288+
ViewGroup widgetGroup = (ViewGroup) widgetFrame;
289+
boolean hasChild = widgetGroup.getChildCount() > 0;
290+
widgetFrame.setVisibility(hasChild ? View.VISIBLE : View.GONE);
291+
if (hasChild) {
292+
for (int index = 0; index < widgetGroup.getChildCount(); index++) {
293+
View child = widgetGroup.getChildAt(index);
294+
child.setDuplicateParentStateEnabled(true);
295+
}
296+
}
297+
}
298+
}
299+
300+
private static class PreferenceSpacingDecoration extends RecyclerView.ItemDecoration {
301+
private final int spacing;
302+
303+
PreferenceSpacingDecoration(@NonNull Context context) {
304+
spacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2,
305+
context.getResources().getDisplayMetrics());
306+
}
307+
308+
@Override
309+
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
310+
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
311+
RecyclerView.Adapter<?> adapter = parent.getAdapter();
312+
if (!(adapter instanceof PreferenceGroupAdapter)) {
313+
return;
314+
}
315+
int position = parent.getChildAdapterPosition(view);
316+
if (position == RecyclerView.NO_POSITION) {
317+
return;
318+
}
319+
Preference preference = ((PreferenceGroupAdapter) adapter).getItem(position);
320+
if (!(preference instanceof PreferenceCategory)) {
321+
outRect.bottom = spacing;
322+
}
323+
}
324+
}
325+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /