屏幕尺寸相差较大的适配

1.需求描述,需要适配市面正常尺寸的手机,一般是5.5左右,当然新出的刘海屏,曲面屏,全面屏等等很多都大于5.5的;这中类型的目前还没有测试机,所以没看到过效果,除了5.5尺寸,还有7.5工业板常见尺寸屏幕,所谓的9.5超大屏商务板,可能你们没有啥概念,上图:

屏幕尺寸相差较大的适配_第1张图片
5.5vs7.5vs9.5.png

屏幕尺寸相差较大的适配_第2张图片
5.5vs9.5.png

哭诉:理论上一个app遇到尺寸差异这么大的就要重新设计一套UI分别使用了,无奈时间不允许,只能这样将就。

解决方法:

  1. 鸿神 的 AndroidAutoLayout,这个现在已经没那么多人用了
  2. 今日头条的适配方案,正火。
  3. SmallestWidth 限定符适配方案,正火。
    最后我采用的是[JessYan]的基于今日头条适配方案的AndroidAutoSize。这个代码侵入程度接近于0啊,惊叹,具体的使用与实现看作者的原著即可,地址是:https://www.jianshu.com/p/4aa23d69d481
    用法上面的链接都有,但是他的框架是再AndroidManifest设置一个固定的比例来实现的,比如5.0尺寸左右的用这个比例375:667,这个比例就是苹果的750:1334,4.7英寸的苹果的6,6s,7,8的都是这个比例,这个比率放到android的5.0-5.5的也都合适,而且app的设计图只给了一份,就是基于这个尺寸的,所以一开始配置是这样的:

build.gradle的配置

dependencies {
    ...
    // 基于今日头条适配方案的扩展框架:https://www.jianshu.com/p/4aa23d69d481
    implementation 'me.jessyan:autosize:1.0.5'
    ...
}

AndroidManifest.xml的配置

        
        
        

这个设置在5.0-5.5还是比较能接受的
但是放到平板上一看,惨不忍睹,看看效果图:

屏幕尺寸相差较大的适配_第3张图片
左边是实际开发图右边是效果图.png

SO,5.5,7.5和9.5由于尺寸差别太大是不能用一个比例来通配的,但是在AndroidManifest.xml里面并不能通过获取手机尺寸来设置,但是作者已经考虑到类似的需求了,可以通过接口来动态设置比例,实现CustomAdapt这个接口就可以在getSizeInDp() {}里面设置了, 所以我最后的做法是:尺寸差别太大的手机和平板动态获取尺寸,根据尺寸设置宽度适配或者高度适配的数值。
代码如下:


package com.dasudian.dsd.utils.app;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

import com.dasudian.dsd.DsdApplication;

import java.math.BigDecimal;

public class ScreenUtils {
    private ScreenUtils()
    {
        /* cannot be instantiated */
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    /**
     * 获得屏幕宽度
     *
     * @return
     */
    public static int getScreenWidth()
    {
        WindowManager wm = (WindowManager) DsdApplication.getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }

    /**
     * 获得屏幕高度
     *
     * @return
     */
    public static int getScreenHeight()
    {
        WindowManager wm = (WindowManager) DsdApplication.getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.heightPixels;
    }

    /**
     * 获得状态栏的高度
     *
     * @param context
     * @return
     */
    public static int getStatusHeight(Context context)
    {
        int statusHeight = -1;
        try
        {
            Class clazz = Class.forName("com.android.internal.R$dimen.xml");
            Object object = clazz.newInstance();
            int height = Integer.parseInt(clazz.getField("status_bar_height")
                    .get(object).toString());
            statusHeight = context.getApplicationContext().getResources().getDimensionPixelSize(height);
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        return statusHeight;
    }

    /**
     * 获取当前屏幕截图,包含状态栏
     *
     * @param activity
     * @return
     */
    public static Bitmap snapShotWithStatusBar(Activity activity)
    {
        View view = activity.getWindow().getDecorView();
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap bmp = view.getDrawingCache();
        int width = getScreenWidth();
        int height = getScreenHeight();
        Bitmap bp = null;
        bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
        view.destroyDrawingCache();
        return bp;
    }

    /**
     * 获取当前屏幕截图,不包含状态栏
     *
     * @param activity
     * @return
     */
    public static Bitmap snapShotWithoutStatusBar(Activity activity)
    {
        View view = activity.getWindow().getDecorView();
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap bmp = view.getDrawingCache();
        Rect frame = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
        int statusBarHeight = frame.top;

        int width = getScreenWidth();
        int height = getScreenHeight();
        Bitmap bp = null;
        bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
                - statusBarHeight);
        view.destroyDrawingCache();
        return bp;
    }

    /**
     * 获取当前屏幕的尺寸大小
     * @return
     */
    public static double getPingMuSize() {
        try {
            WindowManager wm = (WindowManager) DsdApplication.getContext().getSystemService(Context.WINDOW_SERVICE);
            Display display = wm.getDefaultDisplay();
            DisplayMetrics dm = new DisplayMetrics();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                display.getRealMetrics(dm);
            }else {
                display.getMetrics(dm);
            }
            double x = Math.pow(dm.widthPixels / dm.xdpi, 2);
            double y = Math.pow(dm.heightPixels / dm.ydpi, 2);
            // 屏幕尺寸
            BigDecimal decimal = new BigDecimal(Math.sqrt(x + y));
            decimal = decimal.setScale(1,BigDecimal.ROUND_UP);
            double mScreenInches = decimal.doubleValue();
            return mScreenInches;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 根据手机尺寸设置 适配框架 对应的宽,这里默认是拿宽,之前是在AndroidManifest的里面配置的,发现平板和手机因为尺寸差别太大无法只设置一个宽.
     * Nexus 5x Api28--->当前手机尺寸为:5.3
     * 其它信息:DisplayMetrics{density=2.625, width=1080, height=1794, scaledDensity=2.625, xdpi=420.0, ydpi=420.0}
     * 375:667
     *
     * Raindi ITAB-01 Api22--->当前手机尺寸为:7.5
     * 其它信息:DisplayMetrics{density=1.0, width=600, height=976, scaledDensity=1.0, xdpi=160.0, ydpi=160.0}
     * 482  820
     *
     * KTE X20 Api26--->9.5
     * 其它信息:DisplayMetrics{density=2.0, width=1600, height=2464, scaledDensity=2.0, xdpi=320.0, ydpi=320.0}
     *
     * 580  960
     * @return 返回不同尺寸终端适应的宽,注意,你们自己需要匹配的平板的数值自己去尝试,这里只是参考。
     */
    public static int getAutoSizeWidth() {
        // 580是9.5寸的商务数据终端的适配值.
        int width = 580;
        try {
            double size = getPingMuSize();
            if(size < 7) {
                width = 375; // height = 667
            } else if (size >= 7 && size < 8){
                width = 482; // height = 820
            } else {
                width = 580; // height = 960
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return width;
        }
    }

    /**
     * 获取当前屏幕的尺寸大小
     * @return
     */
    public static DisplayMetrics getMetrics() {
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager manager = (WindowManager) DsdApplication.getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        manager.getDefaultDisplay().getMetrics(metrics);
        return metrics;
    }
}
一.上面的实现方法放在BaseActivity里面调用,建议你的的APP所有的类都要继承于BaseActivity/MvpBaseActivity,如果不继承于基类,则自己手动实现implements CustomAdapt接口也可以,然后重写他的方法,如果重写了方法设置了对应的宽或者高得数值就能顺利适配啦,太方便了8,代码如下:
    package com.dasudian.dsd.mvp.base;

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.dasudian.dsd.R;
import com.dasudian.dsd.utils.app.IntentUtil;
import com.dasudian.dsd.utils.app.ScreenUtils;
import com.dasudian.dsd.utils.stack.StackManager;
import com.dasudian.dsd.widget.NavigationBar;

import me.jessyan.autosize.internal.CustomAdapt;

public abstract class BaseActivity> extends AppCompatActivity implements CustomAdapt {
    protected T mPresenter;
    protected NavigationBar mNavigationBar;
    private StackManager mStackManager;

    private AlertDialog progressDialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//竖屏
        // 不是所有的界面都需要实现mvp , so允许为空
        if (createPresenter() != null) {
            mPresenter = createPresenter();
            mPresenter.attachView((V) this);

        }
    }

    // createPresenter
    protected abstract T createPresenter();

    //用于引入布局文件
    abstract protected int provideContentViewId();


    /**
     * 规定按照宽度适配
     * @return
     */
    @Override
    public boolean isBaseOnWidth() {
        return true;
    }

    /**
     * 设置适配的宽度或者高度
     * @return
     */
    @Override
    public float getSizeInDp() {
        return ScreenUtils.getAutoSizeWidth();
    }

    

    protected void openActivity(Class cls) {
        Intent intent = new Intent(this, cls);
        startActivity(intent);
    }
    
    public void openActivityForResult(Class cls, int requestCode) {

        openActivity(cls, null, requestCode);
    }


    public void openActivity(Class cls, Bundle bundle, int requestCode) {

        Intent intent = new Intent(this, cls);
        if (bundle != null) {
            intent.putExtras(bundle);
        }
        if (requestCode == 0) {
            IntentUtil.startPreviewActivity(this, intent, 0);
        } else {
            IntentUtil.startPreviewActivity(this, intent, requestCode);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.detachView();
        }
    }

    public TextView showProgressDialog() {
        return showProgressDialog(false);
    }
    
    public StackManager getStackManager() {
        return mStackManager.getStackManager();
    }
    
    public void closeProgressDialog() {
        try {
            if (progressDialog != null) {
                progressDialog.dismiss();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public TextView showProgressDialog(boolean isCanCancel) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setCancelable(false);
        View view = View.inflate(this, R.layout.dialog_loading, null);
        builder.setView(view);
        ProgressBar pb_loading = (ProgressBar) view.findViewById(R.id.pb_loading);
        TextView tv_hint = (TextView) view.findViewById(R.id.tv_loading_hint);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            pb_loading.setIndeterminateTintList(ContextCompat.getColorStateList(this, R.color.colorPrimaryDark));
        }
        tv_hint.setText("正在加载中...");
        progressDialog = builder.create();
        progressDialog.show();
        return tv_hint;
    }
}

适配完之后的效果:


屏幕尺寸相差较大的适配_第4张图片
右边是适配后的实际开发图左边边是效果图.png

这个效果和上面虽然也不完美,但是也还可以接受是不是???,反正比没适配之前的好多啦,而且5.0尺寸和9.5尺寸差距了一倍,这个程度我是可以接受了哈哈O_O

二:第三方库里面的类也要适配,例如某些图片选择插件,打开相册选择那一块的Activity也不是我们写的,如果没适配会非常不协调,类似这种,要在Application里面提前适配,代码如下:
/**
     * 适配手机平板,由于框架适配的取值有多种情况,一是早于Application就从AndroidManifest.xml的取值进行初始化,第二种是初始化Activity的时候通过回调接口重新赋值(本项目就是在基类进行的初始化);第三种是下面的提前声明第三方库的某个Activity需要适配的值
     * 本项目使用第二中和第三种方法一起进行适配平板和手机.
     * {@link ScreenUtils#getAutoSizeWidth()}
     * 注意:如果用了这个框架的代码进行适配,必须在AndroidManifest.xml的中设置初始值.
     */
    private void initAdaptivePhoneAndPad() {
        try {
            // 获取尺寸
            double size = ScreenUtils.getPingMuSize();
            LogUtil.e("手机尺寸为:" + size);
            LogUtil.e("手机其它信息:" + ScreenUtils.getMetrics().toString());
            // 适配第三方图片库的MatisseActivity类,不然这个会很小,其它的第三方类也需要在这里申明,具体参考:https://github.com/JessYanCoding/AndroidAutoSize/blob/master/demo/src/main/java/me/jessyan/autosize/demo/BaseApplication.java#L94
            AutoSizeConfig.getInstance().getExternalAdaptManager().addExternalAdaptInfoOfActivity(MatisseActivity.class, new ExternalAdaptInfo(true, ScreenUtils.getAutoSizeWidth()));
            AutoSizeConfig.getInstance().getExternalAdaptManager().addExternalAdaptInfoOfActivity(ImageCropActivity.class, new ExternalAdaptInfo(true, ScreenUtils.getAutoSizeWidth()));
            AutoSizeConfig.getInstance().getExternalAdaptManager().addExternalAdaptInfoOfActivity(NotificationActivity.class, new ExternalAdaptInfo(true, ScreenUtils.getAutoSizeWidth()));
            AutoSizeConfig.getInstance().getExternalAdaptManager().addExternalAdaptInfoOfActivity(MessageActivity.class, new ExternalAdaptInfo(true, ScreenUtils.getAutoSizeWidth()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

到此处截至,AndroidAutoSize框架用来适配的部分就完成了。

三:由于尺寸相差这么大,还会有其它适配问题,例如设计师设计了一个图标在距顶部40%的地方,设计图上标记距离高度是100dp,如果我们设死marginTop = 100dp,那在9.5尺寸的平板上差距实际只是22%,**看如下数据:

5.3尺寸的手机:DisplayMetrics{density=2.625, width=1080, height=1794, scaledDensity=2.625, xdpi=420.0, ydpi=420.0}
1794px 2.625dpi 683.428571dp
设计图274dp / 683.428571 = 0.4011713
9.5尺寸的手机:DisplayMetrics{density=2.0, width=1600, height=2464, scaledDensity=2.0, xdpi=320.0, ydpi=320.0}
2464px 2.0dpi 1232dp
274dp / 1232 = 0.2224026

所以在视觉效果上差距会很大,这种情况有两种解决方法,一种是使用android.support.constraint.ConstraintLayout布局,动态拖拽,设置距离顶部的bias值,例如下面的代码就是让imageView_icon距离顶部0.1的间距。


例如这个布局:


屏幕尺寸相差较大的适配_第5张图片
ConstraintLayout.png

但是如果是比较复杂的布局,拖拽也挺麻烦的,所以在复杂的布局我用的是在代码里动态获取总宽高,然后获取10%的高度,再转成对应的dp值,然后设置。

动态设置margin:

    public static void setMargin(View view, int left, int top, int right, int bottom) {
        int scaledLeft = scaleValue(view.getContext(), left);
        int scaledTop = scaleValue(view.getContext(), top);
        int scaledRight = scaleValue(view.getContext(), right);
        int scaledBottom = scaleValue(view.getContext(), bottom);

        if ((view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams)) {
            ViewGroup.MarginLayoutParams mMarginLayoutParams = (ViewGroup.MarginLayoutParams) view
                    .getLayoutParams();
            if (mMarginLayoutParams != null) {
                if (left != -2147483648) {
                    mMarginLayoutParams.leftMargin = scaledLeft;
                }
                if (right != -2147483648) {
                    mMarginLayoutParams.rightMargin = scaledRight;
                }
                if (top != -2147483648) {
                    mMarginLayoutParams.topMargin = scaledTop;
                }
                if (bottom != -2147483648) {
                    mMarginLayoutParams.bottomMargin = scaledBottom;
                }
                view.setLayoutParams(mMarginLayoutParams);
            }
        }
    }
四,例如设计师设计了图标占屏幕宽度的一半,的地方,设计图上标记宽高是340dp,如果我们设死width=height= 340dp,那原本占宽度50%的图片宽高在9.5尺寸的平板上只是占了22.78%,看如下数据:

5.3尺寸的手机:DisplayMetrics{density=2.625, width=1080, height=1794, scaledDensity=2.625, xdpi=420.0, ydpi=420.0}
1794px 2.625dpi 683.428571dp
设计图340dp / 683.428571 ≈ 0.5,
9.5尺寸的手机:DisplayMetrics{density=2.0, width=1600, height=2464, scaledDensity=2.0, xdpi=320.0, ydpi=320.0}
2464px 2.0dpi 1232dp
340dp/ 1232 = 0.2759

,所注意这种情况下我们还是要按照百分百来设置这个图片的宽高。
PercentImageView.java

package com.dasudian.dsd.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.util.AttributeSet;

import com.dasudian.dsd.R;
import com.dasudian.dsd.utils.app.ScreenUtils;

/**
 * 可设置百分百高度的图片
 */
public class PercentImageView extends android.support.v7.widget.AppCompatImageView {
    private float widthPer;
    private float heightPer;

    public PercentImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PercentImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.percent_imageview);
        widthPer = ta.getFloat(R.styleable.percent_imageview_widthPer, 0);
        heightPer = ta.getFloat(R.styleable.percent_imageview_heightPer, 0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec));

        int widthSize  = (int) (ScreenUtils.getScreenWidth() * widthPer);
        int heightSize = (int) (ScreenUtils.getScreenHeight() * heightPer);
        // 用户设置[0,1]区间以外的值都无效,都是采用ImageView默认的设置。
        if(widthPer > 0 && widthPer < 1) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        }
        if(heightPer > 0 && heightPer  < 1) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

attrs.xml中配置自定义参数:



    
    
        
        
        
        
    


xml中使用百分百高度的图片





    

    
  

项目用到的适配方面大体就这些了,还有LinearLayout等比,多套设计图那些就不说了,反正适配要见鸡行事,混着用才能完美,大家执生,撇。

你可能感兴趣的:(屏幕尺寸相差较大的适配)