有时候因为项目需求,不能在布局文件里直接设置View的宽高等属性为固定值时,常常需要在Activity或Fragment里动态地设置。但是直接在onCreate里通过View.getWidth()、View.getHeight()或View.geLeft()等方法获取View的属性值时,获取到的值却为0。
这是因为Activity的启动流程和Activity的布局文件加载绘制流程其实是两个异步的过程,虽然Activity启动后你可以看到界面的显示,但是其组件尚未完成绘制流程,因此获取到的值为默认值0。
遇到这种情况,通常可以通过View的观察者进行绘制时的监听,在其回调方法中进行值的获取和传递。
下面看一个例子:
要实现顶部Tab滑动选项的底部标识(即白色横条部分)的宽度等于中间一个tab选项的宽度,并且其开始横坐标也要在对应tab选项的底部,但我们一开始不知道一个tab选项的宽度具体是多少,所以底部标识的宽度也不确定,不能在布局文件中直接指定,需要在Activity中根据其他View的属性来确定其宽度值和开始横坐标的值。目标实现效果如下图:
布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/greyBg">
<RelativeLayout
android:id="@+id/rel_top"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="47dp"
android:background="@color/greenBg">
<LinearLayout
android:id="@+id/lin_center_tab"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:layout_centerHorizontal="true">
<TextView
android:id="@+id/txt_tab_hot"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="热门"
android:textColor="@color/whiteBg"
android:textSize="16sp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"/>
<TextView
android:id="@+id/txt_tab_focus"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="关注"
android:textColor="@color/whiteBg"
android:textSize="16sp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"/>
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/lin_bottom_sign"
android:layout_width="match_parent"
android:layout_height="3dp"
android:orientation="horizontal"
android:background="@color/greenBg">
<ImageView
android:id="@+id/img_bottom_sign"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</LinearLayout>
在布局文件中,两个TextView的宽度没有直接给定,其父布局LinearLayout的宽度和左边距也不确定。
public class MainActivity extends Activity {
//标题栏中间tab布局区域,即两个TextView的父布局
private LinearLayout mLinCenterTab;
//标题栏底部白色滑动标识图片控件的声明
private ImageView mImgBottomSign;
//标识图片的开始横坐标
private int cursorX = 0;
//是否测量过布局区域
private boolean isMeasured = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
initView();
}
/**
* 控件初始化
*/
private void initView() {
mLinCenterTab = (LinearLayout) findViewById(R.id.lin_center_tab);
mImgBottomSign = (ImageView) findViewById(R.id.img_bottom_sign);
//设置标识控件的背景颜色,区别于标题栏背景
mImgBottomSign.setBackgroundColor(getResources().getColor(R.color.whiteBg));
setImgWidthAndLeftMargin(mLinCenterTab.getWidth(),mLinCenterTab.getLeft());
}
/**
* 设置标识图片的宽度和开始横坐标
* @param width 宽度
* @param leftMargin 开始横坐标
*/
private void setImgWidthAndLeftMargin(int width,int leftMargin) {
//获取到标识控件的布局参数对象
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mImgBottomSign.getLayoutParams();
//设置布局参数的宽度
layoutParams.width = width;
//设置标识控件的布局参数,将宽度值通过布局参数传递给控件
mImgBottomSign.setLayoutParams(layoutParams);
Log.d("ImgWidth","ImgWidth:" + mImgBottomSign.getWidth());
//设置标识控件的开始横坐标
cursorX = leftMargin;
mImgBottomSign.setX(cursorX);
}
}
要让标识图片的宽度为一个tab选项的宽度,直接将mLinCenterTab的宽度值的一半和左边距传递到setImgWidthAndLeftMargin()方法里,得到的效果如下图:
虽然Activity已经启动,我们也能看到界面已经显示出来了,但其布局文件的绘制流程还没有完成,所以传入setImgWidthAndLeftMargin()的值其实都是默认值0,所以标识图片的宽度从布局文件中设置的match_parent变成0,在界面上就看不到了。
解决方法:通过View的观察者进行监听
修改initView()的代码,如下所示:
/**
* 控件初始化
*/
private void initView() {
mLinCenterTab = (LinearLayout) findViewById(R.id.lin_center_tab);
mImgBottomSign = (ImageView) findViewById(R.id.img_bottom_sign);
mImgBottomSign.setBackgroundColor(getResources().getColor(R.color.whiteBg));
//设置标识控件的背景颜色,区别于标题栏背景
ViewTreeObserver treeObserver = (ViewTreeObserver) mLinCenterTab.getViewTreeObserver();
treeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
//如果还没测量过,则进行测量并传值
if(!isMeasured) {
setImgWidthAndLeftMargin(mLinCenterTab.getMeasuredWidth() / 2,mLinCenterTab.getLeft());
isMeasured = true;
}
return true;
}
});
}
treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
setImgWidthAndLeftMargin(mLinCenterTab.getMeasuredWidth() / 2,mLinCenterTab.getLeft());
}
});
源码下载链接:ViewTreeObserverTest