The project address
Toutiao screen adaptation scheme & vertical screen switching optimization
Begin to use
- Add the following dependencies to
module
Under thebuild.gradle
In the
dependencies {
implementation 'com. Oikawaii. Library: core: 1.0.0'
implementation 'com. Oikawaii. Library: density: 1.0.0'
}
Copy the code
Method of use
- in
Application
(note that in thesuper.onCreate()
The location of the)
package ${PACKAGE_NAME}
import android.app.Application
import com.oikawaii.library.core.android.app
import com.oikawaii.library.density.DensityUtil
class ${NAME} : Application() {
override fun onCreate(a) {
app = this
DensityUtil.init()
super.onCreate()
}
}
Copy the code
- in
Activity
(note that in thesuper.onCreate()
The location of the)
class ${NAME} : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
DensityUtil.init(this.420f, status = false, navigation = false, flag = Density.SHORT_SIDE_BASED)
super.onCreate(savedInstanceState)
}
}
}
Copy the code
preface
As for the advantages and disadvantages of Toutiao’s screen adaptation scheme, the content of relevant articles has been relatively mature, so I will not go into details here. This is mainly an issue to solve the horizontal and vertical screen switching in the comment of the paper on the landing study of Toutiao’s screen adaptation scheme.
Related articles:
An extremely low-cost Android screen adaptation (Bytedance Technical Team)
Research on Toutiao Screen Adaptation Scheme (codeGoogle)
About vertical and horizontal switching
Somehow the screen of the Android developer is the relative is not too care about problems, because the ecological terms of the Android tablet itself is not very successful, mobile phone can also use Android: screenOrientation = “portrait” to ban the use of landscape pattern, You can also use the layout-land resource folder.
In toutiao adaptation solution brings an advantage — “known width”, to fix the design width of 420DP as an example, if you want a Win10 Logo in the middle of the screen (virtual), just need to do so:
<?xml version="1.0" encoding="utf-8"? >
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_gravity="center"
android:layout_height="420dp" >
<TextView
android:background="#3CB6F8"
android:layout_width="420dp"
android:layout_height="420dp" />
<TextView
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:background="#FF554C"
android:layout_width="210dp"
android:layout_height="210dp" />
<TextView
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:background="#72D44F"
android:layout_width="210dp"
android:layout_height="210dp" />
</RelativeLayout>
Copy the code
But now the landscape looks like this:
At this time, there are two problems in the landscape screen: incomplete square display and square dislocation.
Solve horizontal screen dislocation
The reason for the misplacement of landscape is simple, but before you fix it, take a look at how it works on Android 10.
So the solution is very simple to upgrade Android 10, so the reason is very simple virtual keys.
The solution is simple:
Method 1: adding the decision of the navigation Bar is enough. In order to avoid the need to add the decision of the Status Bar at the same time.
/** * Initializes Density in the Activity@paramThe design width of the ruler UI *@paramStatus Specifies whether to include the title bar * when setting the base size@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions
fun init(activity: Activity, ruler: Float, status: Boolean, navigation: Boolean)
val width = DimenUtil.width(navigation)
val height = DimenUtil.height(status, navigation)
val density = width / ruler
val res = activity.resources
val metrics = res.displayMetrics
metrics.density = density
metrics.densityDpi = (160 * density).toInt()
metrics.scaledDensity = density * (scaledDensity / this.density)
scale = this.density / density
setBitmapDensity(metrics.densityDpi)
}
Copy the code
Method 2: Check automatically according to SystemUiVisibility.
/** * Initializes Density in the Activity@paramDesign width of ruler UI */
fun init(activity: Activity, ruler: Float) {
val f = activity.window.decorView.systemUiVisibility
val sf1 = View.SYSTEM_UI_FLAG_FULLSCREEN
val sf2 = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
val nf = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
val sf = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
val navigation = ((f and sf) == sf) || ((f and nf) == nf)
val status = ((f and sf) == sf) || ((f and sf1) == sf1) || ((f and sf2) == sf2)
val width = DimenUtil.width(navigation)
val height = DimenUtil.height(status, navigation)
val density = width / ruler
val res = activity.resources
val metrics = res.displayMetrics
metrics.density = density
metrics.densityDpi = (160 * density).toInt()
metrics.scaledDensity = density * (scaledDensity / this.density)
scale = this.density / density
setBitmapDensity(metrics.densityDpi)
}
Copy the code
(Refer to Android common utility class (based on Kotlin) and relevant source code for calculation and pit removal of Navigation Bar and Status Bar.)
Resolve incomplete square display
As for the incomplete display of the square, in fact, there is a foreshadowing in front of it — “known width”. It is this advantage that brings about the display problem of the landscape screen. The layout file only uses a specific DP value to set the size, and this DP value is based on the design width.
The solution, too, is simple:
Method 1: set the base edge (e.g., width based, height based, short based, long based), set the design size for the base edge.
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@IntDef({ Density.WIDTH_BASED, Density.HEIGHT_BASED, Density.LONG_SIDE_BASED, Density.SHORT_SIDE_BASED })
@Retention(RetentionPolicy.SOURCE)
public @interface Density {
int WIDTH_BASED = 1;
int HEIGHT_BASED = 2;
int LONG_SIDE_BASED = 3;
int SHORT_SIDE_BASED = 4;
}
Copy the code
Note the height of the Status Bar when using height as the base edge.
/** * Initializes Density in the Activity@paramThe design width of the ruler UI *@paramStatus Specifies whether to include the title bar * when setting the base size@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions
fun init(activity: Activity, ruler: Float, status: Boolean, navigation: Boolean.@Density flag: Int) {
val width = DimenUtil.width(navigation)
val height = DimenUtil.height(status, navigation)
val pixels = when(flag) {
Density.WIDTH_BASED -> width
Density.HEIGHT_BASED -> height
Density.SHORT_SIDE_BASED -> if(app.isPortrait) width else height
Density.LONG_SIDE_BASED -> if(app.isLandscape) width else height
else -> 0
}
val density = pixels / ruler
val res = activity.resources
val metrics = res.displayMetrics
metrics.density = density
metrics.densityDpi = (160 * density).toInt()
metrics.scaledDensity = density * (scaledDensity / this.density)
scale = this.density / density
setBitmapDensity(metrics.densityDpi)
}
Copy the code
(For the Status judgment of Navigation Bar and Status Bar, refer to common Android tool classes (based on Kotlin) and relevant source code.)
Method 2: With width as the reference edge, set different design widths for landscape screen and portrait screen. (Since the Status Bar is independent of width, you can remove the size of the Status Bar)
/** * Initializes Density in the Activity@paramPortRuler UI design width when portrait *@paramDesign width of landRuler UI in landscape *@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions
fun init(activity: Activity, portRuler: Float, landRuler: Float, navigation: Boolean) {
val width = DimenUtil.width(navigation)
val ruler = if(app.isLandscape) landRuler else portRuler
val density = width / ruler
val res = activity.resources
val metrics = res.displayMetrics
metrics.density = density
metrics.densityDpi = (160 * density).toInt()
metrics.scaledDensity = density * (scaledDensity / this.density)
scale = this.density / density
setBitmapDensity(metrics.densityDpi)
}
Copy the code
Appendix — Core code
(See Android common utility classes (based on Kotlin) for the implementation of some methods.)
- DensityUtil.kt
import android.view.View
import android.app.Activity
import android.util.DisplayMetrics
import android.content.res.Configuration
import android.content.ComponentCallbacks
import com.oikawaii.library.core.android.app
import com.oikawaii.library.core.android.util.DimenUtil
import com.oikawaii.library.core.android.ktx.isPortrait
import com.oikawaii.library.core.android.ktx.isLandscape
/ * * *@author Sukcria Miksria
* @version2018/10/01. * * /
object DensityUtil : ComponentCallbacks {
var scale = 0f // Screen zoom
private var density: Float = 0f / / Application DisplayMetrics
private var scaledDensity: Float = 0f //
private lateinit var metrics: DisplayMetrics / / Application of Density
/** * Initializes Metrics in Application */
fun init(a) {
// Get application DisplayMetrics
metrics = app.resources.displayMetrics
// Determine whether initialization is required
if(density ! =0f) return
/ / initialization
density = metrics.density
scaledDensity = metrics.scaledDensity
// Listen for font changes
app.registerComponentCallbacks(this)}/** * Initializes Density in the Activity@paramThe design width of the ruler UI is 420dp */ by default
fun init(activity: Activity, ruler: Float = 420f, @Density flag: Int = Density.SHORT_SIDE_BASED) {
val f = activity.window.decorView.systemUiVisibility
val sf1 = View.SYSTEM_UI_FLAG_FULLSCREEN
val sf2 = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
val nf = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
val sf = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
val navigation = ((f and sf) == sf) || ((f and nf) == nf)
val status = ((f and sf) == sf) || ((f and sf1) == sf1) || ((f and sf2) == sf2)
init(activity, ruler, status, navigation, flag)
}
/** * Initializes Density in the Activity@paramThe design width of the ruler UI is 420dp * by default@paramStatus Specifies whether to include the title bar * when setting the base size@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions
fun init(activity: Activity, ruler: Float = 420f, status: Boolean = false, navigation: Boolean = false.@Density flag: Int = Density.SHORT_SIDE_BASED) {
val width = DimenUtil.width(navigation)
val height = DimenUtil.height(status, navigation)
val pixels = when(flag) {
Density.WIDTH_BASED -> width
Density.HEIGHT_BASED -> height
Density.SHORT_SIDE_BASED -> if(app.isPortrait) width else height
Density.LONG_SIDE_BASED -> if(app.isLandscape) width else height
else -> 0
}
init(activity.resources.displayMetrics, pixels / ruler)
}
/** * Initializes Density in the Activity@paramPortRuler UI design width when portrait, default is 420dp *@paramThe design width of landRuler UI in landscape. Default is 980dp */
fun init(activity: Activity, portRuler: Float = 420f, landRuler: Float) {
val f = activity.window.decorView.systemUiVisibility
val nf = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
val sf = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
val navigation = ((f and sf) == sf) || ((f and nf) == nf)
init(activity, portRuler, landRuler, navigation)
}
/** * Initializes Density in the Activity@paramPortRuler UI design width when portrait, default is 420dp *@paramThe design width of landRuler UI in landscape. Default is 980dp *@paramNavigation specifies whether to include the virtual key */ when setting the baseline dimensions
fun init(activity: Activity, portRuler: Float = 420f, landRuler: Float, navigation: Boolean = false) {
val pixels = DimenUtil.width(navigation)
val ruler = if(app.isLandscape) landRuler else portRuler
init(activity.resources.displayMetrics, pixels / ruler)
}
/** * Initializes the Activity's Density */
private fun init(metrics: DisplayMetrics, density: Float) {
metrics.density = density
metrics.densityDpi = (160 * density).toInt()
metrics.scaledDensity = density * (scaledDensity / this.density)
scale = this.density / density
setBitmapDensity(metrics.densityDpi)
}
/** * Set the Bitmap's default screen density * Since the Bitmap's screen density is configured to read, it needs to be forcibly changed using reflection *@paramDensity Screen density */
private fun setBitmapDensity(density: Int) {
try {
val cls = Class.forName("android.graphics.Bitmap")
val field = cls.getDeclaredField("sDefaultDensity")
field.isAccessible = true
field.set(null, density)
field.isAccessible = false
} catch (e: ClassNotFoundException) {
} catch (e: NoSuchFieldException) {
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
/** * callback for font changes */
override fun onLowMemory(a) {}
/** * callback for font changes */
override fun onConfigurationChanged(newConfig: Configuration) {
// Reassign appScaledDensity after the font change
if (newConfig.fontScale > 0) scaledDensity = metrics.scaledDensity
}
}
Copy the code
- Density.java
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/ * * *@author Sukcria Miksria
* @version2019/10/04 * /
@IntDef({ Density.WIDTH_BASED, Density.HEIGHT_BASED, Density.LONG_SIDE_BASED, Density.SHORT_SIDE_BASED })
@Retention(RetentionPolicy.SOURCE)
public @interface Density {
int WIDTH_BASED = 1;
int HEIGHT_BASED = 2;
int LONG_SIDE_BASED = 3;
int SHORT_SIDE_BASED = 4;
}
Copy the code