美文网首页
屏幕尺寸相差较大的适配

屏幕尺寸相差较大的适配

作者: hai_phon | 来源:发表于2019-01-03 12:42 被阅读0次

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

    5.5vs7.5vs9.5.png 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的配置

            <meta-data
                android:name="design_width_in_dp"
                android:value="375" />
            <meta-data
                android:name="design_height_in_dp"
                android:value="667" />
            <!-- 适配设置结束 -->
    

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

    左边是实际开发图右边是效果图.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的<meta-data>里面配置的,发现平板和手机因为尺寸差别太大无法只设置一个宽.
         * 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<V, T extends BasePresenter<V>> 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;
        }
    }
    

    适配完之后的效果:


    右边是适配后的实际开发图左边边是效果图.png

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

    二:第三方库里面的类也要适配,例如某些图片选择插件,打开相册选择那一块的Activity也不是我们写的,如果没适配会非常不协调,类似这种,要在Application里面提前适配,代码如下:
    /**
         * 适配手机平板,由于框架适配的取值有多种情况,一是早于Application就从AndroidManifest.xml的<meta-data>取值进行初始化,第二种是初始化Activity的时候通过回调接口重新赋值(本项目就是在基类进行的初始化);第三种是下面的提前声明第三方库的某个Activity需要适配的值
         * 本项目使用第二中和第三种方法一起进行适配平板和手机.
         * {@link ScreenUtils#getAutoSizeWidth()}
         * 注意:如果用了这个框架的代码进行适配,必须在AndroidManifest.xml的<meta-data>中设置初始值.
         */
        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的间距。

    <ImageView
            android:id="@+id/imageView_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:src="@mipmap/dsd_logo"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintVertical_bias="0.1"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            />
    

    例如这个布局:


    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 version="1.0" encoding="utf-8"?>
    <resources>
        
        <declare-styleable name="percent_imageview">
            <!-- 宽度占屏幕宽度的比例 -->
            <attr name="widthPer" format="float" />
            <!-- 高度占屏幕高度的比例 -->
            <attr name="heightPer" format="float" />
        </declare-styleable>
    
    </resources>
    

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

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:hhf="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    
    
        <com.dasudian.dsd.widget.PercentImageView
            android:id="@+id/imageview_detail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/item_margin_vertical"
            android:scaleType="fitXY"
            hhf:heightPer="0.25"
            android:src="@mipmap/dsd_logo"
            />
    
        <TextView
            android:id="@+id/tv_tip"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/item_margin_vertical"
            android:visibility="visible"
            android:textColor="@color/text_color_black2"
            android:text="我们的数据分析师生了个“孩子”,名字叫“小D”。小D天生拥有一种超能力:智能识别物体" />
    </LinearLayout>  
    

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

    相关文章

      网友评论

          本文标题:屏幕尺寸相差较大的适配

          本文链接:https://www.haomeiwen.com/subject/uuftrqtx.html