前言:在《Android 关于屏幕的一些事儿》中最后提到了开发中我们比较头疼的问题--屏幕适配,本文将提供一种比较简单粗暴并且行之有效的解决方案。
一、问题回顾
《Android 关于屏幕的一些事儿》中详细分析了android开发中关于适配的一些常识,如果之前没有了解过建议先阅读下。
我们提出的问题在OPPO 1107上想要达到屏幕宽度的一半我们要写160dp,而在MI3中我们却要写180dp,那么我们到底要写多少才是真正的一半呢?
当然是具体的写多少都是有问题的,由于一般开发中都是默认屏幕的宽度是320dp的,那么能不能我写160px在所有的设备上都是显示占屏幕宽度的一半呢?
基于这个想法,我们来开启今天的讨论。
二、解决思路
由于不同的设备并不是都以320dp为基准的,为什么会出现这种状况就要讨论屏幕分辨率以及屏幕ppi等概念,由于之前博客中有详细说明,这里就不再赘述。
那么我们可以进行一次缩放适配,屏幕宽度以320dp为基准,屏幕真实的dp数目 / 320 就是缩放比例系数,用这个系数对布局中精确值定义的控件进行缩放就可以达到适配的目的。
比如:在MI3中,竖屏时真实宽度dp数目为360dp,除以 320dp的基准得到的缩放比例为 1.125,那么将宽度为160dp 的乘以 1.125 得到 180dp。
三、代码编写
1. 获取屏幕真实dp数
/**
* 获取屏幕可操作区域宽度dp数目
*
* @param activity
* @return
*/
private float getScreenWidthDp(Activity activity) {
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.widthPixels / displayMetrics.density;
}
2. 计算缩放比例
scale = getScreenWidthDp(activity) / 320.0f;
3. 遍历布局
/**
* 缩放View
* @param view
*/
public void scaleView(View view) {
if(null != view && scale != 1.0f) {
// TODO:重新设置View大小
if(view instanceof ViewGroup) {
scaleView((ViewGroup) view);
}
}
}
/**
* 缩放ViewGroup
* @param viewGroup
*/
public void scaleView(ViewGroup viewGroup) {
if(null != viewGroup && scale != 1.0f) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
scaleView(viewGroup.getChildAt(i));
}
}
}
4. 重新设置View大小
/**
* 重新设置View大小
* @param view
*/
private void resetViewSize(View view) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
if(null != layoutParams) {
if(layoutParams instanceof ViewGroup.MarginLayoutParams) {
// TODO:对margin进行缩放
}
if(layoutParams.width != ViewGroup.LayoutParams.MATCH_PARENT
&& layoutParams.width != ViewGroup.LayoutParams.WRAP_CONTENT) {
// TODO:宽度为精确值时进行缩放
}
if(layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT
&& layoutParams.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
// TODO:高度为精确值时进行缩放
}
view.setLayoutParams(layoutParams);
}
// TODO:对padding进行缩放
// TODO:对字体进行缩放
}
5. 缩放函数
private int resetWidth(int width) {
return (int) (width * scale);
}
private int resetHeight(int height) {
return (int) (height * scale);
}
private float resetTextSize(float textSize) {
return textSize * scale;
}
经过以上几个步骤,我们简单粗暴的屏幕适配就完成啦。
四、完整代码
/**
* 版权所有:XXX有限公司
*
* ScaleHelper
*
* @author zhou.wenkai ,Created on 2016-6-14 11:17:54
* Major Function:<b>布局适配帮助类</b>
*
* 注:如果您修改了本类请填写以下内容作为记录,如非本人操作劳烦通知,谢谢!!!
* @author mender,Modified Date Modify Content:
*/
public class ScaleHelper {
// 默认屏幕宽度基准
private float screenWidthDp = 320.0f;
// 缩放比例
private float scale;
public ScaleHelper(Activity activity) {
if(null == activity) {
throw new IllegalArgumentException("activity must`t be null!");
}
scale = getScreenWidthDp(activity) / screenWidthDp;
}
/**
* 获取屏幕可操作区域宽度dp数目
*
* @param activity
* @return
*/
private float getScreenWidthDp(Activity activity) {
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.widthPixels / displayMetrics.density;
}
/**
* 缩放View
* @param view
*/
public void scaleView(View view) {
if(null != view && scale != 1.0f) {
resetViewSize(view);
if(view instanceof ViewGroup) {
scaleView((ViewGroup) view);
}
}
}
/**
* 缩放ViewGroup
* @param viewGroup
*/
public void scaleView(ViewGroup viewGroup) {
if(null != viewGroup && scale != 1.0f) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
scaleView(viewGroup.getChildAt(i));
}
}
}
/**
* 重新设置View大小
* @param view
*/
private void resetViewSize(View view) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
if(null != layoutParams) {
// 对margin进行缩放
if(layoutParams instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams;
marginLayoutParams.leftMargin = resetWidth(marginLayoutParams.leftMargin);
marginLayoutParams.topMargin = resetHeight(marginLayoutParams.topMargin);
marginLayoutParams.rightMargin = resetWidth(marginLayoutParams.rightMargin);
marginLayoutParams.bottomMargin = resetHeight(marginLayoutParams.bottomMargin);
}
// 宽度为精确值时进行缩放
if(layoutParams.width != ViewGroup.LayoutParams.MATCH_PARENT
&& layoutParams.width != ViewGroup.LayoutParams.WRAP_CONTENT) {
layoutParams.width = resetWidth(layoutParams.width);
}
// 高度为精确值时进行缩放
if(layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT
&& layoutParams.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
layoutParams.height = resetHeight(layoutParams.height);
}
view.setLayoutParams(layoutParams);
}
// 对padding进行缩放
view.setPadding(
resetWidth(view.getPaddingLeft()),
resetHeight(view.getPaddingTop()),
resetWidth(view.getPaddingRight()),
resetHeight(view.getPaddingBottom())
);
// 对字体大小进行缩放
if(view instanceof TextView) {
((TextView)view).setTextSize(0, resetTextSize(((TextView) view).getTextSize()));
}
}
private int resetWidth(int width) {
return (int) (width * scale);
}
private int resetHeight(int height) {
return (int) (height * scale);
}
private float resetTextSize(float textSize) {
return textSize * scale;
}
}
五、使用步骤
1. 初始化ScaleHelper
ScaleHelper scaleHelper = new ScaleHelper(MainActivity.this);
2. 对需要适配的布局进行适配
scaleHelper.scaleView(view);
六、效果预览
布局分为上下两部分,以上部分为参照,下部分进行布局适配,在OPPO 1107上屏幕宽度dp和宽度基准320dp相同不做处理,在MI3上进行适配后就达到了和OPPO 1107上相同的效果。
想到以前做项目布局要求极其严格,那些设计测试恨不得1px都能看得出来,所以不得已又大段大段的代码适配布局,看下用了该工具的效果:
对比上下两张卡,在MI3的设备时使用布局适配后,效果还是非常明显的!!!
八、一行引入库
如果您的项目使用 Gradle 构建, 只需要在您的build.gradle
文件添加下面一行到 dependencies
:
compile 'com.kevin:scalehelper:1.0.0'