Skip to content

Commit

Permalink
[NavigationBar][NavigationRail] Removed font scaling from NavigationB…
Browse files Browse the repository at this point in the history
…arView item labels.

PiperOrigin-RevId: 426264069
  • Loading branch information
hunterstich authored and pekingme committed Feb 4, 2022
1 parent b4cab87 commit d66676f
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 21 deletions.
Expand Up @@ -34,6 +34,7 @@
import androidx.appcompat.widget.TooltipCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
Expand Down Expand Up @@ -63,6 +64,7 @@
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.motion.MotionUtils;
import com.google.android.material.resources.MaterialResources;

/**
* Provides a view that will be used to render destination items inside a {@link
Expand Down Expand Up @@ -214,7 +216,7 @@ public void initialize(@NonNull MenuItemImpl itemData, int menuType) {
/**
* Remove state so this View can be reused.
*
* Item Views are held in a pool and reused when the number of menu items to be shown changes.
* <p>Item Views are held in a pool and reused when the number of menu items to be shown changes.
* This will be called when this View is released from the pool.
*
* @see NavigationBarMenuView#buildMenuView()
Expand All @@ -230,8 +232,8 @@ void clear() {
* If this item's layout contains a container which holds the icon and active indicator, return
* the container. Otherwise, return the icon image view.
*
* This is needed for clients who subclass this view and set their own item layout resource which
* might not container an icon container or active indicator view.
* <p>This is needed for clients who subclass this view and set their own item layout resource
* which might not container an icon container or active indicator view.
*/
private View getIconOrContainer() {
return iconContainer != null ? iconContainer : icon;
Expand Down Expand Up @@ -371,8 +373,8 @@ public void onAnimationUpdate(ValueAnimator animation) {
/**
* Refresh the state of this item if it has been initialized.
*
* This is useful if parameters calculated based on this item's checked state (label visibility,
* indicator state, iconContainer position) have changed and should be recalculated.
* <p>This is useful if parameters calculated based on this item's checked state (label
* visibility, indicator state, iconContainer position) have changed and should be recalculated.
*/
private void refreshChecked() {
if (itemData != null) {
Expand Down Expand Up @@ -621,15 +623,32 @@ public void setIconSize(int iconSize) {
}

public void setTextAppearanceInactive(@StyleRes int inactiveTextAppearance) {
TextViewCompat.setTextAppearance(smallLabel, inactiveTextAppearance);
setTextAppearanceWithoutFontScaling(smallLabel, inactiveTextAppearance);
calculateTextScaleFactors(smallLabel.getTextSize(), largeLabel.getTextSize());
}

public void setTextAppearanceActive(@StyleRes int activeTextAppearance) {
TextViewCompat.setTextAppearance(largeLabel, activeTextAppearance);
setTextAppearanceWithoutFontScaling(largeLabel, activeTextAppearance);
calculateTextScaleFactors(smallLabel.getTextSize(), largeLabel.getTextSize());
}

/**
* Remove font scaling if the text size is in scaled pixels.
*
* <p>Labels are instead made accessible by showing a scaled tooltip on long press of a
* destination. If the given {@code textAppearance} is 0 or does not have a textSize, this method
* will not remove the existing scaling from the {@code textView}.
*/
private static void setTextAppearanceWithoutFontScaling(
TextView textView, @StyleRes int textAppearance) {
TextViewCompat.setTextAppearance(textView, textAppearance);
int unscaledSize =
MaterialResources.getUnscaledTextSize(textView.getContext(), textAppearance, 0);
if (unscaledSize != 0) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, unscaledSize);
}
}

public void setTextColor(@Nullable ColorStateList color) {
if (color != null) {
smallLabel.setTextColor(color);
Expand Down Expand Up @@ -698,8 +717,8 @@ public void setActiveIndicatorWidth(int width) {
}

/**
* Update the active indicators width and height for the available width and label
* visibility mode.
* Update the active indicators width and height for the available width and label visibility
* mode.
*
* @param availableWidth The total width of this item layout.
*/
Expand Down
Expand Up @@ -16,12 +16,16 @@

package com.google.android.material.resources;

import com.google.android.material.R;

import static android.util.TypedValue.COMPLEX_UNIT_MASK;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.appcompat.content.res.AppCompatResources;
Expand All @@ -30,6 +34,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.annotation.StyleableRes;

/**
Expand Down Expand Up @@ -190,6 +195,60 @@ public static boolean isFontScaleAtLeast2_0(@NonNull Context context) {
return context.getResources().getConfiguration().fontScale >= FONT_SCALE_2_0;
}

/**
* Return the {@code R.styleable.TextAppearance_android_textSize} value from a text appearance
* style at its density scaled value only.
*
* If the text size from the text appearance is using dp, the density scaled value will be
* returned. If the text size is using sp, the raw value will be scaled according to display
* density but not font scale, as if it were a dp value.
*
* This is used for components that do not scale their text due to being space constrained and
* instead offer alternative methods of showing scaled text.
*/
public static int getUnscaledTextSize(
@NonNull Context context, @StyleRes int textAppearance, int defValue) {
if (textAppearance == 0) {
return defValue;
}

TypedArray a = context.obtainStyledAttributes(textAppearance, R.styleable.TextAppearance);
TypedValue v = new TypedValue();
boolean available = a.getValue(R.styleable.TextAppearance_android_textSize, v);
a.recycle();

if (!available) {
return defValue;
}

// If the resource is in scaled pixels (sp) manually unpack the resource and scale to density
// but not font scale.
if (getComplexUnit(v) == TypedValue.COMPLEX_UNIT_SP) {
// Get the raw value. If text size is set to 14sp in the dimen file, this will return 14.
// Scale the raw value using density and round to avoid truncating.
return Math.round(
TypedValue.complexToFloat(v.data) * context.getResources().getDisplayMetrics().density);
}

// If the resource is not is sp, return with regular resource system scaling.
return TypedValue.complexToDimensionPixelSize(
v.data, context.getResources().getDisplayMetrics());
}

/**
* Return the complex unit type for the given value.
*
* <p>This is a compat method of {@link TypedValue#getComplexUnit()}, which is only available on
* API 22 and above.
*/
private static int getComplexUnit(TypedValue tv) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) {
return tv.getComplexUnit();
} else {
return (COMPLEX_UNIT_MASK & (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT));
}
}

/**
* Returns the @StyleableRes index that contains value in the attributes array. If both indices
* contain values, the first given index takes precedence and is returned.
Expand Down
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.material.resources;

import com.google.android.material.R;

import static android.content.Context.WINDOW_SERVICE;
import static com.google.common.truth.Truth.assertThat;

import android.content.res.Configuration;
import androidx.appcompat.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;

@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
public class MaterialResourcesTest {

private ActivityController<AppCompatActivity> activityController;

@Before
public void createActivity() {
ApplicationProvider.getApplicationContext()
.setTheme(R.style.Theme_Material3_DayNight_NoActionBar);
activityController = Robolectric.buildActivity(AppCompatActivity.class).create();
}

@Test
public void testGetUnscaledDimensionPixelSize_returnsSameValueWhenFontScalingIsOff() {
AppCompatActivity activity = activityController.get();
int spResult =
MaterialResources.getUnscaledTextSize(
activity, R.style.TextAppearance_Test_UsesSP, 0);
int dpResult =
MaterialResources.getUnscaledTextSize(
activity, R.style.TextAppearance_Test_UsesDp, 0);
assertThat(spResult).isEqualTo(dpResult);
}

@Test
public void testUnscaledDimensionPixelSize_differsFromDimensionPixelSize() {
float fontScale = 1.7F;
setFontScale(activityController, fontScale);
AppCompatActivity activity = activityController.get();
int unscaledSp =
MaterialResources.getUnscaledTextSize(
activity, R.style.TextAppearance_Test_UsesSP, 0);
int scaledSp = activity.getResources().getDimensionPixelSize(
R.dimen.material_text_size_sp);
assertThat(unscaledSp).isNotEqualTo(scaledSp);
assertThat(scaledSp).isEqualTo(Math.round(unscaledSp * fontScale));
}

@Test
@Config(qualifiers = "xxxhdpi")
public void
testGetUnscaledDimensionPixelSize_returnsSameValueWhenFontScalingIsOnWithHighDensity() {
setFontScale(activityController, 1.3f);
AppCompatActivity activity = activityController.get();
int spResult =
MaterialResources.getUnscaledTextSize(
activity, R.style.TextAppearance_Test_UsesSP, 0);
int dpResult =
MaterialResources.getUnscaledTextSize(
activity, R.style.TextAppearance_Test_UsesDp, 0);
assertThat(spResult).isEqualTo(dpResult);
}

@Test
public void testUnscaledDimensionPixelSize_noTextSizeAvailable() {
AppCompatActivity activity = activityController.get();
int noSizeResult =
MaterialResources.getUnscaledTextSize(
activity, R.style.TextAppearance_Test_NoTextSize, 18);
assertThat(noSizeResult).isEqualTo(18);
}

@Test
public void testUnscaledDimensionPixelSize_noTextAppearanceAvailable() {
AppCompatActivity activity = activityController.get();
int noTextAppearanceResult =
MaterialResources.getUnscaledTextSize(
activity, 0, 22);
assertThat(noTextAppearanceResult).isEqualTo(22);
}

private static void setFontScale(
ActivityController<AppCompatActivity> activityController, float scale) {
AppCompatActivity activity = activityController.get();
Configuration configuration = activity.getResources().getConfiguration();
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
WindowManager wm = (WindowManager) activity.getSystemService(WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
configuration.fontScale = scale;
metrics.scaledDensity = configuration.fontScale * metrics.density;
activity.getResources().updateConfiguration(configuration, metrics);
activityController.configurationChange(configuration);
}
}
@@ -1,21 +1,25 @@
<resources>
<!--
Copyright 2019 The Android Open Source Project
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>

<dimen name="mtrl_large_touch_target">100dp</dimen>
<dimen name="action_bar_size">16dp</dimen>
<dimen name="default_dimension">100dp</dimen>

<dimen name="material_text_size_sp">14sp</dimen>
<dimen name="material_text_size_dp">14dp</dimen>
</resources>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<style name="TextAppearance.Test.UsesSP" parent="TextAppearance.Material3.TitleSmall">
<item name="android:textSize">@dimen/material_text_size_sp</item>
</style>
<style name="TextAppearance.Test.UsesDp" parent="TextAppearance.Material3.TitleSmall">
<item name="android:textSize">@dimen/material_text_size_dp</item>
</style>
<style name="TextAppearance.Test.NoTextSize" parent=""/>
</resources>

0 comments on commit d66676f

Please sign in to comment.