我在上一篇屏幕适配方案中,介绍了一下关于屏幕适配所需要知道的一些基础知识,以及一些网上的屏幕适配方面的解决方案,那么这一片的目的,就是寻找一个适合的屏幕适配方案,来保持在我们的应用再各种机型中都能有良好的表现吧
在上一篇结束时,我列举了几个屏幕适配的方案,最后我认为目前来说最合适的还是http://blog.csdn.net/zhengjingle/article/details/51742839这一片中所描述的
张鸿洋大神的适配的源码我也拜读了,只是他的结构搭见的比较大,对于他的结构我大致画了一张图
鸿神的结构搭得比较大,处理的情况也比较多不过说穿了就是两点
1、初始化时读取所有需要转化的尺寸,转化为对应的Attrs对象
2、onMeasure时执行所有Attrs对象的execute方法来调整尺寸
那么还有一点,其中的宽高的算法是怎样的呢?进去看
public void apply(View view)
{
boolean log = view.getTag() != null && view.getTag().toString().equals("auto");
if (log)
{
L.e(" pxVal = " + pxVal + " ," + this.getClass().getSimpleName());
}
int val;
if (useDefault())
{
val = defaultBaseWidth() ? getPercentWidthSize() : getPercentHeightSize();
if (log)
{
L.e(" useDefault val= " + val);
}
} else if (baseWidth())
{
val = getPercentWidthSize();
if (log)
{
L.e(" baseWidth val= " + val);
}
} else
{
val = getPercentHeightSize();
if (log)
{
L.e(" baseHeight val= " + val);
}
}
if (val > 0)
val = Math.max(val, 1);//for very thin divider
execute(view, val);
}
所有的尺寸的计算方式应该是类似的,那么我来看看宽度的计算
getPercentWidthSize()
里面是这样的
protected int getPercentWidthSize()
{
return AutoUtils.getPercentWidthSizeBigger(pxVal);
}
再往里面
public static int getPercentWidthSizeBigger(int val)
{
int screenWidth = AutoLayoutConifg.getInstance().getScreenWidth();
int designWidth = AutoLayoutConifg.getInstance().getDesignWidth();
int res = val * screenWidth;
if (res % designWidth == 0)
{
return res / designWidth;
} else
{
return res / designWidth + 1;
}
}
我来看一下这里的计算方式,首先,如果我们自己来算比例是个什么公式?
designWidth(设计的屏幕宽度) designViewWidth(设计的控件宽度,即图上标注的像素值,即val)
----------------------- = ----------------------------------------------------------
screenWidth(实际屏幕宽度)screenViewWidth(实际上在屏幕显示的像素宽度)
转换一下
designWidth(设计的屏幕宽度)*screenViewWidth(实际上在屏幕显示的像素宽度)=screenWidth(屏幕宽度)*designViewWidth(val)
screenViewWidth = screenWidth(屏幕宽度) * designViewWidth(val) /designWidth(设计的屏幕宽度)
那么放在上面的代码里就是:
val * screenWidth / designWidth
所以得出的结论就是,就是一个等比例转换,只是做了一个细化的处理
if (res % designWidth == 0)
{
return res / designWidth;
} else
{
return res / designWidth + 1;
}
对比例转换中的没有除尽的情况进行了一下处理
那么所有的要素已经集齐,剩下的就是,我自己是否要用这个库,我觉得这个库写的扩展性很好,但是所做的结构搭建的有点大,我也许可以自己封装一个好用的工具来完成这个行为。
其实,http://blog.csdn.net/zhengjingle/article/details/51742839这篇文章中做的我觉得已经很简洁了,只是区别在于,这个适配并不是在 onMeasure中做的适配,所以相对来说效率上会底点,在面对一些布局非常复杂的页面时,会有一些效率上的差距,所以我觉得我可以尝试一下
我自己的封装
首先,对于尺寸的计算工具我使用了http://blog.csdn.net/zhengjingle/article/details/51742839这篇文章中的工具,只是我稍微做了一点修改
public class AutoUtils {
private static int displayWidth;
private static int displayHeight;
private static int designWidth;
private static int designHeight;
private static double textPixelsRate;
public static void setSize(Activity act, boolean hasStatusBar, int designWidth, int designHeight){
if(act==null || designWidth<1 || designHeight<1)return;
Display display = act.getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
if (hasStatusBar) {
height -= getStatusBarHeight(act);
}
AutoUtils.displayWidth=width;
AutoUtils.displayHeight=height;
AutoUtils.designWidth=designWidth;
AutoUtils.designHeight=designHeight;
double displayDiagonal=Math.sqrt(Math.pow(AutoUtils.displayWidth, 2)+Math.pow(AutoUtils.displayHeight, 2));
double designDiagonal=Math.sqrt(Math.pow(AutoUtils.designWidth, 2)+Math.pow(AutoUtils.designHeight, 2));
AutoUtils.textPixelsRate=displayDiagonal/designDiagonal;
}
/**
* 获取状态栏高度
* @param context
* @return
*/
public static int getStatusBarHeight(Context context)
{
int result = 0;
try {
int resourceId = context.getResources().getIdentifier(
"status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(
resourceId);
}
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
return result;
}
/**
* 获取页面的View并调整尺寸
* @param act
*/
public static void auto(Activity act){
if(act==null || displayWidth<1 || displayHeight<1)return;
View view=act.getWindow().getDecorView();
auto(view);
}
public static void auto(View view){
if(view==null || displayWidth<1 || displayHeight<1)return;
AutoUtils.autoTextSize(view);
AutoUtils.autoSize(view);
AutoUtils.autoPadding(view);
AutoUtils.autoMargin(view);
if(view instanceof ViewGroup){
auto((ViewGroup)view);
}
}
private static void auto(ViewGroup viewGroup){
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
View child = viewGroup.getChildAt(i);
if(child!=null){
auto(child);
}
}
}
public static void autoMargin(View view){
if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams))
return;
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if(lp == null)return ;
lp.leftMargin = getDisplayWidthValue(lp.leftMargin);
lp.topMargin = getDisplayHeightValue(lp.topMargin);
lp.rightMargin = getDisplayWidthValue(lp.rightMargin);
lp.bottomMargin = getDisplayHeightValue(lp.bottomMargin);
}
public static void autoPadding(View view){
int l = view.getPaddingLeft();
int t = view.getPaddingTop();
int r = view.getPaddingRight();
int b = view.getPaddingBottom();
l = getDisplayWidthValue(l);
t = getDisplayHeightValue(t);
r = getDisplayWidthValue(r);
b = getDisplayHeightValue(b);
view.setPadding(l, t, r, b);
}
public static void autoSize(View view){
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp == null) return;
if(view instanceof ImageView) {
Log.d("tag", "getAutoTheViewWidthBefore:" + lp.width + ",getTheViewHeight:" + lp.height + ",ValueName:" + view.getClass().getSimpleName());
}
if(lp.width>0){
lp.width = getDisplayWidthValue(lp.width);
}
if(lp.height>0){
lp.height = getDisplayHeightValue(lp.height);
}
if(view instanceof ImageView) {
Log.d("tag", "getAutoTheViewWidth:" + lp.width + ",getTheViewHeight:" + lp.height + ",ValueName:" + (displayWidth / designWidth) + ",,Data1:" + displayWidth + "," + designWidth);
}
}
public static void autoTextSize(View view){
if(view instanceof TextView){
double designPixels=((TextView) view).getTextSize();
double displayPixels=textPixelsRate*designPixels;
((TextView) view).setIncludeFontPadding(false);
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, (float) displayPixels);
}
}
public static int getDisplayWidthValue(int designWidthValue){
int value = (designWidthValue * displayWidth / designWidth);
if(designWidthValue<2){
return designWidthValue;
}
int res = designWidthValue * displayWidth;
if (res % designWidth == 0)
{
return res / designWidth;
} else
{
return res / designWidth + 1;
}
//return designWidthValue * displayWidth / designWidth;
}
public static int getDisplayHeightValue(int designHeightValue){
if(designHeightValue<2){
return designHeightValue;
}
int res = designHeightValue * displayWidth;
if (res % designWidth == 0)
{
return res / designWidth;
} else
{
return res / designWidth + 1;
}
// return designHeightValue * displayHeight / designHeight;
//return designHeightValue * displayWidth / designWidth;//以宽度的比例为标准来计算
}
}
我把高度的计算部分替换成了用宽度的比例来计算,同时参考了张鸿洋大神的+1的方式来使尺寸的计算更为细致
为什么我连高度的计算也用宽度的比例来计算呢?因为在我尝试设置一个正方形的图片时,如果按照手机的实际高度比来计算的话,你会发现本来需要的正方形变成了长方形,比例不同,造成的结果也不同,不巧我的手机正是这样的一只手机,所以我做了一些调整
然后,在这个的基础上,我做了一个AdaptionLayout,正是用来适配的
/**
* Created by yangjh on 2017/3/8.
* 用于适配尺寸的View,在onSizeChanged中做了自己以及子View的尺寸处理
*/
public class AdaptionLayout extends LinearLayout {
public AdaptionLayout(Context context) {
super(context);
}
public AdaptionLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AdaptionLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public AdaptionLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(!isInEditMode()){
//适配尺寸,存在缺陷,子View被动态设置尺寸时,怎么适配
AutoUtils.auto(this);
}
}
/**
* 添加子布局,因为是LinearLayout并且设置的orientation为vertical
* 所以是纵向布局下去
* @param layoutId
*/
public void addLayout(int layoutId){
View layout = LayoutInflater.from(getContext()).inflate(layoutId,null);
addView(layout);
}
}
这个类用于给自己和所有子View调整尺寸
然后为了最大限度地减少调用,我写了一个BaseAdaptionActivity
/**
* Created by yangjh on 2017/3/8.
* 这个类用于屏幕适配的Activity,继承了这个类的Activity可以使用px来写layout中的控件宽高
* 在这里面会自动进行比例适配
*/
public abstract class BaseAdaptionActivity extends BaseToolActivity {
private static final String KEY_DESIGN_WIDTH = "design_width";
private static final String KEY_DESIGN_HEIGHT = "design_height";
private AdaptionLayout userLayout;//根布局
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//初始化视图的尺寸
initAutoSize();
super.onCreate(savedInstanceState);
}
private void initAutoSize(){
PackageManager packageManager = getPackageManager();
ApplicationInfo applicationInfo;
int mDesignWidth = 0;
int mDesignHeight = 0;
try
{
applicationInfo = packageManager.getApplicationInfo(this
.getPackageName(), PackageManager.GET_META_DATA);
if (applicationInfo != null && applicationInfo.metaData != null)
{
mDesignWidth = (int) applicationInfo.metaData.get(KEY_DESIGN_WIDTH);
mDesignHeight = (int) applicationInfo.metaData.get(KEY_DESIGN_HEIGHT);
}
} catch (PackageManager.NameNotFoundException e)
{
e.printStackTrace();
}
AutoUtils.setSize(this, hasStatusBar(), mDesignWidth == 0 ? 320 : mDesignWidth, mDesignHeight == 0 ? 480 : mDesignHeight);//没有状态栏,设计尺寸的宽高
}
/**
* 适配尺寸
* @param layoutResID
*/
@Override
public void setContentView(@LayoutRes int layoutResID) {
//这个自定义View中在onMeasure做了尺寸适配
userLayout = (AdaptionLayout)LayoutInflater.from(this).inflate(R.layout.layout_activity_adaption_root,null);
setContentView(userLayout);
//接下来初始化外面设置的layout
userLayout.addLayout(layoutResID);
//然后初始化默认的toolBar是否展示
View autoToolBar = userLayout.findViewById(R.id.autoToolBar);
autoToolBar.setVisibility(showToolBar()?View.VISIBLE:View.GONE);
}
/**
* 是否需要包含状态栏来适配
* @return
*/
public boolean hasStatusBar(){
return true;
}
/**
* 是否展示标题栏
* @return
*/
public boolean showToolBar(){
return false;
}
/**
* 获取根布局
* @return
*/
public AdaptionLayout getUserLayout() {
return userLayout;
}
}
这个类中去manifest中读取设计的宽高,没有读取到就使用默认的,然后继承了这个类的Activity只需要像正常使用那样设置布局,因为后续设置的ContentView都是会在AdaptionLayout中成为子View,尺寸会被自动调整
这样来说,目的基本已经达到了,一般的需求也可以满足了,但是,我考虑到一种情况会有点问题,那就是在代码中
动态地去设置改变尺寸,这种时候我的这种设计无法自动处理到,这时候就需要调用
AutoUtils.auto(view);
来适配,而使用鸿洋大神的则不需要,但是我还是觉得我这样的调用简单了许多
目前来说先这样吧,等我什么时候想到了可以解决动态设置改变宽高也可以达到适配时再行更新