布局适配
- 避免写死空间尺寸,使用wrap_content,match_parent
- LinearLayout xxx:layout_weight=“0.5”
- RelativeLayout xxx:layout_centerInParent=“true”
- ContraintLayout xxx:layout_constraintLeft_toLeftOf=“parent”
- Percent-support-lib xxx:layout_widthPercent=“30%”
图片资源适配
- .9图或者SVG图实现缩放
- 备用位图匹配不同分辨率
用户流程适配
- 根据业务逻辑执行不同的跳转逻辑
- 根据别名展示不同的界面
限定符的适配
- 分辨率适配 drawable-hdpi,drawable-xdpi …
- 尺寸限定符 layout-small,layout-large …
- 最小宽度限定符 values-sw360dp,values-sw384dp …
- 屏幕方向限定符 layout-land,layout-port
缺点: - 增加APK大小,适配机型越多的话,需要的XML也就越多
- 适配所有机型的分辨率,xml文件加起来有近3M
- 不能适配奇葩机型,如手表
刘海屏适配
- Android 9.0 官方适配
- 9.0以前华为、Oppo、Vivo、小米等各家自己的刘海屏方案
屏幕适配-自定义View
以一个特定宽度尺寸的设备为参考,在View的加载过程,根据当前设备的实际像素换算出目标像素,再作用在控件上。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
float scaleX = 100;//获取横向缩放比
float scaleY = 100;//获得竖向缩放比
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i); //重新设置子View的布局属性,再进行View的测量
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.width = (int)(lp.width * scaleX); //换算宽度目标值
lp.height = (int) (lp.height * scaleY); //换算高度目标值
lp.topMargin = (int) (lp.height * scaleY); //换算四周间距的目标值
//....
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
屏幕适配-百分比布局
测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取父容器的尺寸
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; 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;
float marginLeftPercent = lp.marginLeftPercent;
float marginRightPercent= lp.marginRightPercent;
float marginTopPercent= lp.marginTopPercent;
float marginBottomPercent = lp.marginBottomPercent;
if (widthPercent > 0){
params.width = (int) (widthSize * widthPercent);
}
//...
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
解析LayoutParams
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
public LayoutParams generateLayoutParams(AttributeSet attrs){
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends RelativeLayout.LayoutParams{
private float widthPercent;
private float heightPercent;
private float marginLeftPercent;
private float marginRightPercent;
private float marginTopPercent;
private float marginBottomPercent;
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);
marginLeftPercent = a.getFloat(R.styleable.PercentLayout_marginLeftPercent, 0);
marginRightPercent = a.getFloat(R.styleable.PercentLayout_marginRightPercent, 0);
marginTopPercent = a.getFloat(R.styleable.PercentLayout_marginTopPercent, 0);
marginBottomPercent = a.getFloat(R.styleable.PercentLayout_marginBottomPercent, 0);
a.recycle();
}
}
源码分析
-> setContentView
-> PhoneWindow.setContentView
-> mLayoutInflater.inflate
-> ViewGroup.generateLayoutParams
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
可以看到,LayoutParams是由View的父容器来生成的,通过new LayoutParams的方式。
屏幕适配-像素密度
修改density、scaleDensity、densityDpi,直接更改系统内部对于目标尺寸而言的像素密度。
public class Density {
private static final float WIDTH = 320;//参考设备的宽,单位是dp 320 / 2 = 160
private static float appDensity;//表示屏幕密度
private static float appScaleDensity; //字体缩放比例,默认appDensity
public static void setDensity(final Application application, Activity activity){
//获取当前app的屏幕显示信息
DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if (appDensity == 0){
//初始化赋值操作
appDensity = displayMetrics.density;
appScaleDensity = displayMetrics.scaledDensity;
//添加字体变化监听回调
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字体发生更改,重新对scaleDensity进行赋值
if (newConfig != null && newConfig.fontScale > 0){
appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
//计算目标值density, scaleDensity, densityDpi
float targetDensity = displayMetrics.widthPixels / WIDTH; // 1080 / 360 = 3.0
float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
int targetDensityDpi = (int) (targetDensity * 160);
//替换Activity的density, scaleDensity, densityDpi
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
dm.density = targetDensity;
dm.scaledDensity = targetScaleDensity;
dm.densityDpi = targetDensityDpi;
}
}
然后在Application中设置
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Density.setDensity(App.this, activity);
}
//...
});
或者在BaseActivity中设置
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Density.setDensity(getApplication(),this);
}
这样,就可以在XML中,根据设计稿中的WIDTH,直接按照设计稿的尺寸来编写布局了,并能很好地进行屏幕适配。
<TextView
android:id="@+id/text"
android:layout_width="160dp"
android:layout_height="160dp"
android:text="Hello World!"
android:background="@color/colorAccent"/>
屏幕适配-动态代码适配
直接在java代码中进行适配
public class UIUtils {
//工具类
private static UIUtils instance ;
//ios 标准
public static final float STANDARD_WIDTH=1080f;
public static final float STANDARD_HEIGHT=1920f;
//实际设备信息 赋值 他是不知道横竖 1080 1920
//width 1920 高度 1080
public static float displayMetricsWidth;
public static float displayMetricsHeight;
public static float systemBarHeight;
//applicaiton
public static UIUtils getInstance(Context context){
if(instance==null){
instance=new UIUtils(context);
}
return instance;
}
public static UIUtils notityInstance(Context context){
instance=new UIUtils(context);
return instance;
}
//activity
public static UIUtils getInstance() {
if (instance == null) {
throw new RuntimeException("UiUtil应该先调用含有构造方法进行初始化");
}
return instance;
}
private UIUtils(Context context) {
//计算缩放系数
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics=new DisplayMetrics();
if(displayMetricsWidth==0.0f || displayMetricsHeight==0.0f){
//在这里得到设备的真实值
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
//状态栏高度
systemBarHeight=getSystemBarHeight(context);
//横屏
if(displayMetrics.widthPixels>displayMetrics.heightPixels){
this.displayMetricsWidth=(float)(displayMetrics.heightPixels);
this.displayMetricsHeight=(float)(displayMetrics.widthPixels-systemBarHeight);
}else {
//竖屏
this.displayMetricsWidth=(float)(displayMetrics.widthPixels);
this.displayMetricsHeight=(float)(displayMetrics.heightPixels-systemBarHeight);
}
}
}
public float getHorizontalScaleValue(){
return ((float)(displayMetricsWidth)) / STANDARD_WIDTH;
}
public float getVerticalScaleValue(){
return ((float)(displayMetricsHeight))/(STANDARD_HEIGHT-systemBarHeight);
}
private int getSystemBarHeight(Context context){
return getValue(context,"com.android.internal.R$dimen","system_bar_height",48);
}
public int getWidth(int width) {
return Math.round((float)width * this.displayMetricsWidth / STANDARD_WIDTH);
}
public int getHeight(int height) {
return Math.round((float)height * this.displayMetricsHeight / (STANDARD_HEIGHT-systemBarHeight));
}
private int getValue(Context context, String dimeClass, String system_bar_height, int defaultValue) {
//com.android.internal.R$dimen system_bar_height 状态栏的高度
try {
Class<?> clz=Class.forName(dimeClass);
Object object = clz.newInstance();
Field field=clz.getField(system_bar_height);
int id=Integer.parseInt(field.get(object).toString());
return context.getResources().getDimensionPixelSize(id);
} catch (Exception e) {
e.printStackTrace();
}
return defaultValue;
}
}
public class ViewCalculateUtil {
/**
* 设置LinearLayout中 view的高度宽度
*
* @param view
* @param width
* @param height
*/
public static void setViewLinearLayoutParam(View view, int width, int height, int topMargin, int bottomMargin, int lefMargin,
int rightMargin) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
if (width != RelativeLayout.LayoutParams.MATCH_PARENT && width != RelativeLayout.LayoutParams.WRAP_CONTENT && width != RelativeLayout.LayoutParams.FILL_PARENT) {
layoutParams.width = UIUtils.getInstance().getWidth(width);
} else {
layoutParams.width = width;
}
if (height != RelativeLayout.LayoutParams.MATCH_PARENT && height != RelativeLayout.LayoutParams.WRAP_CONTENT && height != RelativeLayout.LayoutParams.FILL_PARENT) {
layoutParams.height = UIUtils.getInstance().getHeight(height);
} else {
layoutParams.height = height;
}
layoutParams.topMargin = UIUtils.getInstance().getHeight(topMargin);
layoutParams.bottomMargin = UIUtils.getInstance().getHeight(bottomMargin);
layoutParams.leftMargin = UIUtils.getInstance().getWidth(lefMargin);
layoutParams.rightMargin = UIUtils.getInstance().getWidth(rightMargin);
view.setLayoutParams(layoutParams);
}
}
然后进行使用即可
public class MainActivity extends AppCompatActivity {
private TextView tvText3;
private TextView tvText4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UIUtils.getInstance(this.getApplicationContext());
setContentView(R.layout.activity_main);
tvText3 = findViewById(R.id.tvText3);
tvText4 = findViewById(R.id.tvText4);
ViewCalculateUtil.setViewLinearLayoutParam(tvText3, 540, 100, 0, 0, 0, 0);
ViewCalculateUtil.setViewLinearLayoutParam(tvText4, 1080, 100, 0, 0, 0, 0);
ViewCalculateUtil.setTextSize(tvText3,30);
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
UIUtils.notityInstance(this);
}
}