尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Android高手笔记-屏幕适配 UI优化

Android高手笔记-屏幕适配  UI优化 屏幕与适配由于Android碎片化严重屏幕分辨率千奇百怪而想要在各种分辨率的设备上显示基本一致的效果适配成本越来越高屏幕适配究其根本只有两个问题在不同尺寸及分辨率上UI的一致影响着用户体验从效果图到UI界面代码的转化效率影响着开发效率适配方式px在标识尺寸时Android官方并不推荐使用px像素因为不同分辨率的屏幕上同样像素大小的控件在分辨率越高的手机上UI显示效果越小因为分辨率越高单位尺寸内容纳的像素数越多dp所以官方推荐使用dp作为尺寸单位来适配UIdp在不同分辨率和尺寸的手机上代表了不同的真实像素px和dp的转化关系pxdp*dpi/160,其中dpi是像素密度是系统软件上指定的单位尺寸的像素数量往往是写在系统出厂配置文件的一个固定值这和屏幕硬件的ppi物理像素密度是不同的是参考了物理像素密度后人为指定的一个值保证了某个区间内的物理像素密度在软件上都使用同一个值有利于UI适配的简化这就是最原始的Android适配方案dp自适应布局和weight比例布局基本可以解决不同手机上的适配问题但是这种方案有两个缺陷只能适配大部分手机也做不到和效果图的完全一致某些特殊机型仍需单独适配比如同样1920*1080的手机的dpi却可能不同设计稿到布局代码的实现效率低设计稿的宽高和手机屏幕的宽高不同px和dp间的转换往往需要百分比或估算等会极大地拉低开发效率宽高限定符适配穷举市面上所有的Android手机的宽高像素值设定一个基准的分辨率(最好和设计稿的宽高一致)其他分辨率根据这个基准分辨率来计算生成对应的dimens文件可通过java、python脚本实现自动生成放在不同的尺寸文件夹values内部插图如480_320为基准对于800_480的dimens文件x1(480/320*11.5px; x2(480/320_23px;但是这种方案也有个致命缺陷需要精准命中才能适配如1440_750的手机如果找不到对应的尺寸文件夹就只能用统一默认的dimens文件UI就可能变形而Android手机厂商众多机型更是不可枚举所以容错机制很差鸿洋的AndroidAutoLayout适配方案等动态计算UI适配框架鸿洋的适配方案也来自于宽高限定符方案的启发目前已经停止维护因为框架要在运行时会在onMeasure里面做变换我们自定义的控件可能会被影响或限制可能有些特定的控件需要单独适配这里面可能存在的暗坑是不可预见的整个适配工作是有框架完成的而不是系统完成的一旦使用这个框架未来一旦遇到很难解决的问题替换起来是非常麻烦的而且项目一旦停止维护后续的升级就只能靠你自己了smallestWidth适配 或者叫sw限定符适配指的是Android会识别屏幕可用高度和宽度的较小者的dp值其实就是手机的宽度值然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件。这种机制和上文提到的宽高限定符适配原理上是一样的都是系统通过特定的规则来选择对应的文件举个例子小米5的dpi是480,横向像素是1080px根据pxdp(dpi/160)横向的dp值是1080/(480/160),也就是360dp,系统就会去寻找是否存在value-sw360dp的文件夹以及对应的资源文件插图smallestWidth限定符适配和宽高限定符适配最大的区别在于有很好的容错机制如果没有value-sw360dp文件夹系统会向下寻找比如离360dp最近的只有value-sw350dp 那么Android就会选择value-sw350dp文件夹下面的资源文件这个特性就完美的解决了 上文提到的宽高限定符的容错问题。通过java、python脚本实现自动生成dimens文件插图 这种方案的优势是稳定性不会有暗坑smallestWidth适配方案有一个小问题那就是它是在Android 3.2 以后引入的Google的本意是用它来适配平板的布局文件但是实际上显然用于diemns适配的效果更好所以这种方案支持的最小版本就是Android3.2了还有一个缺陷就是多个dimens文件可能导致apk变大根据生成的dimens文件的覆盖范围和尺寸范围apk可能会增大300kb-800kb左右糗百的拉丁吴大佬生成好的文件https://github.com/ladingwu/dimens_sw所有的适配方案都不是用来取代match_parent,wrap_content的而是用来完善他们的今日头条适配方案通过修改densitydensity dpi / 160值强行把所有不同尺寸分辨率的手机的宽度dp值改成一个统一的值这样就解决了所有的适配问题这个方案侵入性很低而且也没有涉及私有API只是对老项目是不太友好如果我们想在所有设备上显示完全一致其实是不现实的因为屏幕高宽比不是固定的16:9、4:3甚至其他宽高比层出不穷宽高比不同显示完全一致就不可能了。但是通常下我们只需要以宽支持上下滑动的页面或高不支持上下滑动的页面一个维度去适配通过阅读源码我们可以得知density 是 DisplayMetrics 中的成员变量而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以获得而Resouces通过Activity或者Application的Context获得DisplayMetrics 中和适配相关的几个变量DisplayMetrics.density 就是上述的density;DisplayMetrics.densityDpi 就是上述的dpi;DisplayMetrics#scaledDensity 字体的缩放因子正常情况下和density相等但是调节系统字体大小后会改变这个值;布局文件中dp的转换最终都是调用TypedValue#applyDimension(int unit, float value,DisplayMetrics metrics) 插图来进行转换,方法中用到的DisplayMetrics正是从Resources中获得的再看看图片的decodeBitmapFactory#decodeResourceStream方法插图也是通过 DisplayMetrics 中的值来计算的因此想要满足上述需求我们只需要修改 DisplayMetrics 中和 dp 转换相关的变量即可所以得到了下面适配方案假设设计图宽度是360dp以宽维度来适配那么适配后的 density 设备真实宽(单位px) / 360接下来只需要把我们计算好的 density 在系统中修改下即可同时在 Activity#onCreate 方法中调用下但是会有字体过小的现象原因是在上面的适配中我们忽略了DisplayMetrics#scaledDensity的特殊性将DisplayMetrics#scaledDensity和DisplayMetrics#density设置为同样的值从而某些用户在系统中修改了字体大小失效了但是我们还不能直接用原始的scaledDensity直接用的话可能导致某些文字超过显示区域因此我们可以通过计算之前scaledDensity和density的比获得现在的scaledDensity但是测试后发现另外一个问题就是如果在系统设置中切换字体再返回应用字体并没有变化。于是还得监听下字体切换调用 Application#registerComponentCallbacks 注册下onConfigurationChanged 监听即可可以参考https://github.com/Blankj/AndroidUtilCodehttps://github.com/JessYanCoding/AndroidAutoSize这两个开源库UI优化CPU 与 GPUAndroid的绘制实现主要是借助CPU与GPU结合刷新机制共同完成的。除了屏幕UI 渲染还依赖两个核心的硬件CPU 与 GPU。UI 组件在绘制到屏幕之前都需要经过 Rasterization栅格化操作而栅格化操作又是一个非常耗时的操作。GPUGraphic Processing Unit 也就是图形处理器它主要用于处理图形运算可以帮助我们加快栅格化操作。CPU软件绘制使用的是 Skia 库它是一款能在低端设备如手机上呈现高质量的 2D 跨平台图形框架类似 Chrome、Flutter 内部使用的都是 Skia 库;OpenGL 与 Vulkan对于硬件绘制我们通过调用 OpenGL ES 接口利用 GPU 完成绘制。OpenGL是一个跨平台的图形 API它为 2D/3D 图形处理硬件指定了标准软件接口。而 OpenGL ES 是 OpenGL 的子集专为嵌入式设备设计。Android 7.0 把 OpenGL ES 升级到最新的 3.2 版本同时还添加了对Vulkan的支持。Vulkan 是用于高性能 3D 图形的低开销、跨平台 API。相比 OpenGL ESVulkan 在改善功耗、多核优化提升绘图调用上有着非常明显的优势。把应用程序图形渲染过程当作一次绘画过程那么绘画过程中 Android 的各个图形组件的作用是1. 画笔Skia 或者 OpenGL。我们可以用 Skia 画笔绘制 2D 图形也可以用 OpenGL 来绘制 2D/3D 图形。正如前面所说前者使用 CPU 绘制后者使用 GPU 绘制。 2. 画纸Surface。所有的元素都在 Surface 这张画纸上进行绘制和渲染。在 Android 中Window 是 View 的容器每个窗口都会关联一个 Surface。 而 WindowManager 则负责管理这些窗口并且把它们的数据传递给 SurfaceFlinger。 3. 画板Graphic Buffer。Graphic Buffer 缓冲用于应用程序图形的绘制在 Android 4.1 之前使用的是双缓冲机制在 Android 4.1 之后使用的是三缓冲机制。 4. 显示SurfaceFlinger。它将 WindowManager 提供的所有 Surface通过硬件合成器 Hardware Composer 合成并输出到显示屏。Android 渲染的演进在 Android 3.0 之前或者没有启用硬件加速时系统都会使用软件方式来渲染 UI;Androd 3.0 开始Android 开始支持硬件加速;Android 4.0 时默认开启硬件加速;Android 4.1开启了Project Butter: 主要包含两个组成部分一个是 VSYNC一个是 Triple Buffering。VSYNC信号对于 Android 4.0CPU 可能会因为在忙别的事情导致没来得及处理 UI 绘制。为解决这个问题Project Buffer 引入了VSYNC它类似于时钟中断。每收到 VSYNC 中断CPU 会立即准备 Buffer 数据由于大部分显示设备刷新频率都是 60Hz一秒刷新 60 次也就是说一帧数据的准备工作都要在 16ms 内完成。三缓冲机制 Triple BufferingAndroid 4.1 之前Android 使用双缓冲机制CPU、GPU 和显示设备都能使用各自的缓冲区工作互不影响Android 4.1还新增了 Systrace 性能数据采样和分析工具。Tracer for OpenGL ES 也是 Android 4.1 新增加的工具它可逐帧、逐函数的记录 App 用 OpenGL ES 的绘制过程。它提供了每个 OpenGL 函数调用的消耗时间所以很多时候用来做性能分析。但因为其强大的记录功能在分析渲染问题时当 Traceview、Systrace 都显得棘手时还找不到渲染问题所在时此时这个工具就会派上用场了。Android 4.2系统增加了检测绘制过度工具Android 5.0RenderThread:经过 Project Butter 黄油计划之后Android 的渲染性能有了很大的改善。但是不知道你有没有注意到一个问题虽然我们利用了 GPU 的图形高性能运算但是从计算 DisplayList到通过 GPU 绘制到 Frame Buffer整个计算和绘制都在 UI 主线程中完成。Android 5.0 引入了两个比较大的改变。一个是引入了 RenderNode 的概念它对 DisplayList 及一些 View 显示属性做了进一步封装。另一个是引入了 RenderThread所有的 GL 命令执行都放到这个线程上渲染线程在 RenderNode 中存有渲染帧的所有信息可以做一些属性动画这样即便主线程有耗时操作的时候也可以保证动画流畅。还可以开启 Profile GPU Rendering 检查。Android 6.0 在 gxinfo 添加了更详细的信息在 Android 7.0 又对 HWUI 进行了一些重构而且支持了 Vulkan在 Android P 支持了 Vulkun 1.1。UI 渲染测量测试工具Profile GPU Rendering 和 Show GPU Overdraw。问题定位工具Systrace 和 Tracer for OpenGL ESLayout Inspector: AndroidStudio自带的工具它的主要作用就是用来查看视图层级结构的开启路径如下: 点击Tools工具栏 -第三栏的Layout Inspector - 选中当前的进程Choreographer:用来获取FPS的并且可以用于线上使用具备实时性但是仅能在Api 16之后使用具体的调用代码如下private long mStartFrameTime 0; private int mFrameCount 0; /** * 单次计算FPS使用160毫秒 */ private static final long MONITOR_INTERVAL 160L; private static final long MONITOR_INTERVAL_NANOS MONITOR_INTERVAL * 1000L * 1000L; /** * 设置计算fps的单位时间间隔1000ms,即fps/s */ private static final long MAX_INTERVAL 1000L; TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void getFPS() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.JELLY_BEAN) { return; } Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { Override public void doFrame(long frameTimeNanos) { if (mStartFrameTime 0) { mStartFrameTime frameTimeNanos; } long interval frameTimeNanos - mStartFrameTime; if (interval MONITOR_INTERVAL_NANOS) { double fps (((double) (mFrameCount * 1000L * 1000L)) / interval) * MAX_INTERVAL; // log输出fps LogUtils.i(当前实时fps值为 fps); mFrameCount 0; mStartFrameTime 0; } else { mFrameCount; } Choreographer.getInstance().postFrameCallback(this); } }); }我们需要排除掉页面没有操作的情况即只在界面存在绘制的时候才做统计。我们可以通过 addOnDrawListener 去监听界面是否存在绘制行为: getWindow().getDecorView().getViewTreeObserver().addOnDrawListenerChoreographer.getInstance().postFrameCallback();使用Choreographer获取FPS的完整代码如下在 Android Studio 3.1 之后Android 推荐使用Graphics API DebuggerGAPID来替代 Tracer for OpenGL ES 工具。GAPID 可以说是升级版它不仅可以跨平台而且功能更加强大支持 Vulkan 与回放。通过上面的几个工具我们可以初步判断应用 UI 渲染的性能是否达标例如是否经常出现掉帧、掉帧主要发生在渲染的哪一个阶段、是否存在 Overdraw 等。虽然这些图形化界面工具非常好用但是它们难以用在自动化测试场景中那有哪些测量方法可以用于自动化测量 UI 渲染性能呢1. gfxinfogfxinfo可以输出包含各阶段发生的动画以及帧相关的性能信息具体命令如下adb shell dumpsys gfxinfo 包名除了渲染的性能之外gfxinfo 还可以拿到渲染相关的内存和 View hierarchy 信息。在 Android 6.0 之后gxfinfo 命令新增了 framestats 参数可以拿到最近 120 帧每个绘制阶段的耗时信息: adb shell dumpsys gfxinfo 包名 framestats2. SurfaceFlinger除了耗时我们还比较关心渲染使用的内存。可以通过下面的命令拿到系统 SurfaceFlinger 相关的信息adb shell dumpsys SurfaceFlinger获取界面布局耗时AOPAround(execution(* android.app.Activity.setContentView(..))) public void getSetContentViewTime(ProceedingJoinPoint joinPoint) { Signature signature joinPoint.getSignature(); String name signature.toShortString(); long time System.currentTimeMillis(); try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } LogHelper.i(name cost (System.currentTimeMillis() - time)); }LayoutInflaterCompat.setFactory2上面我们使用了AOP的方式监控了Activity的布局加载耗时那么如果我们需要监控每一个控件的加载耗时该怎么实现呢Override protected void onCreate(Nullable Bundle savedInstanceState) { // 使用LayoutInflaterCompat.Factory2全局监控Activity界面每一个控件的加载耗时 // 也可以做全局的自定义控件替换处理比如将TextView全局替换为自定义的TextView。 LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() { Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { if (TextUtils.equals(name, TextView)) { // 生成自定义TextView } long time System.currentTimeMillis(); // 1 View view getDelegate().createView(parent, name, context, attrs); LogHelper.i(name cost (System.currentTimeMillis() - time)); return view; } Override public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } }); // 2、setFactory2方法需在super.onCreate方法前调用否则无效 super.onCreate(savedInstanceState); setContentView(getLayoutId()); unBinder ButterKnife.bind(this); mActivity this; ActivityCollector.getInstance().addActivity(this); onViewCreated(); initToolbar(); initEventAndData(); }UI优化的常用手段1. 尽量使用硬件加速之所以不能使用硬件加速是因为硬件加速不能支持所有的 Canvas API如果使用了不支持的 API系统就需要通过 CPU 软件模拟绘制这也是渐变、磨砂、圆角等效果渲染性能比较低的原因。SVG 也是一个非常典型的例子SVG 有很多指令硬件加速都不支持。但我们可以用一个取巧的方法提前将这些 SVG 转换成 Bitmap 缓存起来这样系统就可以更好地使用硬件加速绘制。同理对于其他圆角、渐变等场景我们也可以改为 Bitmap 实现。2. Create View 优化View 的创建也是在 UI 线程里对于一些非常复杂的界面这部分的耗时不容忽视。包括各种 XML 的随机读的 I/O 时间、解析 XML 的时间、生成对象的时间Framework 会大量使用到反射。优化方式Button buttonnew Button(this); button.setBackgroundColor(Color.RED); button.setText(Hello World); ViewGroup viewGroup (ViewGroup) LayoutInflater.from(this).inflate(R.layout.activity_main, null); viewGroup.addView(button);public static boolean prepareLooperWithMainThreadQueue(boolean reset) { if (isMainThread()) { return true; } else { ThreadLocalLooper threadLocal (ThreadLocal) ReflectionHelper.getStaticFieldValue(Looper.class, sThreadLocal); if (threadLocal null) { return false; } else { Looper looper null; if (!reset) { Looper.prepare(); looper Looper.myLooper(); Object queue ReflectionHelper.invokeMethod(Looper.getMainLooper(), getQUeue, new Class[0], new Object[0]); if (!(queue instanceof MessageQueue)) { return false; } } ReflectionHelper.invokeMethod(threadLocal, set, new Class[]{Object.class}, new Object[]{looper}); return true; } } } // 要注意的是在创建完 View 后我们需要把线程的 Looper 恢复成原来的。 private static boolean isMainThread() { return Looper.myLooper() Looper.getMainLooper(); }implementation com.android.support:asynclayoutinflater:28.0.0 // 内部分别使用了IO和反射的方式去加载布局解析器和创建对应的View // setContentView(R.layout.activity_main); // 使用AsyncLayoutInflater进行布局的加载 new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() { Override public void onInflateFinished(NonNull View view, int i, Nullable ViewGroup viewGroup) { setContentView(view); // findViewById、视图操作等 } }); super.onCreate(savedInstanceState);AsyncLayoutInflater是通过侧面缓解的方式去缓解布局加载过程中的卡顿但是它依然存在一些问题Android AsyncLayoutInflater 限制及改进:/** * 实现异步加载布局的功能修改点 * * 1. super.onCreate之前调用没有了默认的Factory * 2. 排队过多的优化 */ public class AsyncLayoutInflaterPlus { private static final String TAG AsyncLayoutInflaterPlus; private Handler mHandler; private LayoutInflater mInflater; private InflateRunnable mInflateRunnable; // 真正执行加载任务的线程池 private static ExecutorService sExecutor Executors.newFixedThreadPool(Math.max(2, Runtime.getRuntime().availableProcessors() - 2)); // InflateRequest pool private static Pools.SynchronizedPoolAsyncLayoutInflaterPlus.InflateRequest sRequestPool new Pools.SynchronizedPool(10); private Future? future; public AsyncLayoutInflaterPlus(NonNull Context context) { mInflater new AsyncLayoutInflaterPlus.BasicInflater(context); mHandler new Handler(mHandlerCallback); } UiThread public void inflate(LayoutRes int resid, Nullable ViewGroup parent, NonNull CountDownLatch countDownLatch, NonNull AsyncLayoutInflaterPlus.OnInflateFinishedListener callback) { if (callback null) { throw new NullPointerException(callback argument may not be null!); } AsyncLayoutInflaterPlus.InflateRequest request obtainRequest(); request.inflater this; request.resid resid; request.parent parent; request.callback callback; request.countDownLatch countDownLatch; mInflateRunnable new InflateRunnable(request); future sExecutor.submit(mInflateRunnable); } public void cancel() { future.cancel(true); } /** * 判断这个任务是否已经开始执行 * * return */ public boolean isRunning() { return mInflateRunnable.isRunning(); } private Handler.Callback mHandlerCallback new Handler.Callback() { Override public boolean handleMessage(Message msg) { AsyncLayoutInflaterPlus.InflateRequest request (AsyncLayoutInflaterPlus.InflateRequest) msg.obj; if (request.view null) { request.view mInflater.inflate( request.resid, request.parent, false); } request.callback.onInflateFinished( request.view, request.resid, request.parent); request.countDownLatch.countDown(); releaseRequest(request); return true; } }; public interface OnInflateFinishedListener { void onInflateFinished(View view, int resid, ViewGroup parent); } private class InflateRunnable implements Runnable { private InflateRequest request; private boolean isRunning; public InflateRunnable(InflateRequest request) { this.request request; } Override public void run() { isRunning true; try { request.view request.inflater.mInflater.inflate( request.resid, request.parent, false); } catch (RuntimeException ex) { // Probably a Looper failure, retry on the UI thread Log.w(TAG, Failed to inflate resource in the background! Retrying on the UI thread, ex); } Message.obtain(request.inflater.mHandler, 0, request) .sendToTarget(); } public boolean isRunning() { return isRunning; } } private static class InflateRequest { AsyncLayoutInflaterPlus inflater; ViewGroup parent; int resid; View view; AsyncLayoutInflaterPlus.OnInflateFinishedListener callback; CountDownLatch countDownLatch; InflateRequest() { } } private static class BasicInflater extends LayoutInflater { private static final String[] sClassPrefixList { android.widget., android.webkit., android.app. }; BasicInflater(Context context) { super(context); if (context instanceof AppCompatActivity) { // 加上这些可以保证AppCompatActivity的情况下super.onCreate之前 // 使用AsyncLayoutInflater加载的布局也拥有默认的效果 AppCompatDelegate appCompatDelegate ((AppCompatActivity) context).getDelegate(); if (appCompatDelegate instanceof LayoutInflater.Factory2) { LayoutInflaterCompat.setFactory2(this, (LayoutInflater.Factory2) appCompatDelegate); } } } Override public LayoutInflater cloneInContext(Context newContext) { return new AsyncLayoutInflaterPlus.BasicInflater(newContext); } Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { View view createView(name, prefix, attrs); if (view ! null) { return view; } } catch (ClassNotFoundException e) { // In this case we want to let the base class take a crack // at it. } } return super.onCreateView(name, attrs); } } public AsyncLayoutInflaterPlus.InflateRequest obtainRequest() { AsyncLayoutInflaterPlus.InflateRequest obj sRequestPool.acquire(); if (obj null) { obj new AsyncLayoutInflaterPlus.InflateRequest(); } return obj; } public void releaseRequest(AsyncLayoutInflaterPlus.InflateRequest obj) { obj.callback null; obj.inflater null; obj.parent null; obj.resid 0; obj.view null; sRequestPool.release(obj); } }View 重用:ListView、RecycleView 通过 View 的缓存与重用大大地提升渲染性能。因此我们可以参考它们的思想实现一套可以在不同 Activity 或者 Fragment 使用的 View 缓存机制。AsynclayoutInflater异步创建View异步创建:那我们能不能在线程提前创建 View实现 UI 的预加载吗可以通过又一个非常取巧的方式来实现。在使用线程创建 UI 的时候先把线程的 Looper 的 MessageQueue 替换成 UI 线程 Looper 的 Queue。使用代码创建;不能设置LayoutInflater.Factory需要通过自定义AsyncLayoutInflater的方式解决由于它是一个final所以需要将代码直接拷处进行修改。因为是异步加载所以需要注意在布局加载过程中不能有依赖于主线程的操作。X2C: 框架保留了XML的优点并解决了其IO操作和反射的性能问题。开发人员只需要正常写XML代码即可在编译期X2C会利用APT工具将XML代码翻译为Java代码。3. measure/layout 优化渲染流程中 measure 和 layout 也是需要 CPU 在主线程执行的;优化方法减少 UI 布局层次优化 layout 的开销尽量不要重复去设置背景单层布局尽量选择LinearLayout或FrameLayout而少用 RelativeLayout应为RelativeLayout功能较复杂更耗性能但从程序扩展性的角度看更倾向于RelativeLayout多层布局布局较复杂时RelativeLayout能够有效的减少布局层级标签实现布局文件的复用如app自定义的TitleBar 只支持 layout_xx 和id属性当include和被包含布局的根标签都指定了id时以include为准指定layout_xx属性时 必须也要指定layout_width和layout_height否则无法生效标签在UI的结构优化中起着非常重要的作用它可以删减多余的层级优化UI。多用于替换FrameLayout或者当一个布局包含另一个时标签消除视图层次结构中多余的视图组。例如你的主布局文件是垂直布局引入了一个垂直布局的include这是如果include布局使用的LinearLayout就没意义了 使用的话反而减慢你的UI表现。这时可以使用标签优化。标签懒加载不会影响UI初始化时的性能各种不常用的布局如进度条、显示错误消息等可以使用ViewStub标签以减少内存使用量加快渲染速度使用 style 来定义通用的属性从而重复利用代码减少代码量封装组合view实现view复用使用 LinearLayoutCompat 组件来实现线性布局元素之间的分割线从而减少了使用View来实现分割线效果布局优化Litho异步布局1. 配置Litho的相关依赖 // 项目下 repositories { jcenter() } // module下 dependencies { // ... // Litho implementation com.facebook.litho:litho-core:0.33.0 implementation com.facebook.litho:litho-widget:0.33.0 annotationProcessor com.facebook.litho:litho-processor:0.33.0 // SoLoader implementation com.facebook.soloader:soloader:0.5.1 // For integration with Fresco implementation com.facebook.litho:litho-fresco:0.33.0 // For testing testImplementation com.facebook.litho:litho-testing:0.33.0 // Sections options用来声明去构建一个list implementation com.facebook.litho:litho-sections-core:0.33.0 implementation com.facebook.litho:litho-sections-widget:0.33.0 compileOnly com.facebook.litho:litho-sections-annotations:0.33.0 annotationProcessor com.facebook.litho:litho-sections-processor:0.33.0 } 2. Application下的onCreate方法中初始化SoLoader Override public void onCreate() { super.onCreate(); SoLoader.init(this, false); //Litho使用了Yoga进行布局而Yoga包含有native依赖在Soloader.init方法中对这些native依赖进行了加载。 } 3. 在Activity的onCreate方法中添加如下代码即可显示单个的文本视图 // 1、将Activity的Context对象保存到ComponentContext中并同时初始化 // 一个资源解析者实例ResourceResolver供其余组件使用。 ComponentContext componentContext new ComponentContext(this); // 2、Text内部使用建造者模式以实现组件属性的链式调用下面设置的text、 // TextColor等属性在Litho中被称为Prop此概念引申字React。 Text lithoText Text.create(componentContext) .text(Litho text) .textSizeDip(64) .textColor(ContextCompat.getColor(this, R.color.light_deep_red)) .build(); // 3、设置一个LithoView去展示Text组件LithoView.create内部新建了一个 // LithoView实例并用给定的ComponentlithoText进行初始化 setContentView(LithoView.create(componentContext, lithoText)); 4. 使用自定义Component Litho中的视图单元叫做Component即组件它的设计理念来源于React组件化的思想。 每个组件持有描述一个视图单元所必须的属性与状态用于视图布局的计算工作。 视图最终的绘制工作是由组件指定的绘制单元View或Drawable来完成的。 LayoutSpec public class ListItemSpec { OnCreateLayout static Component onCreateLayout(ComponentContext context) { // Column的作用类似于HTML中的div标签 return Column.create(context) .paddingDip(YogaEdge.ALL, 16) .backgroundColor(Color.WHITE) .child(Text.create(context) .text(Litho Study) .textSizeSp(36) .textColor(Color.BLUE) .build()) .child(Text.create(context) .text(JsonChao) .textSizeSp(24) .textColor(Color.MAGENTA) .build()) .build(); } } // 2、构建ListItem组件 ListItem listItem ListItem.create(componentContext).build();Litho是 Facebook 开源的声明式 Android UI 渲染框架它是基于另外一个 Facebook 开源的布局引擎Yoga开发的。Flutter:自己的布局 渲染引擎RenderThread 与 RenderScriptAndroid 5.0系统增加了 RenderThread对于 ViewPropertyAnimator 和 CircularReveal 动画我们可以使用RenderThead 实现动画的异步渲染。
返回列表