手撸一款Android屏幕适配SDK
1、屏幕适配的原因
Android手机屏幕碎片化严重,导致界面元素在不同屏幕上的显示效果不一致。下面我们看下未对控件适配在不同屏幕上的截图。
这张是在不同机型上未适配的截图。
这张是在不同机型适配后的图。
这是夜游模拟器为适配
2、屏幕适配的目的
让界面元素匹配不同屏幕的尺寸
3、屏幕适配的方式
布局适配
避免写死布局控件,使用warp_content,match_parent。
LinerLayout 使用layout_weight属性
RelativeLinerLayout使用Layout_centerInParent,layout_toLeft等属性
推荐使用ContraintLayout约束布局。
Percent_support_lib 百分比布局容器 layout_weithPercent=“30%”
图片适配
.9图或SVG图实现缩放
备用位图匹配不同分辨率(不同文件夹下放置不同分辨率的图片)
用户流程适配
根据业务逻辑执行不同的逻辑跳转
根据别名展示不同的界面
限定符的适配
分辨率限定符:drawble-hdpi/xhdpi…
尺寸限定符:layout-small/layout-lager…
最小宽度限定符:values-sw360dp/values-sw384dp,…
屏幕方向限定符:layout-land,layout-port.
刘海屏适配
Android 9.0官方适配
华为,小米,Vivo等官方文档
屏幕适配的核心
说了这么多,我们想想 屏幕适配的核心是什么? 前面我们说因为屏幕碎片化严重,所以需要适配,目的就是为了在不同的屏幕上展示相同的效果,这个要有相同的效果就是需要将屏幕中的元素进行 缩放 。所以,屏幕适配的核心就是适配,我们要做的就是对不同的屏幕找到它对应的适配比例。
屏幕适配的原理
上面我们说了屏幕适配的核心是缩放,我们要做的就是找到对应的缩放比例,之前所说的各种适配方式就针对不同屏幕,对元素做缩放。
但是之前的屏幕适配方案都感觉有点麻烦,小编认为相对比较简单的方案就是今日头条的修改系统的density,但是缺点就是适配范围不可控,它是一刀切的形式。适配范围不能随心所欲。那么有没有一种适配是可以想要适配某个布局就让它适配的昵。
它是有的,我们知道我们可以获取到屏幕的宽度和高度,这个宽高就是像素,设计师设计的图一般都会标准dx。我们假设设计师的设计稿中屏幕的尺寸为1080*1920,我们屏幕的尺寸为720*1280。一个View的宽高在设计屏幕中应该比在设计稿中应该缩小至之前的2/3.这个2/3就是缩放比例。
我们有了这个缩放比例后就可以,根据设计稿中的尺寸乘以这个尺寸得到屏幕中的尺寸。
自定义UI适配工具类
以一个特定宽度尺寸的设备为参考,在View的加载过程中,根据当前设备的实际像素换算出目标像素,再作用在控件上。
定义设计稿中的屏幕宽高
首先我们需要设定一个缩放的基准,这个基准就是设计稿中的屏幕尺寸,这里我们设为1080*1920
//屏幕基准分辨率值
public static final float STANDARD_WIDTH=1080f;
public static final float STANDARD_HEIGHT=1920f;
获取系统屏幕宽高尺寸
接着我们要获取系统手机屏幕的分辨率,记得这里要减去屏幕的状态栏的高度
//当前设备信息
public static float displayMetricsWidth;
public static float displayMetricsHeight;
public static float stateBarHeight; ;
/******
* 省略部分代码
*
******/
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);
int systemBarHeight=getSystemBarHeight(context);
//判断当前手机横竖屏
if(displayMetrics.widthPixels>displayMetrics.heightPixels){
//TODO 注意这里要减去状态栏的高度 bthvi
displayMetricsWidth=(float)(displayMetrics.heightPixels);
displayMetricsHeight=(float)(displayMetrics.widthPixels-systemBarHeight);
}else{
displayMetricsWidth=(float)(displayMetrics.widthPixels);
displayMetricsHeight=(float)(displayMetrics.heightPixels-systemBarHeight);
}
stateBarHeight = getSystemBarHeight(context);
}
}
/**
* 用于得到状态框的高度
*/
public int getSystemBarHeight(Context context){
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
int h = context.getResources().getDimensionPixelSize(resourceId);
if (h != -1) {
return h;
}
return getHeight(context,"com.android.internal.R$dimen","system_bar_height",48);
}
/**
* 通过反射得到状态栏的高度
* @param context
* @param dimeClass
* @param system_bar_height
* @param defaultValue
* @return
*/
private int getHeight(Context context, String dimeClass, String system_bar_height, int defaultValue) {
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;
}
设定控件的初始宽高
按照设计稿尺寸在xml中定义控件的宽高
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#EEEEEE"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/title"
android:orientation="horizontal"
android:layout_width="match_parent"
android:background="#FFFFFF"
android:layout_height="160px">
<ImageView
android:id="@+id/back"
android:src="@mipmap/btn_back"
android:layout_gravity="center_vertical"
android:layout_margin="43px"
android:layout_width="40px"
android:layout_height="63px" />
<TextView
android:id="@+id/text"
android:textColor="#333333"
android:textSize="52px"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:layout_marginRight="83px"
android:text="UICalculate"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:id="@+id/img_top"
android:src="@mipmap/polyv_top_img"
android:layout_width="1080px"
android:layout_height="518px"
android:scaleType="fitXY"
/>
<RelativeLayout
android:id="@+id/middle_layout"
android:padding="29px"
android:background="#FFFFFF"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/polyv_new_img"
android:scaleType="fitXY"
android:src="@mipmap/polyv_news_img"
android:layout_width="92px"
android:layout_height="92px" />
<TextView
android:id="@+id/news_top"
android:layout_toEndOf="@+id/polyv_new_img"
android:layout_marginLeft="43px"
android:gravity="center_vertical"
android:textSize="40px"
android:textColor="#333333"
android:text="UICalculate视频直播上线啦"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/news_bottom"
android:layout_toEndOf="@+id/polyv_new_img"
android:layout_below="@+id/news_top"
android:textColor="#898989"
android:text="2019年6月30日 08:30"
android:textSize="35px"
android:layout_marginLeft="43px"
android:layout_width="match_parent"
android:layout_height="49px" />
</RelativeLayout>
<LinearLayout
android:id="@+id/bottom_layout"
android:layout_marginTop="23px"
android:orientation="vertical"
android:background="#FFFFFF"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:paddingBottom="32px"
android:paddingTop="32px"
android:paddingLeft="43px"
android:paddingRight="43px"
android:layout_width="match_parent"
android:gravity="center_vertical"
android:layout_height="127px">
<TextView
android:text="直播回看"
android:textColor="#333333"
android:textSize="46px"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_alignParentRight="true"
android:src="@mipmap/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<View
android:id="@+id/divider"
android:background="#eeeeee"
android:layout_marginLeft="43px"
android:layout_width="match_parent"
android:layout_height="1px"/>
<RelativeLayout
android:background="#FFFFFF"
android:paddingTop="43px"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/bottom_layout1"
android:paddingLeft="43px"
android:layout_alignParentLeft="true"
android:layout_width="540px"
android:layout_height="400px">
<TextView
android:id="@+id/titletext"
android:textColor="#666666"
android:text="主题分享 >"
android:textSize="40px"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_marginTop="14px"
android:layout_below="@+id/titletext"
android:src="@mipmap/hy_polyvlive1"
android:layout_width="475px"
android:layout_height="288px" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/bottom_layout2"
android:paddingRight="43px"
android:paddingLeft="23px"
android:layout_alignParentRight="true"
android:layout_width="540px"
android:layout_height="400px">
<TextView
android:id="@+id/titletext2"
android:textColor="#666666"
android:text="研报直播 >"
android:textSize="40px"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_marginTop="14px"
android:layout_alignParentRight="true"
android:layout_below="@+id/titletext2"
android:src="@mipmap/hy_polyvlive2"
android:layout_width="475px"
android:layout_height="288px" />
</RelativeLayout>
<RelativeLayout
android:paddingLeft="43px"
android:id="@+id/bottom_layout3"
android:layout_below="@+id/bottom_layout1"
android:layout_alignParentLeft="true"
android:layout_width="540px"
android:layout_height="400px">
<TextView
android:id="@+id/titletext3"
android:textColor="#666666"
android:text="每日一课 >"
android:textSize="40px"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_marginTop="14px"
android:layout_below="@+id/titletext3"
android:src="@mipmap/hy_polyvlive3"
android:layout_width="475px"
android:layout_height="288px" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/bottom_layout4"
android:layout_below="@+id/bottom_layout2"
android:paddingRight="43px"
android:paddingLeft="23px"
android:layout_alignParentRight="true"
android:layout_width="540px"
android:layout_height="400px">
<TextView
android:id="@+id/titletext4"
android:textColor="#666666"
android:text="财经路上 >"
android:textSize="40px"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_marginTop="14px"
android:layout_alignParentRight="true"
android:layout_below="@+id/titletext4"
android:src="@mipmap/hy_polyvlive4"
android:layout_width="475px"
android:layout_height="288px" />
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
通过反射得到屏幕中所有控件
首先通过反射得到屏幕中根布局的布局,再通过递归逐层获取它的子控件,并对子控件进行适配。
/**
* 根据传入的Activity获取屏幕所有的适配
* @param activity
* @return
*/
public static List<View> getLsitViews(Activity activity) {
List<View> listViews = new ArrayList<>();
ViewGroup context = (ViewGroup) activity.findViewById(android.R.id.content);
addUICalculateView(context);
return listViews;
}
/**
* 遍历所有子view
*
* @param
* @param
*/
public static void addUICalculateView(View context) {
if (context instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) context;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View view = viewGroup.getChildAt(i);
ViewAttr attr = new ViewAttr(view);
attr.setPadding_left(view.getPaddingLeft());
attr.setPadding_top(view.getPaddingTop());
attr.setPadding_right(view.getPaddingRight());
attr.setPadding_bottom(view.getPaddingBottom());
attr.apply();
addUICalculateView(view);
}
}else {
}
}
得到控件在屏幕中的宽高
我们前面知道了屏幕的宽高,基准的宽高,那么我们根据控件的宽高可以得到控件在屏幕中的宽高。
/**
* 获取当前屏幕的控件的宽
* @param width
* @return
*/
public int getWidth(int width) {
return Math.round((float)width * displayMetricsWidth / STANDARD_WIDTH);
}
/**
* 获取当前屏幕控件的高
* @param height
* @return
*/
public int getHeight(int height) {
return Math.round((float)height * displayMetricsHeight / (STANDARD_HEIGHT-stateBarHeight));
}
设置控件在屏幕中的宽高
得到控件在屏幕中宽高之后我们需要通过工具类将新的宽高设置给控件。
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* Copyright: Copyright (c) 2018
* Project: 屏幕适配工具类
* Author: bthvi
* Date: 2019-06-21 09:26
*/
public class ViewCalculateUtil
{
private static final String TAG = ViewCalculateUtil.class.getSimpleName();
/**
* 根据屏幕的大小设置view的高度,间距
*
* @param view
* @param width
* @param height
* @param topMargin
* @param bottomMargin
* @param lefMargin
* @param rightMargin
*/
public static void setViewLayoutParam(View view, int width, int height, int topMargin, int bottomMargin, int lefMargin, int rightMargin)
{
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
if (layoutParams != null)
{
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);
}
else
{
}
}
/**
* @param view
* @param width
* @param height
* @param topMargin
* @param bottomMargin
* @param lefMargin
* @param rightMargin
*/
public static void setViewFrameLayoutParam(View view, int width, int height, int topMargin, int bottomMargin, int lefMargin,
int rightMargin)
{
FrameLayout.LayoutParams layoutParams = (FrameLayout.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);
}
/**
* 设置view的内边距
*
* @param view
* @param topPadding
* @param bottomPadding
* @param leftpadding
* @param rightPadding
*/
public static void setViewPadding(View view, int topPadding, int bottomPadding, int leftpadding, int rightPadding)
{
view.setPadding(UIUtils.getInstance( ).getWidth(leftpadding),
UIUtils.getInstance( ).getHeight(topPadding),
UIUtils.getInstance( ).getWidth(rightPadding),
UIUtils.getInstance( ).getHeight(bottomPadding));
}
/**
* 设置字号
*
* @param view
* @param size
*/
public static void setTextSize(TextView view, int size)
{
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, UIUtils.getInstance( ).getHeight(size));
}
/**
* 设置LinearLayout中 view的高度宽度
*
* @param view
* @param width
* @param height
*/
public static void setViewLinearLayoutParam(View view, int width, int height)
{
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;
}
view.setLayoutParams(layoutParams);
}
public static void setViewGroupLayoutParam(View view, int width, int height)
{
ViewGroup.LayoutParams 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;
}
view.setLayoutParams(layoutParams);
}
/**
* 设置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);
}
/**
* 设置RelativeLayout中 view的高度宽度
*
* @param view
* @param width
* @param height
*/
public static void setViewRelativeLayoutParam(View view, int width, int height, int topMargin, int bottomMargin, int lefMargin,
int rightMargin)
{
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.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 static void setViewGroupLayoutParam(View view, int width, int height, int margin_top, int margin_bottom, int margin_left, int margin_right) {
}
}
在代码中的应用
在代码中使用可以说非常简单,就两句话,
1、先初始化,
2、然后如果适配Activity再传入需要适配的Activity的上下文;
如果是适配布局则直接传入布局就OK了。
就是这么简单。
//初始化
UIUtils.getInstance(this);
//根据布局适配
UIUtils.getInstance().register(linearLayout);
//根据Activity适配
UIUtils.getInstance().register(this);
适配效果
这张是只适配中间布局的,也就是调用了 UIUtils.getInstance().register(linearLayout);
这张是只适配全局的,也就是调用了 UIUtils.getInstance().register(this);
总结
优点:
相比较其他的屏幕适配这哥屏幕适配方案可以说是返璞归真,傻瓜式接入,就两行代码解决。适配所有屏幕,别且对任何系统控件和系统属性都支持。
缺点:
目前来说缺点可能就是对于dp不适配。
项目链接:
github:一种非常简单的屏幕适配方案
作者:紫雾凌寒
来源:CSDN