Android适配UI动态设置View视图控件的间距

一、动态设置View视图控件的间距

如下布局,要动态控制TextView距离顶部的间距:
1、需求:

RelativeLayout有一个背景宽高为1635*1029px的图片,需要TextView控制显示在背景图片距离顶部150px的位置处.

<RelativeLayout
    android:id="@+id/rlt_left"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:background="@mipmap/detect_bg_pic01">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/detect_margin_title_top"
        android:background="@mipmap/detect_bg_tab"
        android:gravity="top|center_horizontal"
        android:text="@{vm.currentStepTitle}"
        android:textColor="@color/white"
        android:textSize="16sp" />
</RelativeLayout>
2、分析:

1、RelativeLayout宽高自适应,所以会等比例缩放

2、获取RelativeLayout的实际高度

3、计算TextView距离顶部背景图片150px的实际间距

4、动态设置TextView距离顶部的间距

3、实现:
//...

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //...
        //获取RelativeLayout的实际高度
        int rltLeftHeight = rltLeft.getHeight();
        
        //计算TextView距离顶部背景图片150px的实际间距
        int topmargin = (rltLeftHeight * 150) / 1029;
        
        //动态设置TextView距离顶部的间距
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) rltLeft.getLayoutParams();
        layoutParams.setMargins(0,topmargin,0,0);
        tvTitle.setLayoutParams(layoutParams);
        
}
//...
4、动态设置margin(外边距) & pading(内边距)
a、padding
view.setPadding(int left, int top, int right, int bottom);

查看源码可以定位到setPadding是在View.class中,设置单位是px,in pixels表示单位px

/**
 * Sets the padding. The view may add on the space required to display
 * the scrollbars, depending on the style and visibility of the scrollbars.
 * So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop},
 * {@link #getPaddingRight} and {@link #getPaddingBottom} may be different
 * from the values set in this call.
 *
 * @attr ref android.R.styleable#View_padding
 * @attr ref android.R.styleable#View_paddingBottom
 * @attr ref android.R.styleable#View_paddingLeft
 * @attr ref android.R.styleable#View_paddingRight
 * @attr ref android.R.styleable#View_paddingTop
 * @param left the left padding in pixels
 * @param top the top padding in pixels
 * @param right the right padding in pixels
 * @param bottom the bottom padding in pixels
 */
public void setPadding(int left, int top, int right, int bottom) {
}
b、margin
LayoutParams lp = (LayoutParams) view.getLayoutParams();
lp.setMargins(int left, int top, int right, int bottom);

setMargin是在ViewGroup.class中,单位也是像素px,setMargin是MarginLayoutParams.class内部类的。

/**
 * Sets the margins, in pixels. A call to {@link android.view.View#requestLayout()} needs
 * to be done so that the new margins are taken into account. Left and right margins may be
 * overridden by {@link android.view.View#requestLayout()} depending on layout direction.
 * Margin values should be positive.
 *
 * @param left the left margin size
 * @param top the top margin size
 * @param right the right margin size
 * @param bottom the bottom margin size
 *
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
 */
public void setMargins(int left, int top, int right, int bottom) {
}

margin设置话,要注意view.getLayoutParams()是需要强制转换的,要看view的父元素是什么容器。如上面的处理方案,TextView的父容器是RelativeLayout,所以:

RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) rltLeft.getLayoutParams();
layoutParams.setMargins(0,topmargin,0,0);
tvTitle.setLayoutParams(layoutParams);

二、获取 View 宽高的常用正确用法

1、问题:

根据如上用法,Activity 中在 onCreate()或onResume() 生命周期方法中 通过view.getWidth()或view.getHeight() 方法获取的宽高值都是0。

2、分析:

我们知道View的绘制流程主要分为三步:

onMeasure:

测量视图的大小,从顶层父View到子View递归调用measure()方法,measure()调用onMeasure()方法,onMeasure()方法完成绘制工作。

onLayout:

确定视图的位置,从顶层父View到子View递归调用layout()方法,父View将上一步measure()方法得到的子View的布局大小和布局参数,将子View放在合适的位置上。

onDraw:

绘制最终的视图,首先ViewRoot创建一个Canvas对象,然后调用onDraw()方法进行绘制。

onDraw()方法的绘制流程为:
  • ① 绘制视图背景。

  • ② 绘制画布的图层。

  • ③ 绘制View内容。

  • ④ 绘制子视图,如果有的话。

  • ⑤ 还原图层。

  • ⑥ 绘制滚动条。

所以,当我们在 Activity 的生命周期方法中直接获取 View 的宽高时,View 可能还没执行完 measure 阶段,获取到的宽高结果就会为 0。

3、获取到View的宽和高的正确方案
1、View的post(Runnable runnalbe)方法
view.post(new Runnable() {
    @Override
    public void run() {
        int width = view.getWidth();
    }
});

该方法添加一个Runnable 操作到队列末尾,当等到View attachToWindow时调用。,当View attachToWindow,已经进行了onMeasure()和onLayout()所以可以获取到宽高。

2、监听Viewde ViewTreeObserver的ondraw&onLayout变化

a、利用 ViewTreeObserver 观察者类,监听draw 事件

View将要绘制时,已经经过了onMeasure(),onLayout(),所以可以获取到 View的宽和高。onPreDrawListener可能会被回调多次,所以要注意移除。

view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        Log.e(TAG, "onCreate: "+view.getWidth()+","+view.getHeight());
        Log.e(TAG, "onCreate: "+view.getMeasuredWidth()+","+view.getMeasuredHeight());
        //移除监听
        view.getViewTreeObserver().removeOnPreDrawListener(this);
        return false;
    }
});  

b、利用 ViewTreeObserver 观察者类,监听layout 事件

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        if (Build.VERSION.SDK_INT >= 16) {
            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }else {
            view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
        int width = view.getWidth();
    }
});
3、Activity的onWindowFocusChanged(boolean hasFocus)方法中获取view宽高

onWindowFocusChanged(boolean hasFocus)会是在Activity当前Window焦点改变时,会回调这个方法,当Activity的获取到焦点时,布局的View已经完成了onMeasure()测量和onLayout()布局,可以正确获取到View的宽和高。

  @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        int width = view.getWidth();
        int height = view.getHeight();
    }

以上方案,View.post方案获取 View 的宽高属性,相比 ViewTreeObserver 监听处理,还不需要手动移除观察者监听事件,代码更简洁,使用也简单。

4、getWidth()与getMeasuredWidth() getHeight()与getMeasuredHeight()区分

getMeasuredWidth()、getMeasuredHeight()获取的是view原始的大小,即这个view在XML文件中配置或者是代码中设置的大小。

getWidth()、getHeight()获取的是这个view最终显示的大小,即有可能等于原始的大小也有可能不等于原始大小。

< END >【Android进化之路】
【Android进化之路】
微信扫描二维码,关注我的公众号。 
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值