在Launcher加载的过程中会动态的根据当前设备的屏幕参数初始化DynamicGrid,DynamicGrid中只包含了三个变量mMinWidth(单位DP),mMinHeight(单位DP),mProfile。动态适配的逻辑以及适配后的属性值都在DeviceProfile中。
Launcher加载过程中调用LauncherAppState的initDynamicGrid函数。
protected void onCreate(Bundle savedInstanceState) {
......
// Lazy-initialize the dynamic grid
DeviceProfile grid = app.initDynamicGrid(this,
Math.min(smallestSize.x, smallestSize.y),
Math.min(largestSize.x, largestSize.y),
realSize.x, realSize.y,
dm.widthPixels, dm.heightPixels);
......
}
在initDynamicGrid中初始化DynamicGrid,初始化的过程会创建一系列的DeviceProfile并保存到集合中作为适配参数。
public DynamicGrid(Context context, Resources resources,
int minWidthPx, int minHeightPx,
int widthPx, int heightPx,
int awPx, int ahPx) {
DisplayMetrics dm = resources.getDisplayMetrics();
ArrayList<DeviceProfile> deviceProfiles =
new ArrayList<DeviceProfile>();
boolean hasAA = !AppsCustomizePagedView.DISABLE_ALL_APPS;
// Our phone profiles include the bar sizes in each orientation
deviceProfiles.add(new DeviceProfile("Super Short Stubby",
255, 300, 2, 3, 48, 13, (hasAA ? 5 : 4), 48));
deviceProfiles.add(new DeviceProfile("Shorter Stubby",
255, 400, 3, 3, 48, 13, (hasAA ? 5 : 4), 48));
deviceProfiles.add(new DeviceProfile("Short Stubby",
275, 420, 3, 4, 48, 13, (hasAA ? 5 : 4), 48));
deviceProfiles.add(new DeviceProfile("Stubby",
255, 450, 3, 4, 48, 13, (hasAA ? 5 : 4), 48));
deviceProfiles.add(new DeviceProfile("Nexus S",
296, 491.33f, 4, 4, 48, 13, (hasAA ? 5 : 4), 48));
deviceProfiles.add(new DeviceProfile("Nexus 4",
359, 518, 4, 4, 60, 13, (hasAA ? 5 : 4), 56));
........
mMinWidth = dpiFromPx(minWidthPx, dm);
mMinHeight = dpiFromPx(minHeightPx, dm);
mProfile = new DeviceProfile(context, deviceProfiles,
mMinWidth, mMinHeight,
widthPx, heightPx,
awPx, ahPx,
resources);
}
在DeviceProfile的构造函数中根据参数进行适配的具体工作。
DeviceProfile(....) {
DisplayMetrics dm = resources.getDisplayMetrics();
ArrayList<DeviceProfileQuery> points =
new ArrayList<DeviceProfileQuery>();
transposeLayoutWithOrientation =
resources.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
minWidthDps = minWidth;
minHeightDps = minHeight;
ComponentName cn = new ComponentName(context.getPackageName(),
this.getClass().getName());
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
edgeMarginPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
pageIndicatorHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
// Interpolate the rows
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
}
numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
// Interpolate the columns
points.clear();
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
}
numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
// Interpolate the icon size
points.clear();
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
}
iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
iconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
......
}
1.初始化包括边距,分页图标高度等在dimen中定义的基础数据。
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
edgeMarginPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
pageIndicatorHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
2.计算行数,列数,图标大小等
// Interpolate the rows
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
}
numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
// Interpolate the columns
points.clear();
计算的核心方法为invDistWeightedInterpolate,其计算原则为:遍历DeviceProfileQuery的集合,其宽高与当前屏幕的宽高最接近的元素权重越高,最终结果越接近该DeviceProfileQuery的value.
根据给定的minWidth,minHeight创建一个基准Point xy,并根据与基准点的坐标由近及远对points进行重新排序。
private float invDistWeightedInterpolate(float width, float height,
ArrayList<DeviceProfileQuery> points) {
float sum = 0;
float weights = 0;
float pow = 5;
float kNearestNeighbors = 3;
final PointF xy = new PointF(width, height);
ArrayList<DeviceProfileQuery> pointsByNearness = points;
//根据两点距离由小到大排序
Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
}
});
//只取前三个值进行加权
for (int i = 0; i < pointsByNearness.size(); ++i) {
DeviceProfileQuery p = pointsByNearness.get(i);
if (i < kNearestNeighbors) {
float w = weight(xy, p.dimens, pow);
if (w == Float.POSITIVE_INFINITY) {
return p.value;
}
weights += w;
}
}
//取前三个值,并根据其权重计算最终值
for (int i = 0; i < pointsByNearness.size(); ++i) {
DeviceProfileQuery p = pointsByNearness.get(i);
if (i < kNearestNeighbors) {
float w = weight(xy, p.dimens, pow);
sum += w * p.value / weights;
}
}
return sum;
}
//权重为两点的距离5次方分之一,则距离大的点对结果影响急剧变小
private float weight(PointF a, PointF b,
float pow) {
float d = dist(a, b);
if (d == 0f) {
return Float.POSITIVE_INFINITY;
}
return (float) (1f / Math.pow(d, pow));
}
最终将invDistWeightedInterpolate方法返回的结果四舍五入后就得到最终值。行数,列数,Icon的size等计算方式相同。
经过初始化后,该DeviceProfile即为定义了整个app的用户界面展示方式。在应用的整个生命周期的各个环节都可以看到它的身影。
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();