public class MainActivity extends AppCompatActivity {
private static final float BASE_FONT_SIZE = 16f ; //Base font size, in SP
private static final float BASE_SCREEN_WIDTH = 360f ; //Base screen width, in DP
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_main ) ;
//Get screen width
int screenWidth = getScreenWidth ( this ) ;
//Calculate dynamic font size
float dynamicFontSize = calculateDynamicFontSize ( screenWidth ) ;
//Set the font size of TextView
TextView textView = findViewById ( R . id . text_view ) ; textView . setTextSize ( TypedValue . COMPLEX_UNIT_SP , dynamicFontSize ) ;
}
/** *Get screen width (unit: DP) */
private static int getScreenWidth ( Context context ) {
DisplayMetrics displayMetrics = context . getResources ( ) . getDisplayMetrics ( ) ;
return ( int ) ( displayMetrics . widthPixels / displayMetrics . density ) ;
}
/** *Calculate dynamic font size (unit: SP) */
private static float calculateDynamicFontSize ( float screenWidth ) {
return BASE_FONT_SIZE * ( screenWidth / BASE_SCREEN_WIDTH ) ;
}
}
-
We have defined the base font size BASE_FONT_SIZE 16sp, and the reference screen width BASE_SCREEN_WIDTH 360 dp. -
stay onCreate Method, we first obtain the width of the current screen (in dp), and then call calculateDynamicFontSize Method to calculate the dynamic font size (unit: sp). -
Finally, we set the calculated dynamic font size to TextView On.
-
We assume that the base font size 16sp corresponds to the base screen width 360dp. -
When the screen width changes, we can calculate the new font size according to the proportion of the screen width. -
The specific calculation formula is: dynamicFontSize = BASE_FONT_SIZE * (screenWidth / BASE_SCREEN_WIDTH) , where screenWidth Is the width of the current screen (in dp).
-
Using sp as the font unit Android recommends using sp (scale independent pixels) as the font unit, because sp will scale according to the user's font size settings to ensure that the text size is reasonable on different devices. -
Inherit the Application class and override the attachBaseContext method. When the application starts, you can override the attachBaseContext Method, the font size of the entire application is scaled and adapted here. -
The TextUtil tool class Android provides TextUtil Tool class, which can scale text content. Call the TextUtil.scale(CharSequence source, float proportion) Method.
public class MyApplication extends Application {
private static final float DEFAULT_FONT_SCALE = 1.0f ;
@Override
protected void attachBaseContext ( Context base ) {
super . attachBaseContext ( base ) ;
//Get the font scaling of the current system
float fontScale = base . getResources ( ) . getConfiguration ( ) . fontScale ;
//Calculate the scale to be scaled
float scale = fontScale / DEFAULT_FONT_SCALE ;
//Scales the font of the entire application
ResourcesCompat . getFont ( base ) . setCompatibilityScaling ( scale ) ;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_main ) ;
TextView textView = findViewById ( R . id . text_view ) ;
//Scale the text of a single TextView textView . setText ( TextUtils . scale ( textView . getText ( ) , 1.2f ) ) ;
}
}
-
Custom MyApplication Class overrides attachBaseContext Method to obtain the font scaling ratio of the current system, and scale and adapt the font of the entire application. -
stay MainActivity , we use TextUtils.scale() Method vs. single TextView The text of is scaled 1.2 times.
<LinearLayout ... app:autoSizeTextType="uniform"> <TextView android:textSize="16sp" ... /> </LinearLayout>
-
DisplayCutout API : This API allows developers to obtain information about the "groove" area on the screen, including location, size, etc., so that it can better adapt to the application interface. -
Window Insets : Android 9.0 also provides the Window Insets mechanism, which allows developers to know the area where the current window is blocked by the system UI, so as to adjust the interface layout. -
Note friendly navigation bar: The navigation bar of Android 9.0 can automatically adapt to the special-shaped screen, and will automatically avoid when there are grooves.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_main ) ;
//Get DecorView of Window
final View decorView = getWindow ( ) . getDecorView ( ) ;
//Set OnApplyWindowInsets listener decorView . setOnApplyWindowInsetsListener ( new View . OnApplyWindowInsetsListener ( ) {
@Override
public WindowInsets onApplyWindowInsets ( View v , WindowInsets insets ) {
//Get the "notch" area around the screen
DisplayCutout displayCutout = insets . getDisplayCutout ( ) ;
if ( displayCutout != null ) {
//Get the location and size of the "groove"
Rect safeInsets = displayCutout . getSafeInsetLeft ( ) , safeInsets = displayCutout . getSafeInsetTop ( ) , safeInsets = displayCutout . getSafeInsetRight ( ) , safeInsets = displayCutout . getSafeInsetBottom ( ) ;
//Adjust the interface layout according to the "groove" information
adjustLayoutForCutout ( decorView , safeInsets ) ;
}
//Return the insets information for the system to continue processing
return insets ;
}
} ) ;
}
private void adjustLayoutForCutout ( View view , Rect safeInsets ) {
//Adjust the inner margin of the view according to the "Groove" information view . setPadding ( safeInsets . left , safeInsets . top , safeInsets . right , safeInsets . bottom ) ;
//If the view is a ViewGroup, adjust the child view recursively
if ( view instanceof ViewGroup ) {
ViewGroup viewGroup = ( ViewGroup ) view ;
for ( int i = zero ; i < viewGroup . getChildCount ( ) ; i ++ ) {
adjustLayoutForCutout ( viewGroup . getChildAt ( i ) , safeInsets ) ;
}
}
}
}
-
We are Window Of DecorView Set OnApplyWindowInsetsListener Listener, in which you can get the information about the "notch" of the screen. -
We passed DisplayCutout Obtain the position and size of the "groove" with Rect And pass these information to adjustLayoutForCutout method. -
adjustLayoutForCutout Method will traverse DecorView And all its children View`, And adjust their inner margin according to the "groove" information, so as to avoid the content being blocked by the "groove".
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity" ;
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_main ) ;
//Get DecorView of Window
final View decorView = getWindow ( ) . getDecorView ( ) ;
//Set OnApplyWindowInsets listener decorView . setOnApplyWindowInsetsListener ( new View . OnApplyWindowInsetsListener ( ) {
@Override
public WindowInsets onApplyWindowInsets ( View v , WindowInsets insets ) {
//Get the area blocked by the system UI
Rect systemWindowInsets = new Rect ( insets . getSystemWindowInsetLeft ( ) , insets . getSystemWindowInsetTop ( ) , insets . getSystemWindowInsetRight ( ) , insets . getSystemWindowInsetBottom ( )
) ;
//Get the "safe area" (i.e., the area not covered)
Rect displayCutoutSafeInsets = insets . getDisplayCutout ( ) != null
? insets . getDisplayCutout ( ) . getSafeInsets ( )
: new Rect ( ) ;
//Print debugging information
Log . d ( TAG , "System UI insets: " + systemWindowInsets ) ;
Log . d ( TAG , "Display cutout safe insets: " + displayCutoutSafeInsets ) ;
//Adjust the interface layout according to the insets information
adjustLayoutForInsets ( decorView , systemWindowInsets , displayCutoutSafeInsets ) ;
//Return the insets information for the system to continue processing
return insets ;
}
} ) ;
}
private void adjustLayoutForInsets ( View view , Rect systemWindowInsets , Rect displayCutoutSafeInsets ) {
//Adjust the inner margin of the view according to the blocked area of the system UI view . setPadding ( systemWindowInsets . left + displayCutoutSafeInsets . left , systemWindowInsets . top + displayCutoutSafeInsets . top , systemWindowInsets . right + displayCutoutSafeInsets . right , systemWindowInsets . bottom + displayCutoutSafeInsets . bottom ) ;
//If the view is a ViewGroup, adjust the child view recursively
if ( view instanceof ViewGroup ) {
ViewGroup viewGroup = ( ViewGroup ) view ;
for ( int i = zero ; i < viewGroup . getChildCount ( ) ; i ++ ) {
adjustLayoutForInsets ( viewGroup . getChildAt ( i ) , systemWindowInsets , displayCutoutSafeInsets ) ;
}
}
}
}
-
We are Window Of DecorView Set OnApplyWindowInsetsListener Listener, in which you can obtain the area blocked by the system UI and the "safe area" (i.e., the area not blocked). -
We passed WindowInsets Object's getSystemWindowInsetXXX() Method to obtain the area blocked by the system UI, and use the getDisplayCutout().getSafeInsets() Method to get the Security Zone. -
We will transfer the information of these two regions to adjustLayoutForInsets Method, which will traverse DecorView And all its children View , and adjust their inner margin according to the occluded region information to avoid the content being occluded.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_main ) ;
//Get LayoutParams of Window
WindowManager . LayoutParams layoutParams = getWindow ( ) . getAttributes ( ) ;
//Set layoutParams to FLAG_LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
//This allows the navigation bar to automatically fit into the screen "notch" layoutParams . layoutInDisplayCutoutMode = WindowManager . LayoutParams . LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES ;
getWindow ( ) . setAttributes ( layoutParams ) ;
}
}
-
Get Current Window Of LayoutParams -
take layoutInDisplayCutoutMode Property set to LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 。 This value indicates that the navigation bar of the application will automatically adapt to the "groove" of the screen to avoid the content being blocked. -
The modified LayoutParams apply to Window On.
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT : Default mode, the system will automatically fit the "groove". LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER : Never let content enter the "groove" area. LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES : Fits the "notch", but only takes effect when the "notch" appears on the short side (top or bottom) of the screen. LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS : Always let content enter the "groove" area.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity" ;
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_main ) ;
//Get DecorView of Window
final View decorView = getWindow ( ) . getDecorView ( ) ;
//Set OnApplyWindowInsets listener decorView . setOnApplyWindowInsetsListener ( new View . OnApplyWindowInsetsListener ( ) {
@Override
public WindowInsets onApplyWindowInsets ( View v, WindowInsets insets ) {
//Get window mode information
int windowingMode = insets . getWindowingMode ( ) ;
//Adjust the interface layout according to the window mode
adjustLayoutForWindowingMode ( decorView, windowingMode ) ;
//Return the insets information for the system to continue processing
return insets ;
}
} ) ;
}
private void adjustLayoutForWindowingMode ( View view, int windowingMode ) {
switch ( windowingMode ) {
case WINDOWING_MODE_MULTI_WINDOW :
//Layout adjustment in multi window mode
handleMultiWindowMode ( view ) ;
break ;
case WINDOWING_MODE_FREEFORM :
//Layout adjustment in free mode
handleFreeformMode ( view ) ;
break ;
default :
//Layout adjustment in full screen mode
handleFullscreenMode ( view ) ;
break ;
}
//If the view is a ViewGroup, adjust the child view recursively
if ( view instanceof ViewGroup ) {
ViewGroup viewGroup = ( ViewGroup ) view ;
for ( int i = zero ; i < viewGroup . getChildCount ( ) ; i ++ ) {
adjustLayoutForWindowingMode ( viewGroup . getChildAt ( i ) , windowingMode ) ;
}
}
}
private void handleMultiWindowMode ( View view ) {
//Layout adjustment logic in multi window mode, such as:
//- Resize font
//- Resize controls
//- Adjust control spacing
// - ...
}
private void handleFreeformMode ( View view ) {
//Layout adjustment logic in free mode, such as:
//- Resize font
//- Resize controls
//- Adjust control spacing
// - ...
}
private void handleFullscreenMode ( View view ) {
//Layout adjustment logic in full screen mode, such as:
//- Restore font size
//- Restore control size
//- Restore control spacing
// - ...
}
}
-
We are Window Of DecorView Set OnApplyWindowInsetsListener Listener, in which the current window mode is obtained. -
We passed WindowInsets Object's getWindowingMode() Method to get the current window mode and pass it to the adjustLayoutForWindowingMode method. adjustLayoutForWindowingMode Method will call the corresponding processing method according to the current window mode (multi window, free mode or full screen mode)( handleMultiWindowMode 、 handleFreeformMode or handleFullscreenMode )。 -
In these processing methods, we can adjust the interface layout according to different window modes, such as adjusting font size, control size, control spacing, etc.
-
Try to use constrained layout ConstraintLayout to reduce nesting levels -
Properly use hard coded values to avoid misuse of resource files -
Use absolute coordinates carefully, and pay attention to horizontal and vertical screen switching -
Reduce overuse of wrap_content -
Use the tools namespace tag to simulate data for easy preview and adjustment -
Keep up with new Android features and screen innovation