本文来自网易云社区
开发的 TV 应用发现在部分电视上可以显示完整, 而其他部分电视显示不全, 周围都会遮挡了.
原因
这是因为部分老的电视有一个 overscan 的概览, 什么叫 overscan 呐? 官方解释如下:
During the evolution of TV technology, overscan originally described an area of TV content outside of a safe zone that most TVs could reliably display. Even on some of today's HDTV flat screens, areas outside that zone may not be visible.
整个展示的界面是外面整个矩形框, 而可见区域是内部的矩形框, 因此如果你布局的内容紧邻边界, 就会导致部分电视不能完整显示整个内容.
解决方案
上面的问题, 我们怎么解决呐? 这里给出两种解决方案.
设置调整
虽然老的电视有 overscan 的概念, 内容显示不全, 但是电视可以设置他的显示内容距离电视边框的距离, 这里主要依靠人为的手动设置, 方式如下:
一般都是选择遥控板的设置, 进入设置界面, 选择显示, 边距调整
经过上述方式的调整, 可以设置内容与边框有一定的距离, 这样可以让所有的内容都显示出来.
难点: 该方式必须要用户手动调整, 如果不调整则显示内容被遮挡体验效果比较差
UI 调整
既然让大家都采用手动调整是比较困难的, 那有没有从应用层面来解决该问题的方法, 有, 肯定有, 官方的文档也给出了解决方案:
Build a 5% margin into your TV screen designs to account for overscan area the TV may not display correctly. On a 1920 x 1080 screen, this margin should be a minimum of 27 pixels from the top and bottom edges and a minimum of 48 pixels from the right and left edges of the picture.
上面的意思是, 在设计界面的时候, 就空出四周的 overscan 距离, 以 1920*1080 为例上下 27px, 左右 48px, 让显示的内容完全处于安全区域, 周围都是无关内容区域.
缺点: 如果设计的内容需要靠近边界, 比如说底部有一个导航条, 可以显示与影藏, 这种时候就需要贴近边界进行设计. 如果再设计的时候空出一部分, 那在 overscan 的电视上展示较好, 无 overscan 的电视展示就比较差, 如果不空, 这效果相反
自适应?
既然上面的两种方案都是有缺点的
全部让用户手动设置不显示
空出部分内容, 在有的时候会显示效果比较差
那有没有一种方法来解决上面面临的问题? 能够根据电视状况自动缩放内容, 在有 overscan 的电视上, 拿到 overscan 的值, 设置显示内容与边界为 overscan 的值, 在没有 overscan 的电视上保持内容不调整.
方案探索
首先我们看代码, 看看代码中是否有 overscan 的值, 经过一番查找, 在 Android 中有一个 Display 类, 他包含了很多显示信息, 其中有一个字段为 DisplayInfo,DisplayInfo 包含了其他的一些显示信息. 其中有四个字段如下:
- /**
- * Describes the characteristics of a particular logical display.
- * @hide
- */public final class DisplayInfo implements Parcelable {
- .... /**
- * @hide
- * Number of overscan pixels on the left side of the display.
- */
- public int overscanLeft; /**
- * @hide
- * Number of overscan pixels on the top side of the display.
- */
- public int overscanTop; /**
- * @hide
- * Number of overscan pixels on the right side of the display.
- */
- public int overscanRight; /**
- * @hide
- * Number of overscan pixels on the bottom side of the display.
- */
- public int overscanBottom; public DisplayInfo() {
- }
- ....
- }
这里我们省略了其他的代码, 可以看到包含有 overscanLeft, overscanTop,overscanRight,overscanBottom, 这就是 Overscan 的值, 到这里感觉有希望. 既然有这四个值, 那我们获取到这四个值再设置会界面应该就能达到我们想要的效果.
读取 overscan
从上面的代码中我们可以看到 DisplayInfo 是被 hide 掉的, 四个值也是被 hide 掉的, 说明从正常的先拿到 Display, 再拿到 DisplayInfo, 之后再获取四个值是不行的了, 那我们反射来获取该值, 代码如下:
- public static Rect getOverScan() { if (overScan == null) {
- overScan = new Rect(); try {
- WindowManager manager = (WindowManager) AppProfile.getContext().getSystemService(Context
- .WINDOW_SERVICE);
- Display display = manager.getDefaultDisplay();
- Class clazz = Display.class;
- Field mDisplayInfo = clazz.getDeclaredField("mDisplayInfo");
- mDisplayInfo.setAccessible(true);
- Object displayInfo = mDisplayInfo.get(display);
- Class displayInfoClazz = Class.forName("Android.view.DisplayInfo");
- Field overscanLeft = displayInfoClazz.getDeclaredField("overscanLeft");
- overScan.left = overscanLeft.getInt(displayInfo);
- Field overscanTop = displayInfoClazz.getDeclaredField("overscanTop");
- overScan.top = overscanTop.getInt(displayInfo);
- Field overscanRight = displayInfoClazz.getDeclaredField("overscanRight");
- overScan.right = overscanRight.getInt(displayInfo);
- Field overscanBottom = displayInfoClazz.getDeclaredField("overscanBottom");
- overScan.bottom = overscanBottom.getInt(displayInfo);
- } catch (Exception e) {
- }
- } return overScan;
- }
首先获取到 Display, 之后在反射获取 DisplayInfo, 最后在读取四个值.
也可以直接反射获取 Display 下的 getOverscanInsets 函数, getOverscanInsets 函数如下:
- /**
- * @hide
- * Return a rectangle defining the insets of the overscan region of the display.
- * Each field of the rectangle is the number of pixels the overscan area extends
- * into the display on that side.
- */public void getOverscanInsets(Rect outRect) {
- synchronized (this) {
- updateDisplayInfoLocked();
- outRect.set(mDisplayInfo.overscanLeft, mDisplayInfo.overscanTop,
- mDisplayInfo.overscanRight, mDisplayInfo.overscanBottom);
- }
- }
反射获取方式如下:
- public static Rect getOverScan1() {
- Rect overScan = new Rect(); try {
- WindowManager manager = (WindowManager) AppProfile.getContext().getSystemService(Context.WINDOW_SERVICE);
- Display display = manager.getDefaultDisplay();
- Class clazz = Display.class;
- Method getOverscanInsets = clazz.getMethod("getOverscanInsets", Rect.class);
- getOverscanInsets.invoke(display, overScan);
- LogUtil.free(overScan.toString());
- } catch (Exception e) {
- } return overScan;
- }
我们采用上面方法在 debug 状态下读取了一下 DisplayInfo 的内容:
Display id 0: DisplayInfo{"内置屏幕", App 1280 x 720, real 1280 x 720, largest App 1280 x 1255, smallest App 720 x 695, 50.0 fps, rotation0, density 160 (160.0 x 160.0) dpi, layerStack 0, type BUILT_IN, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}, DisplayMetrics{density=1.0, width=1280, height=720, scaledDensity=1.0, xdpi=160.0, ydpi=160.0}, isValid=true
反射后获取, 发现并没有 overscan 的值, 很遗憾四个值都是 0. 可是我测试的电视是有 overscan 的. 代码写错了?
我采用命令来设置 overscan 的内容:
adb shell wm overscan 10,20,30,40
分别设置 overscan 的值为 10, 20, 30, 40, 之后我再一次获取 DisplayInfo 的值, 内容如下:
Display id 0: DisplayInfo{"内置屏幕", App 1280 x 720, real 1280 x 720, overscan (10,20,30,40), largest App 1280 x 1255, smallest App 720 x 695, 50.0 fps, rotation0, density 160 (160.0 x 160.0) dpi, layerStack 0, type BUILT_IN, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}, DisplayMetrics{density=1.0, width=1280, height=720, scaledDensity=1.0, xdpi=160.0, ydpi=160.0}, isValid=true
这里可以看到明确有 overscan 的内容, 并且就是我们命令设置的值, 这就尴尬了, 系统默认值获取不到, 手动设置可以获取. 希望之路破灭.
来源: https://www.cnblogs.com/163yun/p/9711834.html