Android自定义ViewGroup(自定义LayoutParams)

原文链接:https://blog.csdn.net/ldld1717/article/details/80458917

2. 自定义LayoutParams

回想一下我们平时使用RelativeLayout的时候,在布局文件中使用android:layout_alignParentRight=“true”、android:layout_centerInParent="true"等各种属性,就能控制子控件显示在父控件的上下左右、居中等效果。
  在View中有一个mLayoutParams的变量用来保存这个View的所有布局属性。ViewGroup.LayoutParams有两个属性layout_width和layout_height,因为所有的容器都需要设置子控件的宽高,所以这个LayoutParams是所有布局参数的基类,如果需要扩展其他属性,都应该继承自它。比如RelativeLayout中就提供了它自己的布局参数类RelativeLayout.LayoutParams,并扩展了很多布局参数,我们平时在RelativeLayout中使用的布局属性都来自它 :

<declare-styleable name= "RelativeLayout_Layout">
        <attr name ="layout_toLeftOf" format= "reference" />
        <attr name ="layout_toRightOf" format= "reference" />
        <attr name ="layout_above" format="reference" />
        <attr name ="layout_below" format="reference" />
        <attr name ="layout_alignBaseline" format= "reference" />
        <attr name ="layout_alignLeft" format= "reference" />
        <attr name ="layout_alignTop" format= "reference" />
        <attr name ="layout_alignRight" format= "reference" />
        <attr name ="layout_alignBottom" format= "reference" />
        <attr name ="layout_alignParentLeft" format= "boolean" />
        <attr name ="layout_alignParentTop" format= "boolean" />
        <attr name ="layout_alignParentRight" format= "boolean" />
        <attr name ="layout_alignParentBottom" format= "boolean" />
        <attr name ="layout_centerInParent" format= "boolean" />
        <attr name ="layout_centerVertical" format= "boolean" />
        <attr name ="layout_alignWithParentIfMissing" format= "boolean" />
        <attr name ="layout_toStartOf" format= "reference" />
        <attr name ="layout_toEndOf" format="reference" />
        <attr name ="layout_alignStart" format= "reference" />
        <attr name ="layout_alignEnd" format= "reference" />
        <attr name ="layout_alignParentStart" format= "boolean" />
        <attr name ="layout_alignParentEnd" format= "boolean" />
    </declare-styleable >

看了上面的介绍,我们大概知道怎么为我们的布局容器定义自己的布局属性了吧,就不绕弯子了,按照下面的步骤做:

①. 大致明确布局容器的需求,初步定义布局属性

在定义属性之前要弄清楚,我们自定义的布局容器需要满足那些需求,需要哪些属性,比如,我们现在要实现像相对布局一样,为子控件设置一个位置属性layout_position=”“,来控制子控件在布局中显示的位置。暂定位置有五种:左上、左下、右上、右下、居中。有了需求,我们就在attr.xml定义自己的布局属性(和之前讲的自定义属性一样的操作,不太了解的可以翻阅 《深入解析自定义属性》。

<?xml version="1.0" encoding= "utf-8"?>
<resources> 
    <declare-styleable name ="CustomLayout">
    <attr name ="layout_position">
        <enum name ="center" value="0" />
        <enum name ="left" value="1" />
        <enum name ="right" value="2" />
        <enum name ="bottom" value="3" />
        <enum name ="rightAndBottom" value="4" />
    </attr >
    </declare-styleable>
</resources>

left就代表是左上(按常理默认就是左上方开始,就不用写leftTop了,简洁一点),bottom左下,right 右上,rightAndBottom右下,center居中。属性类型是枚举,同时只能设置一个值。

②. 继承LayoutParams,定义布局参数类  
我们可以选择继承ViewGroup.LayoutParams,这样的话我们的布局只是简单的支持layout_width和layout_height;也可以继承MarginLayoutParams,就能使用layout_marginxxx属性了。因为后面我们还要用到margin属性,所以这里方便起见就直接继承MarginLayoutParams了。
  覆盖构造方法,然后在有AttributeSet参数的构造方法中初始化参数值,这个构造方法才是布局文件被映射为对象的时候被调用的。
```
public static class CustomLayoutParams extends MarginLayoutParams {
public static final int POSITION_MIDDLE = 0; // 中间
public static final int POSITION_LEFT = 1; // 左上方
public static final int POSITION_RIGHT = 2; // 右上方
public static final int POSITION_BOTTOM = 3; // 左下角
public static final int POSITION_RIGHTANDBOTTOM = 4; // 右下角

   public int position = POSITION_LEFT;  // 默认我们的位置就是左上角

   public CustomLayoutParams(Context c, AttributeSet attrs) {
         super(c, attrs);
        TypedArray a = c.obtainStyledAttributes(attrs,R.styleable.CustomLayout );
         //获取设置在子控件上的位置属性
         position = a.getInt(R.styleable.CustomLayout_layout_position ,position );

        a.recycle();
  }

   public CustomLayoutParams( int width, int height) {
         super(width, height);
  }

   public CustomLayoutParams(ViewGroup.LayoutParams source) {
         super(source);
  }

}
```

③. 重写generateLayoutParams()
  在ViewGroup中有下面几个关于LayoutParams的方法,generateLayoutParams (AttributeSet attrs)是在布局文件被填充为对象的时候调用的,这个方法是下面几个方法中最重要的,如果不重写它,我么布局文件中设置的布局参数都不能拿到。后面我也会专门写一篇博客来介绍布局文件被添加到activity窗口的过程,里面会讲到这个方法被调用的来龙去脉。其他几个方法我们最好也能重写一下,将里面的LayoutParams换成我们自定义的CustomLayoutParams类,避免以后会遇到布局参数类型转换异常。

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new CustomLayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    return new CustomLayoutParams (p);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
    return new CustomLayoutParams (LayoutParams.MATCH_PARENT , LayoutParams.MATCH_PARENT);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof CustomLayoutParams ;
}

④. 在布局文件中使用布局属性 
注意引入命名空间xmlns:openxu= “http://schemas.android.com/apk/res/包名”

<?xml version="1.0" encoding= "utf-8"?>
<com.openxu.costomlayout.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:openxu= "http://schemas.android.com/apk/res/com.openxu.costomlayout"
    android:background="#33000000"
    android:layout_width= "match_parent "
    android:layout_height= "match_parent" >
 
    <Button
        android:layout_width= "wrap_content"
        android:layout_height= "wrap_content"
        openxu:layout_position= "left"
        android:background= "#FF8247"
        android:textColor= "#ffffff"
         android:textSize="20dip"
        android:padding= "20dip"
        android:text= "按钮1" />
 
    <Button
        android:layout_width= "wrap_content"
        android:layout_height= "wrap_content"
        openxu:layout_position= "right"
        android:background= "#8B0A50"
        android:textColor= "#ffffff"
        android:textSize= "18dip"
        android:padding= "10dip"
        android:text= "按钮2222222222222" />
 
    <Button
        android:layout_width= "wrap_content"
        android:layout_height= "wrap_content"
        openxu:layout_position= "bottom"
        android:background= "#7CFC00"
        android:textColor= "#ffffff"
        android:textSize= "20dip"
        android:padding= "15dip"
        android:text= "按钮333333" />
 
    <Button
        android:layout_width= "wrap_content"
        android:layout_height= "wrap_content"
        openxu:layout_position= "rightAndBottom"
        android:background= "#1E90FF"
        android:textColor= "#ffffff"
        android:textSize= "15dip"
        android:padding= "10dip"
        android:text= "按钮4" />
 
    <Button
        android:layout_width= "wrap_content"
        android:layout_height= "wrap_content"
        openxu:layout_position= "center"
        android:background= "#191970"
        android:textColor= "#ffffff"
        android:textSize= "20dip"
        android:padding= "15dip"
        android:text= "按钮5" />
 
</com.openxu.costomlayout.CustomLayout>

⑤. 在onMeasure和onLayout中使用布局参数
  经过上面几步之后,我们运行程序,就能获取子控件的布局参数了,在onMeasure方法和onLayout方法中,我们按照自定义布局容器的特殊需求,对宽度和位置坐特殊处理。这里我们需要注意一下,如果布局容器被设置为包裹类容,我们只需要保证能将最大的子控件包裹住就ok,代码注释比较详细,就不多说了。

 @Override
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { 
  //获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式  
 int widthMode = MeasureSpec. getMode(widthMeasureSpec); 
 int heightMode = MeasureSpec. getMode(heightMeasureSpec); 
 int sizeWidth = MeasureSpec. getSize(widthMeasureSpec); 
 int sizeHeight = MeasureSpec. getSize(heightMeasureSpec); 
 int layoutWidth = 0;
 int layoutHeight = 0;
      // 计算出所有的childView的宽和高
     measureChildren(widthMeasureSpec, heightMeasureSpec);
 
      int cWidth = 0;
      int cHeight = 0;
      int count = getChildCount(); 
 
      if(widthMode == MeasureSpec. EXACTLY){
            //如果布局容器的宽度模式是确定的(具体的size或者match_parent),直接使用父窗体建议的宽度
           layoutWidth = sizeWidth;
     } else{
            //如果是未指定或者wrap_content,我们都按照包裹内容做,宽度方向上只需要拿到所有子控件中宽度做大的作为布局宽度
            for ( int i = 0; i < count; i++)  { 
                  View child = getChildAt(i); 
              cWidth = child.getMeasuredWidth(); 
              //获取子控件最大宽度
              layoutWidth = cWidth > layoutWidth ? cWidth : layoutWidth;
           }
     }
      //高度很宽度处理思想一样
      if(heightMode == MeasureSpec. EXACTLY){
           layoutHeight = sizeHeight;
     } else{
            for ( int i = 0; i < count; i++)  { 
                  View child = getChildAt(i); 
                  cHeight = child.getMeasuredHeight();
                  layoutHeight = cHeight > layoutHeight ? cHeight : layoutHeight;
           }
     }
 
      // 测量并保存layout的宽高
     setMeasuredDimension(layoutWidth, layoutHeight);
}
 
@Override
protected void onLayout( boolean changed, int left, int top, int right,
            int bottom) {
      final int count = getChildCount();
      int childMeasureWidth = 0;
      int childMeasureHeight = 0;
     CustomLayoutParams params = null;
      for ( int i = 0; i < count; i++) {
           View child = getChildAt(i);
            // 注意此处不能使用getWidth和getHeight,这两个方法必须在onLayout执行完,才能正确获取宽高
           childMeasureWidth = child.getMeasuredWidth();
           childMeasureHeight = child.getMeasuredHeight();
 
           params = (CustomLayoutParams) child.getLayoutParams(); 
     switch (params. position) {
            case CustomLayoutParams. POSITION_MIDDLE:    // 中间
                 left = (getWidth()-childMeasureWidth)/2;
                 top = (getHeight()-childMeasureHeight)/2;
                  break;
            case CustomLayoutParams. POSITION_LEFT:      // 左上方
                 left = 0;
                 top = 0;
                  break;
            case CustomLayoutParams. POSITION_RIGHT:     // 右上方
                 left = getWidth()-childMeasureWidth;
                 top = 0;
                  break;
            case CustomLayoutParams. POSITION_BOTTOM:    // 左下角
                 left = 0;
                 top = getHeight()-childMeasureHeight;
                  break;
            case CustomLayoutParams. POSITION_RIGHTANDBOTTOM:// 右下角
                 left = getWidth()-childMeasureWidth;
                 top = getHeight()-childMeasureHeight;
                  break;
            default:
                  break;
           }
 
            // 确定子控件的位置,四个参数分别代表(左上右下)点的坐标值
           child.layout(left, top, left+childMeasureWidth, top+childMeasureHeight);
     }
}

运行效果:
  下面几个效果分别对应布局容器宽高设置不同的属性的情况(设置match_parent 、设置200dip、设置):

从运行结果看,我们自定义的布局容器在各种宽高设置下都能很好的测量大小和摆放子控件。现在我们让他支持margin属性

点击下方链接免费获取Android进阶资料:
https://shimo.im/docs/tXXKHgdjPYj6WT8d/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
Android自定义ViewGroup是指在Android开发中,通过继承ViewGroup类来创建自定义的布局容器。自定义ViewGroup可以用于实现一些特殊的布局效果,比如侧滑菜单、滑动卡片等等。通过自定义ViewGroup,我们可以更灵活地控制子视图的布局和交互行为,以满足特定的需求。自定义ViewGroup的实现主要包括重写onMeasure()方法和onLayout()方法,来测量和布局子视图。同时,我们还可以通过重写onInterceptTouchEvent()方法和onTouchEvent()方法来处理触摸事件,实现自定义的交互效果。如果你对自定义ViewGroup还不是很了解,或者正想学习如何自定义,可以参考相关的教程和文档,如引用\[1\]和引用\[2\]所提到的博客和官方文档。 #### 引用[.reference_title] - *1* [Android 手把手教您自定义ViewGroup(一)](https://blog.csdn.net/iteye_563/article/details/82601716)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [使用LayoutParams自定义安卓ViewGroup](https://blog.csdn.net/lfq88/article/details/127268493)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Android自定义ViewGroup](https://blog.csdn.net/farsight2009/article/details/62046643)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值