Android开发技巧——实现设计师给出的视觉居中的布局
本篇主要是对自定义控件的测量方法(
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
)在实际场景中的运用。
在移动应用的设计中,经常有这样的界面:某个界面的元素非常少,比如空列表界面,或者某某操作成功的界面,只有一两个元素在中间。但是它们在某个布局里又不是数学上的那个居中,而是经过设计师调出来的“视觉居中”。这种“视觉居中”内部是怎么计算的,我大致也不懂,反正结果就是设计师们看起来要显示的信息给人有感觉是在中间的(通常是比中间偏上一点)。
既是这样,那我们在布局中就不能用gravity="center"
或layout_gravity="center"
等这样的属性来设置了。而使用固定的padding或margin来调整它的位置,也难以在不同的屏幕分辨率中实现同样的效果,那就只好按钮设计图的标注,按比例来计算它的位置了。
按比例来调整子view与layout中的距离,在约束布局(ConstraintLayout)中是可以做到的,但是在我个人看来相对这样简单的需求,约束布局有点重了,并且它的依赖在不同方式的编译下总是很容易出问题(比如在自己电脑编译通过,在travis-ci上编译就提示找不到该库的某个版本),还有拖拽生成的代码格式不是很整齐(我有代码洁癖),总需要自己再去格式化一下代码。那么自定义实现一下也是可以的嘛。
首先像这样简单的界面,通常来说,使用LinearLayout
就足够了。我们所需要的只是按比例计算出padding然后设进去,那么内容就能够按我们想要的位置去显示了。在这里,我把这个布局定义为PaddingWeightLinearLayout
,即可以按权重来设计它的padding。它提供了四个属性:
<declare-styleable name="PaddingWeightLinearLayout">
<attr name="ppLeftPadding" format="integer"/>
<attr name="ppRightPadding" format="integer"/>
<attr name="ppTopPadding" format="integer"/>
<attr name="ppBottomPadding" format="integer"/>
</declare-styleable>
分别代表每个方向上的padding的权重。
在构造方法里获取这些属性的值:
private final int mTopWeight;
private final int mBottomWeight;
private final int mLeftWeight;
private final int mRightWeight;
public PaddingWeightLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PaddingWeightLinearLayout);
mTopWeight = ta.getInteger(R.styleable.PaddingWeightLinearLayout_ppTopPadding, 0);
mBottomWeight = ta.getInteger(R.styleable.PaddingWeightLinearLayout_ppBottomPadding, 0);
mLeftWeight = ta.getInteger(R.styleable.PaddingWeightLinearLayout_ppLeftPadding, 0);
mRightWeight = ta.getInteger(R.styleable.PaddingWeightLinearLayout_ppRightPadding, 0);
ta.recycle();
}
那么接下来,我们只需要计算出所有子View所占的空间,然后计算出水平和竖直方向上剩余的空间,按比例分给这四个padding就可以了。之所以使用LinearLayout是因为它是线性布局,子View按线性排列,比较利于计算。如下图(电脑的图画不好,献丑了):
图1表示的是水平方向的布局,那么它的内容所占的大小是:
- 宽度为所有子View的宽度加上其左右Margin的总和。
- 高度为子View高度加上其上下Margin中最高的一个。
图2 是竖直方向的布局,它的内容所占的大小是:
- 宽度为子View宽度加上其左右Margin中最大的一个。
- 高度为所有子View的高度加上其上下Margin的总和。
因此,我们要先测量出子View的大小,然后再进行计算。
测试是在onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法中进行的。其中参数分别表示父布局能够提供给它的空间。这个int类型的参数分为两部分,高2位表示的是模式(mode),后面的30位表示的是具体的大小。这里的mode一共有三个:
-
UNSPECIFIED
父View对子View没有任何约束,可以随意指定大小 -
EXACTLY
父View给子View一个固定的大小,子View会被这些边界限制,不管它自己想要多大 -
AT_MOST
子视图的大小可以是自己想要的值,但是不能超过指定的值
当我们要计算子View时,我们需要调用子View的measure(widthMeasureSpec, int heightMeasureSpec)
方法,为了能够得到子View的确定大小,我们需要将widthMeasureSpec
mode
指定为AT_MOST
,代码如下(以下代码都是在onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法内):
int layoutWidth = MeasureSpec.getSize(widthMeasureSpec);
int layoutHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthSpec = MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.AT_MOST);
int heightSpec = MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.AT_MOST);
然后遍历所有子View,计算子View宽高的总和及最大值:
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
child.measure(widthSpec, heightSpec);
LayoutParams params = (LayoutParams) child.getLayoutParams();
int width = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
int height = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
totalWidth += width;
totalHeight += height;
if (width > maxWidth) {
maxWidth = width;
}
if (height > maxHeight) {
maxHeight = height;
}
}
然后计算出在水平及竖直方向上剩余的空间:
int spaceHorizontal;
int spaceVertical;
if (getOrientation() == VERTICAL) {
spaceHorizontal = layoutWidth - maxWidth;
spaceVertical = layoutHeight - totalHeight;
} else {
spaceHorizontal = layoutWidth - totalWidth;
spaceVertical = layoutHeight - maxHeight;
}
if (spaceHorizontal < 0) {
spaceHorizontal = 0;
}
if (spaceVertical < 0) {
spaceVertical = 0;
}
最后算出各个方向的padding,设置进去,然后重新调用父类的onMeasure(int, int)
方法:
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int horizontalWeight = mLeftWeight + mRightWeight;
if (spaceHorizontal > 0 && horizontalWeight > 0) {
paddingLeft = mLeftWeight * spaceHorizontal / horizontalWeight;
paddingRight = spaceHorizontal - paddingLeft;
}
int verticalWeight = mTopWeight + mBottomWeight;
if (spaceVertical > 0 && verticalWeight > 0) {
paddingTop = mTopWeight * spaceVertical / verticalWeight;
paddingBottom = spaceVertical - paddingTop;
}
setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
下面我们只需要在写布局代码的时候,按照设计图填入标注的padding值,就可以按比例计算出内边距并设置,从而让我们的内容按照比例位置显示了:
<com.parkingwang.widget.PaddingWeightLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical"
android:paddingLeft="@dimen/screen_padding"
android:paddingRight="@dimen/screen_padding"
app:ppBottomPadding="287"
app:ppTopPadding="85">
... 内容布局代码
</com.parkingwang.widget.PaddingWeightLinearLayout>
下面分別是实现的效果以及设计图,可以看到,在内容区域它们所在的位置是相同的(由于屏幕分辨率的关系,大小会有微小差异)。
完整代码请参见Github项目:https://github.com/msdx/androidsnippet

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
使用Xposed强制android WebView开启debug模式
使用Xposed强制android WebView开启debug模式 从 https://developer.chrome.com/devtools/docs/remote-debugging 我们可以知道在android 4.4+可以通过在apk中使用下面的代码开启webview的chrome远程调试 WebView.setWebContentsDebuggingEnabled(true); 但我们开发中接触的apk往往是第三方的,没谁会为我们开启webContentsDebuggingEnabled。而Xposed能强制做到这一点 Xposed Xposed能够勾住(Hook) Android应用程序对象的方法,实现AOP,一个简单的例子: public class WebViewHook implements IXposedHookLoadPackage { // handleLoadPackage 会在android加载每一个apk后执行 public void handleLoadPackage(LoadPackageParam lpparam) throws Throwab...
-
下一篇
xamarin android toolbar(踩坑完全入门详解)
网上关于toolbar的教程有很多,很多新手,在使用toolbar的时候踩坑实在太多了,不好好总结一下,实在浪费。如果你想学习toolbar,你肯定会去去搜索androd toolbar,既然你能看到这篇文章,说明还是搜了xamarin android toolbar。那么这篇文章就好好总结一下toolbar在xamarin android中如何使用,减少大家踩坑的时间。 了解Toolbar android3.0推了ActionBar这个控件,android5.0开始推出Materal Design,其中就有ToolBar控件。可能官方觉得ActionBar限制app开发设计的弹性,google非常推荐大家使用ToolBar来作为客户端的导航栏,以此来取代ActionBar。 ToolBar使用更灵活,设计更多样性。主要的优点有1.可以设置导航栏图标、Logo、标题和子标题等属性; 2可添加自定义控件;3.支持Action Menu;4.可随意设置位置 xamarin android中使用Toolbar 官方为了将toolbar这一控件向下兼容,推出了兼容版的toolbar,所以首先的...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- MySQL数据库在高并发下的优化方案
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- Docker使用Oracle官方镜像安装(12C,18C,19C)