AutoSize是基于今日头条的适配方案,但它有一些缺点,比如代码侵入性较强,在使用第三方的View框架时,可能会出现不兼容的情况。
我目前的sdk项目不能使用这样的框架,于是自己做了一个简单的工具类,也能够满足基本需求。
适配方案
以设计图的960 x 540 dp作为基准,在代码中获取屏幕宽高px,遇到需要适配的view时,调用autoSizeLayout()方法自动调整。
-
autoSizeLayout方法会遍历viewGroup下的所有根节点,获取当前view的内外边距的px
-
px除以屏幕密度得到当前view在布局文件中设置的dp值
-
使用该dp值根据横向或纵向来除以960或540,得出其在设计图中所占比例
-
比例乘以当前屏幕的宽高px,即为应该设置的新的px值
优点:
-
代码中只需封装一次,以后布局需要支持的话,都调用autoSizeLayout()方法进行处理即可
-
xml中的dp值可以完全以设计图为准,具体值会在代码中进行计算
-
集成该框架无需改动现有项目的代码
缺点:
- xml中的shape资源无法适配,需要手动在代码中处理
public class SimpleAutoSize {
/**
* 屏幕宽高px
*/
private int screenWidth = 0;
private int screenHeight = 0;
/**
* 设计图基准dp
*/
private int basisWidthDp = 0;
private int basisHeightDp = 0;
private float density = 0;
private onAutoSizeViewListener onAutoSizeViewListener;
/**
* 可以直接使用View的宽高px值,乘以该比例,得出相对于960*540屏幕的百分比宽高
* <p>
* 计算步骤:
* 1、布局中的16dp,跑到了1080*1920的屏幕上,成为了24px
* 2、先24除以屏幕密度1.5,得到布局文件中的dp值16
* 3、计算16占540的比重
* 4、结果乘以1080,得到32px,所以16dp在当前屏幕上,按照百分比计算应该是32px
*/
private float widthRatio = 0;
private float heightRatio = 0;
private float screenRatio = 0;
private static class SingletonHolder {
private static final SimpleAutoSize instance = new SimpleAutoSize();
}
public static SimpleAutoSize getInstance() {
return SingletonHolder.instance;
}
public interface onAutoSizeViewListener {
public void onAutoSize(View view, float screenRatio);
}
public void init(Context context, int basisWidthDp, int basisHeightDp) {
this.basisWidthDp = basisWidthDp;
this.basisHeightDp = basisHeightDp;
calc(context);
}
private void calc(Context context){
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point screenSize = new Point();
display.getSize(screenSize);
screenWidth = screenSize.x;
screenHeight = screenSize.y;
this.density = context.getResources().getDisplayMetrics().density;
widthRatio = (float) screenWidth / basisWidthDp / density; // 再除以屏幕密度,方便后续一步求出结果
heightRatio = (float) screenHeight / basisHeightDp / density;
screenRatio = (float) screenWidth / basisWidthDp;
}
public SimpleAutoSize.onAutoSizeViewListener getOnAutoSizeViewListener() {
return onAutoSizeViewListener;
}
public void setOnAutoSizeViewListener(SimpleAutoSize.onAutoSizeViewListener onAutoSizeViewListener) {
this.onAutoSizeViewListener = onAutoSizeViewListener;
}
public int getScreenWidth() {
return screenWidth;
}
public int getScreenHeight() {
return screenHeight;
}
public int getBasisWidthDp() {
return basisWidthDp;
}
public int getBasisHeightDp() {
return basisHeightDp;
}
public float getDensity() {
return density;
}
public float getWidthRatio() {
return widthRatio;
}
public float getHeightRatio() {
return heightRatio;
}
public float getScreenRatio() {
return screenRatio;
}
/**
* 计算自适应高度
* @param width 真实宽度px
* @return
*/
public int calcAutoSizeWidth(int width) {
return (int) (width * widthRatio);
}
/**
* 计算自适应高度
* @param height 真实高度px
* @return
*/
public int calcAutoSizeHeight(int height) {
return (int) (height * heightRatio);
}
/**
* 用于判断当前屏幕密度、分辨率是否发生变化
* @param context
* @return
*/
private boolean hasChange(Context context){
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point screenSize = new Point();
display.getSize(screenSize);
if (screenSize.x != screenWidth || screenSize.y != screenHeight){
return true;
}
if (density != context.getResources().getDisplayMetrics().density){
return true;
}
return false;
}
/**
* 按照比例自动调整布局,使控件在不同密度下显示一致
*
* @param view 可以是ViewGroup,也可以是View,如果是ViewGroup,则会递归调用
*/
public void autoSizeLayout(View view) {
if (view instanceof ViewGroup) {
dispatchAutoSizeLayout((ViewGroup) view, null);
} else {
loopAutoSizeView(view, null, false);
}
}
/**
* 按照比例自动调整布局,使控件在不同密度下显示一致
*
* @param viewGroup
* @param onAutoSizeViewListener 监听器,用于针对某个View进行特殊处理,会在调整布局前调用
*/
public void dispatchAutoSizeLayout(ViewGroup viewGroup, onAutoSizeViewListener onAutoSizeViewListener) {
// 判断屏幕分辨率,和density是否发生变化,如果发生变化,则重新计算
if (hasChange(viewGroup.getContext())) {
calc(viewGroup.getContext());
}
loopAutoSizeView(viewGroup, onAutoSizeViewListener, false);
loopChildAutoSizeLayout(viewGroup, onAutoSizeViewListener);
}
private void loopChildAutoSizeLayout(ViewGroup viewGroup, onAutoSizeViewListener onAutoSizeViewListener) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View childAt = viewGroup.getChildAt(i);
loopAutoSizeView(childAt, onAutoSizeViewListener, true);
}
}
private void loopAutoSizeView(View childAt, SimpleAutoSize.onAutoSizeViewListener onAutoSizeViewListener, boolean isLoopChild) {
if (onAutoSizeViewListener != null) {
onAutoSizeViewListener.onAutoSize(childAt, screenRatio);
}
ViewGroup.LayoutParams layoutParams = childAt.getLayoutParams();
if (layoutParams != null) {
// ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) atLayoutParams;
if (layoutParams.width == layoutParams.height && layoutParams.width > 0) {
// 如果宽度和高度都是开发者手动在布局文件中设置的具体数值,并且它们相等,说明开发者想要一个正方形,此时我们以较长的一边为基准
layoutParams.width = (int) (layoutParams.width * widthRatio);
layoutParams.height = (int) (layoutParams.height * heightRatio);
if (layoutParams.width > layoutParams.height) {
layoutParams.height = layoutParams.width;
} else {
layoutParams.width = layoutParams.height;
}
} else {
if (layoutParams.width > 0) {
layoutParams.width = (int) (layoutParams.width * widthRatio);
}
if (layoutParams.height > 0) {
layoutParams.height = (int) (layoutParams.height * heightRatio);
}
}
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
// Log.i("XmlBaseAdView", "处理前的Margin: " + layoutParams.topMargin + " " + layoutParams.bottomMargin + " " + layoutParams.leftMargin + " " + layoutParams.rightMargin);
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams;
marginLayoutParams.topMargin = (int) (marginLayoutParams.topMargin * heightRatio);
marginLayoutParams.bottomMargin = (int) (marginLayoutParams.bottomMargin * heightRatio);
marginLayoutParams.leftMargin = (int) (marginLayoutParams.leftMargin * widthRatio);
marginLayoutParams.rightMargin = (int) (marginLayoutParams.rightMargin * widthRatio);
// Log.i("XmlBaseAdView", "处理后的Margin: " + layoutParams.topMargin + " " + layoutParams.bottomMargin + " " + layoutParams.leftMargin + " " + layoutParams.rightMargin);
}
childAt.setLayoutParams(layoutParams);
}
// Log.i("XmlBaseAdView", "处理前的Padding: " + childAt.getPaddingTop() + " " + childAt.getPaddingBottom() + " " + childAt.getPaddingLeft() + " " + childAt.getPaddingRight());
float padTop = childAt.getPaddingTop() * heightRatio;
float padBottom = childAt.getPaddingBottom() * heightRatio;
float padLeft = childAt.getPaddingLeft() * widthRatio;
float padRight = childAt.getPaddingRight() * widthRatio;
childAt.setPadding((int) padLeft, (int) padTop, (int) padRight, (int) padBottom);
// Log.i("XmlBaseAdView", "处理后的Padding: " + childAt.getPaddingTop() + " " + childAt.getPaddingBottom() + " " + childAt.getPaddingLeft() + " " + childAt.getPaddingRight());
if (childAt instanceof TextView) {
// 如果是TextView,还需要处理字体大小
TextView textView = (TextView) childAt;
float textSize = textView.getTextSize();
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize /
density * screenRatio);
}
if (childAt instanceof ViewGroup && isLoopChild) {
loopChildAutoSizeLayout((ViewGroup) childAt, onAutoSizeViewListener);
}
}
}
初始化:
非常简单,只需要一行代码
SimpleAutoSize.getInstance().init(context, 960, 540);
使用:
第一个参数传入要适配的view,一般是根view
SimpleAutoSize.getInstance().autoSizeLayout(baseAdView, (view, screenRatio) -> {
Resources resources = context.getResources();
if (view.getId() == R.id.ll_countdown_tips) {
view.setBackground(SimpleAutoSize.createRectangleDrawable(resources.getColor(R.color.textBg)
, resources.getDimension(R.dimen.dimen_15) /
resources.getDisplayMetrics().density * screenRatio));
}
if (view.getId() == R.id.tv_show_advert_tag) {
view.setBackground(SimpleAutoSize.createRectangleDrawable(resources.getColor(R.color.textBg)
, resources.getDimension(R.dimen.dimen_3) /
resources.getDisplayMetrics().density * screenRatio));
}
});
autoSizeLayout第二个参数是onAutoSizeViewListener,用于对某些特殊View做特殊处理,比如我这个框架不支持自动调整Drawable资源的宽高,所以如果需要针对性的调整,可以传入listener自行处理。
我这里是因为shape资源无法适配,所以在代码中进行处理。
大家如需使用的话可以把以下工具方法也添加进去。
public static GradientDrawable createRectangleDrawable(int color, float radius) {
return createRectangleDrawable(color, 0, 0, radius);
}
/**
* 创建背景颜色
*
* @param color 填充色
* @param strokeColor 线条颜色
* @param strokeWidth 线条宽度 单位px
* @param radius 角度 px
*/
public static GradientDrawable createRectangleDrawable(int color, int strokeColor, int strokeWidth, float radius) {
try {
GradientDrawable radiusBg = new GradientDrawable();
//设置Shape类型
radiusBg.setShape(GradientDrawable.RECTANGLE);
//设置填充颜色
radiusBg.setColor(color);
//设置线条粗心和颜色,px
radiusBg.setStroke(strokeWidth, strokeColor);
//设置圆角角度,如果每个角度都一样,则使用此方法
radiusBg.setCornerRadius(radius);
return radiusBg;
} catch (Exception e) {
return new GradientDrawable();
}
}
/**
* 设置Selector。
*/
public static StateListDrawable newSelector(int focusColor, int normalColor, float radius) {
StateListDrawable bg = new StateListDrawable();
// View.PRESSED_ENABLED_STATE_SET
bg.addState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}, createRectangleDrawable(focusColor, radius));
// View.ENABLED_FOCUSED_STATE_SET
bg.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, createRectangleDrawable(focusColor, radius));
// View.ENABLED_STATE_SET
bg.addState(new int[]{android.R.attr.state_enabled}, createRectangleDrawable(normalColor, radius));
// View.FOCUSED_STATE_SET
bg.addState(new int[]{android.R.attr.state_focused}, createRectangleDrawable(focusColor, radius));
// View.WINDOW_FOCUSED_STATE_SET
bg.addState(new int[]{android.R.attr.state_window_focused}, createRectangleDrawable(focusColor, radius));
// View.EMPTY_STATE_SET
bg.addState(new int[]{}, createRectangleDrawable(normalColor, radius));
return bg;
}
public static GradientDrawable getBgDrawable() {
return DrawableUtils.createRectangleDrawable(Color.argb(255, 67, 81, 115), 20);
}