Android 屏幕适配

Android 屏幕适配

常见方案

限定符适配

尺寸限定符
限定符描述
layout-small小屏幕
layout-normal基准屏幕
layout-large大屏幕
layout-xlarge超大屏幕
屏幕方向限定符
限定符描述
layout-land横向
layout-port纵向
分辨率限定符
限定符描述
drawable-ldpi<= 120dpi
drawable-mdpi<= 160dpi
drawable-hdpi<= 240dpi
drawable-xhdpi<= 320dpi
drawable-xxhdpi<= 480dpi
drawable-xxxhdpi<= 640dpi
drawable-nodpi适用于所有密度的资源。这些是与密度无关的资源。无论当前屏幕的密度是多少,系统都不会缩放以此限定符标记的资源。
drawable-tvdpi适用于密度介于 mdpi 和 hdpi 之间的屏幕(约 213dpi)的资源。这不属于“主要”密度组。它主要用于电视,而大多数应用都不需要它。对于大多数应用而言,提供 mdpi 和 hdpi 资源便已足够,系统将视情况对其进行缩放。如果您发现有必要提供 tvdpi 资源,应按一个系数来确定其大小,即 1.33*mdpi。例如,如果某张图片在 mdpi 屏幕上的大小为 100px x 100px,那么它在 tvdpi 屏幕上的大小应该为 133px x 133px。
其他限定符
名称限定符描述
最小宽度限定符swdp 例如sw600dp, sw720dp屏幕的最小尺寸,就是屏幕可用区域的最小尺寸,是指屏幕可用高度或宽度的最小值(你可以默认是屏幕的最小宽度).你能用这个限定符确保,无论屏幕方向如何,这个限定符修饰下的布局需要的屏幕最小尺寸是Ndp. 例如,如果你的布局在运行时需要的最小屏幕宽度是600dp,则你可以利用这个限定符创建布局资源目录res/layout-sw600dp.只有当屏幕的最小宽度或最小高度是600dp时,系统才会使用这些布局文件或者资源文件.最小屏幕宽度是固定设备的特有屏幕尺寸,当屏幕方向发生变化时,设备的最小宽度值不变.设备的最小宽度值要考虑屏幕的尺寸和系统UI.例如,如果在屏幕上有一些系统持久化UI元素,则系统的最小宽度值要比实现的屏幕尺寸小一些,因为这些系统的UI元素你的应用是无法使用到的.当你使用之前的广义限定符是,你可以定义连续的一系列限定符.用最小宽度来决定广义屏幕尺寸是有意义的,是因为宽度是影响你UI设计的关键因素.UI在竖直方向上会经常滚动,但是在水平方向上往往是固定的布局.可见不论是适配手机或者平板,宽度往往都是布局的关键因素.因此,你需要关心你手机上的最小宽度值.
屏幕可用宽度wdp Examples: w720p w1024p指定资源使用时需要的最小宽度.当屏幕方向发生变化时,系统会调整这个值,使其始终为你UI显示的宽度. 这个属性经常被用来判断当前是否需要显示多屏布局,因为哪怕用户当前正在使用平板,你也可能不希望用户在平板竖屏时显示多个屏幕的布局样式.这时,你就可以使用这个限定符来标明你布局需要的最小宽度
屏幕可用高度hdp Examples: h720dp h1024dp标明资源使用时需要的最小高度.当屏幕发生旋转时,系统会自动选择当前大的一方作为高度值

.9 图或者 SVG 图实现缩放适配

.9 图为 Android 支持的一种可以自由伸缩的图片,可参考官网描述。这里提供一个制作 .9 的链接
在这里插入图片描述

SVG (多密度矢量图形) 在 Android 4.4 开始支持。可参考官网描述
AndroidStudio 为我们提供了许多默认的 SVG 图,可以在 drawable 中 新建一个 Vector Asset 来创建一个新的 SVG 资源
创建SVG

非常规方案

自定义View-像素适配

以一个特定尺寸(px)作为标准值,在 View 加载过程中,根据当前设备的实际像素尺寸和标准尺寸计算出相应的比例值,在作用于控件上。

可以先预览一下效果,以 720x1280 为基准,宽高比都占屏幕的一半。
在这里插入图片描述

具体实现如下
像素适配工具类:

/**
 * Author silence.
 * Time:2019-08-28.
 * Desc:像素适配工具
 */
public final class PixelUtils {

    //设计稿标准尺寸
    private static final float STANDARD_WIDTH = 720;
    private static final float STANDARD_HEIGHT = 1280;

    private int mDisplayWidth;
    private int mDisplayHeight;

    private PixelUtils(Context context){
        if (mDisplayWidth == 0 || mDisplayHeight == 0){
            WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            if (windowManager != null){
                DisplayMetrics displayMetrics = new DisplayMetrics();
                windowManager.getDefaultDisplay().getMetrics(displayMetrics);
                if (displayMetrics.widthPixels > displayMetrics.heightPixels){//横屏
                    mDisplayWidth = displayMetrics.heightPixels;
                    mDisplayHeight = displayMetrics.widthPixels;
                } else {//竖屏
                    mDisplayWidth = displayMetrics.widthPixels;
                    mDisplayHeight = displayMetrics.heightPixels - getStatusBarHeight(context);
                }
            }
        }
    }

    private int getStatusBarHeight(Context context){
        int resID = context.getResources().getIdentifier("status_bar_height","dimen","android");
        if (resID > 0){
            return context.getResources().getDimensionPixelSize(resID);
        }
        return 0;
    }

    public static float getHorizontalScale(Context context){
        return getInstance(context).mDisplayWidth / STANDARD_WIDTH;
    }
    public static float getVerticalScale(Context context){
        return getInstance(context).mDisplayHeight / STANDARD_HEIGHT;
    }

    private static PixelUtils utils;
    private static PixelUtils getInstance(Context context){
        if (utils == null){
            utils = new PixelUtils(context);
        }
        return utils;
    }
}

自定义适配布局:

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RelativeLayout;

/**
* Author silence.
* Time:2019-08-28.
* Desc:
*/
public class ScreenAdapterRelativeLayout extends RelativeLayout {

   private boolean hasAdjust;//防止多次调整

   public ScreenAdapterRelativeLayout(Context context) {
       super(context);
   }

   public ScreenAdapterRelativeLayout(Context context, AttributeSet attrs) {
       super(context, attrs);
   }

   public ScreenAdapterRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
       super(context, attrs, defStyleAttr);
   }

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       if (!hasAdjust) {
           float scaleX = PixelUtils.getHorizontalScale(getContext());
           float scaleY = PixelUtils.getVerticalScale(getContext());
           int childCount = getChildCount();
           for (int i = 0; i < childCount; i++) {
               View child = getChildAt(i);
               RelativeLayout.LayoutParams params = (LayoutParams) child.getLayoutParams();
               params.width = (int) (params.width * scaleX);
               params.height = (int) (params.height * scaleY);

               params.leftMargin = (int) (params.leftMargin * scaleX);
               params.rightMargin = (int) (params.rightMargin * scaleX);
               params.topMargin = (int) (params.topMargin * scaleY);
               params.bottomMargin = (int) (params.bottomMargin * scaleY);
           }
           hasAdjust = true;
       }

       super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   }
}

相应的布局文件:

 <com.wuba.demo.screenadapter.ScreenAdapterRelativeLayout
  	 xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <ImageView
          android:layout_width="360px"
          android:layout_height="640px"
          android:contentDescription="@string/app_name"
          android:background="@color/colorPrimary"/>
  </com.wuba.demo.screenadapter.ScreenAdapterRelativeLayout>

像素密度

根据特定的标准尺寸(dp)和系统尺寸计算相应的比例,然后修改当前界面的 Density 、 scaleDensity 、 densityDpi 像素密度

可以先预览一下效果,以 320dp 为基准,宽高比都占屏幕的一半。

AndroidStudio 中预览的效果
在这里插入图片描述

在手机上运行效果:

在这里插入图片描述

具体实现如下

import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks;
import android.content.res.Configuration;
import android.util.DisplayMetrics;

/**
 * Author silence.
 * Time:2019-08-28.
 * Desc:像素密度适配工具
 */
public final class DensityUtils {

    private static final float WIDTH = 320;//基准尺寸(dp)

    private static  float appDensity;//屏幕密度
    private static  float appScaleDensity;//字体缩放比例,默认为 appDensity

    public static void setDensity(final Application application, Activity activity){
        DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
        //获取当前 app 的显示信息
        if (appDensity == 0){
            appDensity = displayMetrics.density;
            appScaleDensity = displayMetrics.scaledDensity;
        }
        application.registerComponentCallbacks(new ComponentCallbacks() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                //字体发生变化时,更新 appScaleDensity
                if (newConfig != null && newConfig.fontScale > 0){
                    appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
                }
            }

            @Override
            public void onLowMemory() {

            }
        });
        //计算目标值
        float targetDensity = displayMetrics.widthPixels / WIDTH;// eg: 1080 / 360 = 3
        float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
        int targetDensityDpi = (int) (targetDensity * 160);
        //设置目标值
        displayMetrics = activity.getResources().getDisplayMetrics();
        displayMetrics.density = targetDensity;
        displayMetrics.scaledDensity = targetScaleDensity;
        displayMetrics.densityDpi = targetDensityDpi;
    }

}

百分比布局

以父容器尺寸为标准,在 View 加载过程中,根据当前父容器的实际尺寸计算目标尺寸,在作用于 View 上。谷歌推出过一个 百分比布局,我们此处简略实现下,下面是预览效果。
在这里插入图片描述

具体代码实现,首先在 values 中定义相关属性

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="PercentLayout">
        <attr name="widthPercent" format="float"/>
        <attr name="heightPercent" format="float"/>
    </declare-styleable>

</resources>

在自定义 百分比布局 PercentLayout

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

import com.wuba.demo.R;

/**
 * Author silence.
 * Time:2019-08-28.
 * Desc:
 */
public class PercentLayout extends RelativeLayout {
    public PercentLayout(Context context) {
        super(context);
    }

    public PercentLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PercentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            ViewGroup.LayoutParams params = child.getLayoutParams();
            if (checkLayoutParams(params)){
                LayoutParams lp = (LayoutParams) params;
                float widthPercent = lp.widthPercent;
                float heightPercent = lp.heightPercent;

                if (widthPercent > 0){
                    params.width = (int) (widthSize * widthPercent);
                }
                if (heightPercent > 0){
                    params.height = (int) (heightSize * heightPercent);
                }
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public RelativeLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(),attrs);
    }

    public static class LayoutParams extends RelativeLayout.LayoutParams{

        private float widthPercent;
        private float heightPercent;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            //解析我们自定义的属性
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.PercentLayout);
            widthPercent = a.getFloat(R.styleable.PercentLayout_widthPercent,0);
            heightPercent = a.getFloat(R.styleable.PercentLayout_heightPercent,0);
            a.recycle();
        }
    }
}

最后就是在 xml 中使用了

<com.wuba.demo.screenadapter.PercentLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/top"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:widthPercent="0.5"
        app:heightPercent="0.5"
        android:contentDescription="@string/app_name"
        android:background="@color/colorPrimary"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/top"
        app:widthPercent="0.5"
        app:heightPercent="0.5"
        android:layout_below="@id/top"
        android:contentDescription="@string/app_name"
        android:background="@color/colorPrimary"/>

</com.wuba.demo.screenadapter.PercentLayout>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值