赞
踩
博主声明:
转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。
由于 Android 手机五花八门,手机厂商较多,所以导致的一个问题就是屏幕分辨率各有千秋,诸如:320*480、540*960、768*1280、1080*1920 等等,因为屏幕分辨率不同,导致一个难题就是如何将我们开发的应用适配这些分辨率,这就是本文要引出的一个屏幕适配的问题。
在早期的做法呢,由于 Android project 给我们提供了不同的 drawable res 文件,如 drawable-ldpi、drawable-mdpi、drawable-hdpi、drawable-xhdpi 等等,分别对应不同的分辨率,从低分辨率到高分辨率。我们通过设计和制作不同的分辨率图片资源,放置在不同的 res 下,这样在 Android 系统加载时,会自动的去找对应 res 下的资源文件,这种方法可以做到适配效果。
可是,这样的话会导致一个比较严重的问题,因为每个 res 下都放置了图片资源文件,如果图片过多的话,会增大 apk 的体积,导致 apk 包过于庞大。还有一个是图片制作也比较费时,虽然是不同的分辨率的同一张图,修改起来也麻烦。
如今,这种做法渐渐的被放弃使用了,因为它的缺点比较明显,接下来,我们来看本文要讲的适配方案。
我们做过 Android 开发的都知道,要尽量的将控件的宽度设置成 wrap_content 或者 match_content,将大小设置为一个 dp 值,而不是具体的 px 值,这样能够有效的适配不同的分辨率。但是呢,难免一下控件需要占用屏幕的一个比例值,比如在 768 * 1280 的分辨率下,TextView 需要占一半效果,那么它的 width 就是 384 个 px;然而在 1080 * 1920 的分辨率下,这个 TextView 的 width 就变成了 540 个 px 了。
那么,要适配这样的一种方式,需要如何做呢?接下来,我们来一起看看本文的适配方案吧!
implementation 'com.android.support:percent:28.+'
接下来,我们要想让 TextView 占用屏幕的一半宽度,就可以通过设置一个百分比即可。布局文件代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <android.support.percent.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <TextView
- android:id="@+id/tv"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@color/colorAccent"
- android:gravity="center"
- android:text="博主:威威喵"
- android:textColor="#ffffff"
- app:layout_heightPercent="50%"
- app:layout_widthPercent="50%" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/tv"
- android:layout_alignParentRight="true"
- android:background="@color/colorAccent"
- android:gravity="center"
- android:text="博客:https://blog.csdn.net/smile_Running"
- android:textColor="#ffffff"
- app:layout_heightPercent="50%"
- app:layout_widthPercent="50%" />
-
- </android.support.percent.PercentRelativeLayout>
为了更好的证明它能够适配所有的分辨率,我这里开启了两个不同分辨率的模拟器,一个是 768*1280,一个是 1080*1920 的分辨率,通过对比,可以看到它们的显示效果是一致的,如下图
这种方式是最简单的一种,通过引入 percent 控件,设置 layout_widthPercent 以及 layout_heightPercent 即可。
(1)屏幕分辨率:720 * 1280
(2)屏幕分辨率:1080 * 1920
若红色(TextView)的宽度占用屏幕的一半,要想进行适配,在 720 * 1280 的分辨率中,它的宽度是 360 px,而在 1080 * 1920 的分辨率中,它的宽度是 540 px
例如,当前的设计稿像素大小为 720 * 1280 ,其中 720px 是已知的设计稿宽度,而通过代码可以获取设备的宽度,根据比例公式计算可得:1080 / 720 * 360 = 540 px 。通过封装屏幕缩放比例工具类,可以计算出当前设备宽高与设计稿的一个比例值,代码如下:
- package nd.no.xww.screenadapter;
-
- import android.content.Context;
- import android.content.res.Resources;
- import android.util.DisplayMetrics;
- import android.view.WindowManager;
-
- /**
- * @author xww
- * @desciption : 采用 px 来适配全屏幕
- * @date 2020/1/18
- * @time 19:10
- */
- public class ScreenPixels {
-
- private static ScreenPixels INSTANCE;
- private Context context;
-
- // design pixels on a prototype diagram(must float)
- private static final float DESIGN_WIDTH = 1080f;
- private static final float DESIGN_HEIGHT = 1920f;
-
- private int screenWidth;
- private int screenHeight;
-
- private ScreenPixels(Context context) {
- this.context = context;
-
- final WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- if (windowManager != null) {
- DisplayMetrics displayMetrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
-
- if (displayMetrics.widthPixels > displayMetrics.heightPixels) {
- screenWidth = displayMetrics.heightPixels;
- screenHeight = displayMetrics.widthPixels;
- } else {
- screenWidth = displayMetrics.widthPixels;
- screenHeight = displayMetrics.heightPixels;
- }
- }
- }
-
- public static ScreenPixels getInstance(Context context) {
- if (INSTANCE == null) {
- INSTANCE = new ScreenPixels(context.getApplicationContext());
- }
- return INSTANCE;
- }
-
- private int getStatusBarHeight() {
- Resources resources = context.getResources();
- if (resources != null) {
- int resId = resources.getIdentifier("status_bar_height", "dimen", "android");
- return resources.getDimensionPixelSize(resId);
- }
- return 0;
- }
-
- public int getScreenWidth() {
- return screenWidth;
- }
-
- public int getScreenHeight() {
- return screenHeight;
- }
-
- public float getScaleWidth() {
- return getScreenWidth() / DESIGN_WIDTH;
- }
-
- public float getScaleHeight() {
- return getScreenHeight() / DESIGN_HEIGHT;
- }
- }
获取到这个像素缩放比例,然后通过对 View 的大小缩放,达到适配的目的。具体可以通过自定义 RelativeLayout 或其它 ViewGroup,覆盖 onMeasure() 方法,对每一个 childView 的width、height 以及 margin 进行缩放,代码如下:
- package nd.no.xww.screenadapter;
-
- import android.content.Context;
- import android.util.AttributeSet;
- import android.view.View;
- import android.widget.RelativeLayout;
-
- /**
- * @author xww
- * @desciption : 通过缩放比设置控件的宽高、缩进等
- * @date 2020/1/18
- * @time 19:48
- */
- public class AdaptRelativeLayout extends RelativeLayout {
-
- private static final String TAG = "AdaptRelativeLayout";
-
- private boolean flag = true;
-
- public AdaptRelativeLayout(Context context) {
- super(context);
- }
-
- public AdaptRelativeLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public AdaptRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (flag) { // just measure once
- float scaleWidth = ScreenPixels.getInstance(getContext()).getScaleWidth();
- float scaleHeight = ScreenPixels.getInstance(getContext()).getScaleHeight();
- final int count = getChildCount();
- LayoutParams params;
- View childView;
- for (int i = 0; i < count; i++) {
- childView = getChildAt(i);
- params = (LayoutParams) childView.getLayoutParams();
- params.width = (int) (params.width * scaleWidth);
- params.height = (int) (params.height * scaleHeight);
- params.leftMargin = (int) (params.leftMargin * scaleWidth);
- params.rightMargin = (int) (params.rightMargin * scaleWidth);
- params.topMargin = (int) (params.topMargin * scaleHeight);
- params.bottomMargin = (int) (params.bottomMargin * scaleHeight);
- }
- flag = false;
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
这里的 onMeasure() 方法会进行测量两次,所以在缩放的时候,需要进行一个判断,如果重复测量的话,控件的大小将缩放两倍,会导致偏差。布局文件代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <nd.no.xww.screenadapter.AdaptRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <TextView
- android:layout_width="540px"
- android:layout_height="360px"
- android:background="@color/colorAccent"
- android:gravity="center"
- android:layout_marginTop="10px"
- android:layout_marginLeft="10px"
- android:text="博主:威威喵"
- android:textColor="#ffffff" />
-
- <TextView
- android:layout_width="540px"
- android:layout_height="360px"
- android:layout_alignParentRight="true"
- android:layout_alignParentBottom="true"
- android:background="@color/colorAccent"
- android:gravity="center"
- android:layout_marginBottom="10px"
- android:layout_marginRight="10px"
- android:text="博客:https://blog.csdn.net/smile_Running"
- android:textColor="#ffffff" />
-
- </nd.no.xww.screenadapter.AdaptRelativeLayout>
在布局文件中,我们应该写明当前控件的 px 值,相对于屏幕分辨率的一半。最后,我们的适配效果如下:
首先呢,通过获取 application 的 density、scaleDensity、densityApi,对当前 Activity density、scaleDensity、 进行缩放,以达到适配效果。代码如下:
- package nd.no.xww.screenadapter;
-
- import android.app.Activity;
- import android.app.Application;
- import android.content.ComponentCallbacks;
- import android.content.res.Configuration;
- import android.util.DisplayMetrics;
-
- /**
- * @author xww
- * @desciption : 根据 dp 值来适配
- * @date 2020/1/19
- * @time 17:54
- */
- public class ScreenDensity {
-
- // 设计稿的屏幕宽度 dp 值
- private static final float DESIGN_DENSITY = 360f;
- private static float appScaleDensity;
-
- public static void setDensity(Application application, Activity activity) {
- // 获取 application 的 DisplayMetrics
- DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
- float appDensity = appDisplayMetrics.density;
- appScaleDensity = appDisplayMetrics.scaledDensity;
-
- //监听字体大小变化,重新获取变化后的 appScaleDensity,适配到应用中
- application.registerComponentCallbacks(new ComponentCallbacks() {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- if (newConfig != null && newConfig.fontScale > 0) {
- appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
- }
- }
-
- @Override
- public void onLowMemory() {
- }
- });
-
- // 获取 activity 的 DisplayMetrics
- DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
- // 计算缩放比例(设备屏幕宽度 / 设计稿宽度)
- float targetDensity = appDisplayMetrics.widthPixels / DESIGN_DENSITY;
- float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
- int targetDensityApi = (int) (targetDensity * 160);
-
- activityDisplayMetrics.density = targetDensity;
- activityDisplayMetrics.scaledDensity = targetScaleDensity;
- activityDisplayMetrics.densityDpi = targetDensityApi;
- }
- }
若没有字体大小适配的话,可以不必监听系统字体大小的变化。
这里有必要解释一下,density 表示屏幕的一个物理密度,scaleDensity 表示字体显示大小的一个密度值,通常情况下都是与 density 相等的,而 densityApi 则表示它相对于屏幕密度的一个比例值,就是 dots-per-inch。
注意:在 Activity 的 setContentView 之前进行设置 density
- package nd.no.xww.screenadapter;
-
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
-
- public class MainActivity extends AppCompatActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ScreenDensity.setDensity(getApplication(), this);
- setContentView(R.layout.activity_main);
- }
- }
布局文件代码:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <TextView
- android:id="@+id/tv"
- android:layout_width="180dp"
- android:layout_height="180dp"
- android:background="@color/colorAccent"
- android:gravity="center"
- android:text="博主:威威喵"
- android:textColor="#ffffff" />
-
- <TextView
- android:layout_width="180dp"
- android:layout_height="180dp"
- android:layout_below="@+id/tv"
- android:layout_alignParentRight="true"
- android:background="@color/colorAccent"
- android:gravity="center"
- android:text="博客:https://blog.csdn.net/smile_Running"
- android:textColor="#ffffff" />
-
- </RelativeLayout>
它的一个适配最终效果如下:
如果要设置全局的一个适配,可以有两种方式,一种是抽到 BaseActivity 中进行适配每一个 Activity。第二中方式是在自己的 Application 中,监听每一个 Activity 的生命周期回调情况,代码如下:
- package nd.no.xww.screenadapter;
-
- import android.app.Activity;
- import android.app.Application;
- import android.os.Bundle;
-
- /**
- * @author xww
- * @desciption :
- * @date 2020/1/19
- * @time 19:44
- */
- public class App extends Application {
- @Override
- public void onCreate() {
- super.onCreate();
- registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
- @Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- ScreenDensity.setDensity(App.this, activity);
- }
-
- @Override
- public void onActivityStarted(Activity activity) {
-
- }
-
- @Override
- public void onActivityResumed(Activity activity) {
-
- }
-
- @Override
- public void onActivityPaused(Activity activity) {
-
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
-
- }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
-
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
-
- }
- });
- }
- }
最后,在 xml 文件中记得换成我们自己的 App name 即可。
好了,如上提供的三种适配方案都能较好的解决当前屏幕适配问题,至于如何抉择,就看你的使用场景如何了。第一种方案比较简单,如果不是自定义 View 的话,在适配起来会简单很多,第二种比较适合在自定义 View 中做统一的大小处理,第三种是一个全局的适配方案,目前这种方法也在很多 App 中运用,所以比较推荐第三种。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。