2020-09-01

移动开发之100%设备屏幕适配方案

前言:本片博客将全面深入分析屏幕适配方案,针对不同项目要求,提供不同的适配框架分析,要求框架能100%适配所有设备。

目录

1.前期技术问题分析

2.适用于中小型项目适配方案

3.适用于大型项目适配方案

一、前期技术问题分析: 

1.如何获取全局Context并解决Application冲突:

2020-09-01

前言:从上图Context的类继承关系可以看到,有两个直系子类,分别是包装类和实现类。包装类下可以看到我们熟悉的身影:Service、Application、Activity,所以我们得出结论,Context只在上述环境中存在。因此要想获取全局context,我们常用的方式就是写一个MyApplication继承Application,然后重写onCreate方法提供全局上下文环境,然后

public class MyApplication extends Applocation {

private static Context context;

@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}

public static Context getContext() {
return context;
}

}

我们要告知系统修改其默认做法,转而启动我们自定义的MyApplication类。具体来说就是在AndroidManifest.xml文件的<application>标签下进行指定。 由于一个工程只能有一个Application,所以当我们使用第三方开源项目如Litepal等对application的配置有需求的资源时,就不能使用上述方法重复配置了,我们只需要在自定义的Application加入支持就可以了。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
......
<application
android:name="com.example.test.MyApplication"
......>
......
</application>
</manifest>

public class MyApplication extends Applocation {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
LitePalApplication.initialize(context);//加入支持
}

public static Context getContext() {
return context;
}
}

2.自定义View相关重点: 

首先上图:

2020-09-01

通过上图可以清楚的看出View自定义的过程:

如果是View:首先调用measure方法,接着会调用measure中的onMeasure方法进行测量,测量规则自定义

如果是ViewGroup:首先调用measure方法,接着会调用每个子View的measure方法进行测量,测量规则见下面分析

如何在Activity启动时获取View的宽/高?

1.重写onWindowFocusChanged方法,该方法的含义就是View已经初始化完成了

2.view.post(runnable),该方法的含义就是View已经初始化完成了

3.使用ViewTreeObserver的众多回调可以实现该功能

布局过程的自定义分为三类:

1.重写onMeasure来修改已有View的尺寸

 第一步:调用super.onMeasure方法按原来的计算方法测量一次

第二步:获取到原来的尺寸

第三步:修改成新的尺寸

第四步:保存修改后的尺寸

2.重写onMeasure来全新定义View的尺寸

区别:不需要调用super.onMeasure方法

 第一步:重写onMeasure把自己的尺寸计算出来

第二步:把计算的尺寸通过resolveSize()过滤一遍即可满足父View对子View限制要求的View

2020-09-01

3.重写onMeasure和onLayout来全新定义ViewGroup的内部布局

第一步:重写onMeasure来计算内部布局 

    调用每个子View的measure方法,让子View自我测量
    根据子View给出的尺寸,得出子View的位置,并保存他们的位置和尺寸
    根据子View的位置和尺寸计算出自己的尺寸,并用setMeasureDimension保存

2020-09-01

 

第二步:重写onLayout来摆放子View 

2020-09-01

自定义View须知:

1.让view支持wrap_content

解决方法:只需要对wrap_content模式设置一个默认宽/高即可

2.如果有必要,让view支持padding

解决方法:在onDraw方法中进行运算即可

3.尽量不要在view中使用handler,没必要,因为view内部提供了很多post方法

4.view中如果有线程或者动画,需要及时停止

5.view带有滑动嵌套时,要处理好滑动冲突

二、中小型项目屏幕适配解决方案框架:

框架分析:中小型项目对性能要求相对较低,因此采用修改布局参数的方法进行屏幕适配,该框架存在的性能问题是布局绘制了两次,第一次是setContentView(R.layout.activity_main),第二次是修改布局参数时,因此性能较低。

模块一:实现MyApplication提供全局context,方便使用

模块二:编写UIUtils类计算得到缩放比例

模块三:提供队外调用接口程序,实现框架的调用

模块一:

package com.example.adapter_screenapplication;

import android.app.Application;

/**
* 为了方便框架内部使用application和context使用
*/
public class JettApplication extends Application {
private static JettApplication intance;
public static JettApplication getInstance(){
return intance;
}
@Override
public void onCreate() {
super.onCreate();
intance = this;
}
}

模块二:

package com.example.adapter_screenapplication;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import java.lang.reflect.Field;
/**
* 用来基于美工提供的基准值生成真实设备的宽高值
*/
public class UIUtils {
//美工提供的标准宽高
public static final float STANDARD_WIDTH = 1080.0f;
public static final float STANDARD_HEIGTH = 1872.0f;
private static final String DIMEN_CLASS = "com.android.internal.R$dimen";
//实际设备的分辨率是 480 800
public float displayMetricsWidth;
public float displayMetricsHeigth;
//生成单例
private static UIUtils ourInstance;
public static UIUtils getInstance(Context context){
if (ourInstance==null){
ourInstance = new UIUtils(context);
}
return ourInstance;
}
private UIUtils(Context context){
//获取屏幕的真实宽高
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
if (displayMetricsWidth==0.0F||displayMetricsHeigth==0.0F){
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
//获取状态栏高度
int systemHeigth = getSystemHeigth(context);
//处理真实宽高
if (displayMetrics.widthPixels>displayMetrics.heightPixels){//横屏
this.displayMetricsWidth = (float) displayMetrics.heightPixels;
this.displayMetricsHeigth = (float) displayMetrics.widthPixels-systemHeigth;
}else {//竖屏
this.displayMetricsWidth = (float) displayMetrics.widthPixels;
this.displayMetricsHeigth = (float) displayMetrics.heightPixels-systemHeigth;
}
}
}

private int getSystemHeigth(Context context) {
return getValue(context,"com.android.internal.R$dimen","system_bar_heigth",48);
}

private int getValue(Context context, String attrGroupClass, String ayyrName, int defValue) {
try {
Class c = Class.forName(attrGroupClass);
Object obj = c.newInstance();
Field field = c.getField(ayyrName);
//获取到的是ID
int x = Integer.parseInt(field.get(obj).toString());
return context.getResources().getDimensionPixelOffset(x);
} catch (Exception e) {
return defValue;
}
}
//获取缩放后的结果
public float getWidth(float width){
return width*(this.displayMetricsWidth/STANDARD_WIDTH);
}
public float getHeigth(float heigth){
return heigth*(this.displayMetricsHeigth/STANDARD_HEIGTH);
}
public int getWidth(int width){
return (int) (width*(this.displayMetricsWidth/STANDARD_WIDTH));
}
public int getHeigth(int heigth){
return (int) (heigth*(this.displayMetricsHeigth/STANDARD_HEIGTH));
}
}

模块三:

package com.example.adapter_screenapplication;

import android.view.View;
import android.widget.LinearLayout;

public class ViewCal {
//获取调用层的值进行设置
public static void setViewLinearLayoutParas(View view,int width,int heigth,int topMargin,int bottonMargin,int leftMargin,int rigthMargin){
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
if (width!= LinearLayout.LayoutParams.MATCH_PARENT&&width!= LinearLayout.LayoutParams.WRAP_CONTENT){
layoutParams.width = (int) UIUtils.getInstance(/*提供上下文环境*/JettApplication.getInstance()).getWidth(width);
}else {
layoutParams.width = width;
}
if (heigth!= LinearLayout.LayoutParams.MATCH_PARENT&&heigth!= LinearLayout.LayoutParams.WRAP_CONTENT){
layoutParams.height = (int) UIUtils.getInstance(/*提供上下文环境*/JettApplication.getInstance()).getHeigth(heigth);
}else {
layoutParams.height = heigth;
}
layoutParams.topMargin = (int) UIUtils.getInstance(JettApplication.getInstance()).getHeigth(topMargin);
layoutParams.bottomMargin = (int) UIUtils.getInstance(JettApplication.getInstance()).getHeigth(bottonMargin);
layoutParams.leftMargin = (int) UIUtils.getInstance(JettApplication.getInstance()).getWidth(leftMargin);
layoutParams.rightMargin = (int) UIUtils.getInstance(JettApplication.getInstance()).getWidth(rigthMargin);
view.setLayoutParams(layoutParams);
}
}

调用层:

package com.example.adapter_screenapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

TextView textView1;
TextView textView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView1 = findViewById(R.id.text1);
textView2 = findViewById(R.id.text2);
ViewCal.setViewLinearLayoutParas(textView1,1040,80,10,0,20,10);
ViewCal.setViewLinearLayoutParas(textView2,400,400,20,0,0,0);
}
}

执行结果:

2020-09-01

 

三、大型项目屏幕适配解决方案框架:

框架分析:该框架从自定义布局入手,在绘制层面进行屏幕适配,因此布局只会绘制一次,提高了应用性能,适用于大型项目屏幕适配解决方案。

模块一:自定义属性(宽/高的百分比)

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PercentRelativeLayout" >
<attr name="layout_widthPercent" format="float"/>
<attr name="layout_heigthPercent" format="float"/>
</declare-styleable>
</resources>

模块二:布局中使用自定义属性

<?xml version="1.0" encoding="utf-8"?>
<!--名称空间app-->
<com.example.adapter_screenapplication2.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_widthPercent = "0.5"
app:layout_heigthPercent = "0.5"
android:background="@color/colorAccent"
android:text="Hello World!"/>
</com.example.adapter_screenapplication2.PercentRelativeLayout>

模块三:自定义ViewGroup

package com.example.adapter_screenapplication2;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
//自定义RelativeLayout
public class PercentRelativeLayout extends RelativeLayout {
//自动生成的构造方法
public PercentRelativeLayout(Context context) {
super(context);
}
public PercentRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PercentRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//重写onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = View.MeasureSpec.getSize(widthMeasureSpec);
int heigth = View.MeasureSpec.getSize(heightMeasureSpec);
//测量出子控件
int childCount = this.getChildCount();
for (int i=0;i<childCount;i++){
View child = this.getChildAt(i);
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
//把得到的布局参数进行更改
float widthPercent = 0;
float heigthPercent = 0;
if (layoutParams instanceof PercentRelativeLayout.LayoutParams){
//获取到布局文件上的内容
widthPercent = ((LayoutParams) layoutParams).getWidthPercent();
heigthPercent = ((LayoutParams) layoutParams).getHeigthPercent();
}
if (widthPercent>0){
layoutParams.width = (int) (width*widthPercent);
}
if (heigthPercent>0){
layoutParams.height = (int) (heigth*heigthPercent);
}
}
//调用父类的onMeasure方法得到的值就是修改后的值
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//重写generateLayoutParams:
@Override
public RelativeLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
//这里返回设置好的布局参数
return new LayoutParams(getContext(),attrs);
}

public static class LayoutParams extends RelativeLayout.LayoutParams{

private float widthPercent;
private float heigthPercent;
//set/get方法
public float getWidthPercent() {
return widthPercent;
}
public void setWidthPercent(float widthPercent) {
this.widthPercent = widthPercent;
}
public float getHeigthPercent() {
return heigthPercent;
}
public void setHeigthPercent(float heigthPercent) {
this.heigthPercent = heigthPercent;
}
//在这里把自定义属性加入
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.PercentRelativeLayout);
widthPercent = array.getFloat(R.styleable.PercentRelativeLayout_layout_widthPercent,0);
heigthPercent = array.getFloat(R.styleable.PercentRelativeLayout_layout_heigthPercent,0);
}
}
}

运行结果:TextView占屏幕宽高的1/2

2020-09-01

总结:

本篇博客主要从适配过程中常见的技术问题或注意事项入手,对两种不同的框架进行对比分析,读者可根据需要选择框架更改代码,实现自己的适配框架。上述框架也是自己借鉴大佬们的框架自己消化理解得到的,如有问题欢迎读者指正。

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

2020-09-01

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » 2020-09-01
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏