屏幕尺寸相差較大的適配

1.需求描述,需要適配市面正常尺寸的手機,一般是5.5左右,當然新出的劉海屏,曲面屏,全面屏等等很多都大于5.5的;這中類型的目前還沒有測試機,所以沒看到過效果,除了5.5尺寸,還有7.5工業(yè)板常見尺寸屏幕,所謂的9.5超大屏商務板,可能你們沒有啥概念,上圖:

5.5vs7.5vs9.5.png

5.5vs9.5.png

哭訴:理論上一個app遇到尺寸差異這么大的就要重新設計一套UI分別使用了,無奈時間不允許,只能這樣將就。

解決方法:

  1. 鴻神 的 AndroidAutoLayout,這個現(xiàn)在已經(jīng)沒那么多人用了
  2. 今日頭條的適配方案,正火。
  3. SmallestWidth 限定符適配方案,正火。
    最后我采用的是[JessYan]的基于今日頭條適配方案的AndroidAutoSize。這個代碼侵入程度接近于0啊,驚嘆,具體的使用與實現(xiàn)看作者的原著即可,簡書地址是:http://www.itdecent.cn/p/4aa23d69d481
    用法上面的鏈接都有,但是他的框架是再AndroidManifest設置一個固定的比例來實現(xiàn)的,比如5.0尺寸左右的用這個比例375:667,這個比例就是蘋果的750:1334,4.7英寸的蘋果的6,6s,7,8的都是這個比例,這個比率放到android的5.0-5.5的也都合適,而且app的設計圖只給了一份,就是基于這個尺寸的,所以一開始配置是這樣的:

build.gradle的配置

dependencies {
    ...
    // 基于今日頭條適配方案的擴展框架:http://www.itdecent.cn/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還是比較能接受的
但是放到平板上一看,慘不忍睹,看看效果圖:

左邊是實際開發(fā)圖右邊是效果圖.png

SO,5.5,7.5和9.5由于尺寸差別太大是不能用一個比例來通配的,但是在AndroidManifest.xml里面并不能通過獲取手機尺寸來設置,但是作者已經(jīng)考慮到類似的需求了,可以通過接口來動態(tài)設置比例,實現(xiàn)CustomAdapt這個接口就可以在getSizeInDp() {}里面設置了,所以我最后的做法是:尺寸差別太大的手機和平板動態(tài)獲取尺寸,根據(jù)尺寸設置寬度適配或者高度適配的數(shù)值。
代碼如下:


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;
    }

    /**
     * 獲得狀態(tài)欄的高度
     *
     * @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;
    }

    /**
     * 獲取當前屏幕截圖,包含狀態(tài)欄
     *
     * @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;
    }

    /**
     * 獲取當前屏幕截圖,不包含狀態(tài)欄
     *
     * @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;
        }
    }

    /**
     * 根據(jù)手機尺寸設置 適配框架 對應的寬,這里默認是拿寬,之前是在AndroidManifest的<meta-data>里面配置的,發(fā)現(xiàn)平板和手機因為尺寸差別太大無法只設置一個寬.
     * 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 返回不同尺寸終端適應的寬,注意,你們自己需要匹配的平板的數(shù)值自己去嘗試,這里只是參考。
     */
    public static int getAutoSizeWidth() {
        // 580是9.5寸的商務數(shù)據(jù)終端的適配值.
        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;
    }
}
一.上面的實現(xiàn)方法放在BaseActivity里面調用,建議你的的APP所有的類都要繼承于BaseActivity/MvpBaseActivity,如果不繼承于基類,則自己手動實現(xiàn)implements CustomAdapt接口也可以,然后重寫他的方法,如果重寫了方法設置了對應的寬或者高得數(shù)值就能順利適配啦,太方便了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);//豎屏
        // 不是所有的界面都需要實現(xiàn)mvp , so允許為空
        if (createPresenter() != null) {
            mPresenter = createPresenter();
            mPresenter.attachView((V) this);

        }
    }

    // createPresenter
    protected abstract T createPresenter();

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


    /**
     * 規(guī)定按照寬度適配
     * @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;
    }
}

適配完之后的效果:


右邊是適配后的實際開發(fā)圖左邊邊是效果圖.png

這個效果和上面雖然也不完美,但是也還可以接受是不是???,反正比沒適配之前的好多啦,而且5.0尺寸和9.5尺寸差距了一倍,這個程度我是可以接受了哈哈O_O

二:第三方庫里面的類也要適配,例如某些圖片選擇插件,打開相冊選擇那一塊的Activity也不是我們寫的,如果沒適配會非常不協(xié)調,類似這種,要在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,如果我們設死m(xù)arginTop = 100dp,那在9.5尺寸的平板上差距實際只是22%,**看如下數(shù)據(jù):

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布局,動態(tài)拖拽,設置距離頂部的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

但是如果是比較復雜的布局,拖拽也挺麻煩的,所以在復雜的布局我用的是在代碼里動態(tài)獲取總寬高,然后獲取10%的高度,再轉成對應的dp值,然后設置。

動態(tài)設置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%,看如下數(shù)據(jù):

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]區(qū)間以外的值都無效,都是采用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中配置自定義參數(shù):

<?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="我們的數(shù)據(jù)分析師生了個“孩子”,名字叫“小D”。小D天生擁有一種超能力:智能識別物體" />
</LinearLayout>  

項目用到的適配方面大體就這些了,還有LinearLayout等比,多套設計圖那些就不說了,反正適配要見雞行事,混著用才能完美,大家執(zhí)生,撇。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容