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


哭訴:理論上一個app遇到尺寸差異這么大的就要重新設計一套UI分別使用了,無奈時間不允許,只能這樣將就。
解決方法:
- 鴻神 的 AndroidAutoLayout,這個現(xiàn)在已經(jīng)沒那么多人用了
- 今日頭條的適配方案,正火。
- 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還是比較能接受的
但是放到平板上一看,慘不忍睹,看看效果圖:

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;
}
}
適配完之后的效果:

這個效果和上面雖然也不完美,但是也還可以接受是不是???,反正比沒適配之前的好多啦,而且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"
/>
例如這個布局:

但是如果是比較復雜的布局,拖拽也挺麻煩的,所以在復雜的布局我用的是在代碼里動態(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í)生,撇。