zoukankan      html  css  js  c++  java
  • 自定义View(二)继承自ViewGroup

      自定义View包括很多种,上一次随笔中的那一种是完全继承自View,这次写的这个小Demo是继承自ViewGroup的,主要是将自定义View继承自ViewGroup的这个流程来梳理一下,这次的Demo中自定义了一个布局的效果,并且这个自定义布局中包含布局自己的属性,布局中的控件也包含只属于这个布局才具有的自定义属性(类似于layout_weight只存在于LinearLayout中,只有LinearLayout中的控件可以使用一样)。话不多说,先看效果图:

      其中红色的部分是自定义的ViewGroup,这里使用了wrap_content,所以包裹内容。

      我的思路是这样的:

      先自定义一个类,继承自ViewGroup,继承自ViewGroup的自定义类,一定要重写onLayout()方法,因为这是一个抽象方法,主要作用就是用来绘制子View视图,确定ViewGroup中的每个子控件的位置。然后再重写其两个构造函数,一个是一个参数的,一个是两个参数的。继承自ViewGroup与继承自View有些不同的地方,我们在重写onMeasure()的时候,不仅仅要测量自定义的父布局(本自定义View)的尺寸,还要在其中计算其子View的尺寸信息,这是比较麻烦的地方,计算完尺寸还要再onLayout()方法中根据计算出来的控件的摆放位置,调用 子View.layout(int left,int top,int right,int bottom)方法将控件绘制在ViewGroup上,其中的四个参数为子View控件的左上角的点的坐标右下角的点的坐标信息。这里我们为子View定义了两个自定义属性,实现类似margin的效果,而只要定义了子控件的这类属性,就要自定义一个内部类,继承自MarginLayoutParams类,在这里获取我们在xml中设置的属性信息。也就是主要的方法和与继承自View的自定义View中不同的就是onMeasure()方法,自定义类继承自MarginLayoutParams,onLayout()方法,generateLayoutParams()方法。还不是有点懵?来看看代码,如果是在不懂,可以把代码拷贝到工程中自己改改属性试一下,就会明白不少!

      attr.xml:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <resources>
     3     <!--声明自定义属性 自定义ViewGroup的属性 -->
     4     <declare-styleable name="CascadeViewGroup">
     5         <attr name="verticalSpacing" format="dimension|reference"></attr>
     6         <attr name="horizontalSpacing" format="dimension|reference"></attr>
     7     </declare-styleable>
     8     <!--以下自定义属性针对自定义CascadeViewGroup布局中的子控件设置的属性-->
     9     <declare-styleable name="CascadeViewGroup_LayoutParams">
    10         <attr name="layout_paddingLeft" format="dimension|reference"></attr>
    11         <attr name="layout_paddingTop" format="dimension|reference"></attr>
    12     </declare-styleable>
    13 </resources>

      自定义View类:

      1 package com.example.customviewgroup;
      2 
      3 import android.content.Context;
      4 import android.content.res.TypedArray;
      5 import android.util.AttributeSet;
      6 import android.util.Log;
      7 import android.view.View;
      8 import android.view.ViewGroup;
      9 
     10 /**
     11  */
     12 public class CascadeViewGroup extends ViewGroup{
     13     //声明自定义布局中的子控件距离父布局的间距的宽度和高度
     14     private int mHoriztonalSpacing;
     15     private int mVerticalSpacing;
     16 
     17     public CascadeViewGroup(Context context) {
     18         super(context);
     19     }
     20 
     21     public CascadeViewGroup(Context context, AttributeSet attrs) {
     22         super(context, attrs);
     23         TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.CascadeViewGroup);
     24         mHoriztonalSpacing=array.getDimensionPixelSize(
     25                 R.styleable.CascadeViewGroup_horizontalSpacing,R.dimen.default_horizontal_spacing);
     26         mVerticalSpacing=array.getDimensionPixelSize(
     27                 R.styleable.CascadeViewGroup_verticalSpacing,R.dimen.default_vertical_spacing);
     28         array.recycle();
     29     }
     30 
     31     /**
     32      * onMeasure 测量自身大小  测量子view的大小
     33      * 并且将子view信息保存到LayoutParams中
     34      */
     35     @Override
     36     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     37         //获取viewgroup中子view 的个数
     38         int count=this.getChildCount();
     39         //获取当前光标的坐标位置 横坐标 纵坐标
     40         int width=getPaddingLeft();
     41 //        Log.d("Tag", "base- "+width);
     42         int height=this.getPaddingTop();
     43 //        Log.d("Tag", "base-height: "+height);
     44         for(int i=0;i<count;i++){
     45             View currentView=getChildAt(i);
     46             //测量每个子view的宽度和高度
     47             measureChild(currentView,widthMeasureSpec,heightMeasureSpec);
     48             LayoutParams lp= (LayoutParams) currentView.getLayoutParams();
     49             //判断子view是否设置自定义属性padding
     50             if(lp.mSettingPaddingLeft!=0){
     51                 width+=lp.mSettingPaddingLeft;
     52             }
     53             if(lp.mSettingPaddingTop!=0){
     54                 height+=lp.mSettingPaddingTop;
     55             }
     56             //获取子view的起始点坐标
     57             lp.x=width;
     58             lp.y=height;
     59             width+=mHoriztonalSpacing+currentView.getMeasuredWidth();
     60             height+=mVerticalSpacing+currentView.getMeasuredHeight();
     61         }
     62         //因为最后一次循环多加上了一个mHoriztonalSpacing和mVerticalSpacing,
     63         // 所以父布局的wrap_content情况的话需要减掉一个mHoriztonalSpacing和mVerticalSpacing
     64         width+=getPaddingRight()-mHoriztonalSpacing;
     65         height+=getPaddingBottom()-mVerticalSpacing;
     66         //设置自定义ViewGroup的宽度和高度
     67         //resolveSize()主要就是根据指定的尺寸大小和模式  返回需要的大小值
     68         // 自动判断是哪一种模式,并返回需要的尺寸大小(match_parent或者是指定大小或者是wrap_content)
     69         setMeasuredDimension(resolveSize(width,widthMeasureSpec),
     70                 resolveSize(height,heightMeasureSpec));
     71     }
     72 
     73     /**
     74      * 如果想在自定义ViewGroup中使用margin设置间距,则要在自定义类中重写generateLayoutParams系列方法才行
     75      * @return
     76      */
     77     @Override
     78     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
     79         return new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
     80     }
     81 
     82     @Override
     83     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
     84         return new LayoutParams(this.getContext(),attrs);
     85     }
     86 
     87     @Override
     88     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
     89         return new LayoutParams(p);
     90     }
     91 
     92     /**
     93      * 以内部类的形式自定义LayoutParams 方便测量子view时保存子控件使用的属性值
     94      * 当ViewGroup中的子控件中有自己独特的属性的时候才重写自定义一个类LayoutParams继承自MarginLayoutParams
     95      */
     96     public static class LayoutParams extends MarginLayoutParams{
     97         //记录子view的起始点
     98         int x;
     99         int y;
    100         //子控件的自定义属性,这里的效果相当于margin的效果
    101         int mSettingPaddingLeft;
    102         int mSettingPaddingTop;
    103 
    104         public LayoutParams(Context c, AttributeSet attrs) {
    105             super(c, attrs);
    106             TypedArray array=c.obtainStyledAttributes(attrs,R.styleable.
    107                         CascadeViewGroup_LayoutParams);
    108             mSettingPaddingLeft=array.getDimensionPixelSize(R.styleable.
    109                     CascadeViewGroup_LayoutParams_layout_paddingLeft,0);
    110             mSettingPaddingTop=array.getDimensionPixelSize(
    111                     R.styleable.CascadeViewGroup_LayoutParams_layout_paddingTop,0);
    112             array.recycle();
    113         }
    114 
    115         public LayoutParams(int width, int height) {
    116             super(width, height);
    117         }
    118 
    119         public LayoutParams(MarginLayoutParams source) {
    120             super(source);
    121         }
    122 
    123         public LayoutParams(ViewGroup.LayoutParams source) {
    124             super(source);
    125         }
    126     }
    127 
    128     /**
    129      * 确定布局子view的位置
    130      */
    131     @Override
    132     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    133         int count=getChildCount();
    134         for(int i=0;i<count;i++){
    135             View currentView=getChildAt(i);
    136             LayoutParams lp= (LayoutParams) currentView.getLayoutParams();
    137             currentView.layout(lp.x,lp.y,lp.x+currentView.getMeasuredWidth(),
    138                     lp.y+currentView.getMeasuredHeight());
    139         }
    140     }
    141 }

      布局文件:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <com.example.customviewgroup.CascadeViewGroup
     3     xmlns:android="http://schemas.android.com/apk/res/android"
     4     xmlns:app="http://schemas.android.com/apk/res-auto"
     5     android:layout_width="wrap_content"
     6     android:layout_height="wrap_content"
     7     android:background="#ff0000"
     8     app:horizontalSpacing="10dp"
     9     app:verticalSpacing="10dp"
    10     >
    11 
    12     <!--<TextView-->
    13         <!--android:layout_width="100dp"-->
    14         <!--android:layout_height="wrap_content"-->
    15         <!--android:background="#ff0000"-->
    16         <!--android:textSize="20sp"-->
    17         <!--android:text="打发打发斯蒂芬" />-->
    18 
    19     <TextView
    20         android:layout_width="wrap_content"
    21         android:layout_height="wrap_content"
    22         android:background="#00ff00"
    23         android:textSize="20sp"
    24         android:text="Hello World!" />
    25 
    26     <TextView
    27         android:layout_width="wrap_content"
    28         android:layout_height="wrap_content"
    29         android:background="#00ffff"
    30         android:textSize="20sp"
    31         android:text="Hello World!" />
    32 
    33     <TextView
    34         android:layout_width="wrap_content"
    35         android:layout_height="wrap_content"
    36         android:background="#0000ff"
    37         android:textSize="20sp"
    38         android:text="Hello World!" />
    39 </com.example.customviewgroup.CascadeViewGroup>

      基本上就这么多,但是会有一个小Bug,目前还不太清楚是怎么回事,就是在布局文件的根布局下如果不指定我们自定义的app:horizontalSpacing="10dp"和app:verticalSpacing="10dp"这两个属性,在自定义View的第40行和第42行getPaddingLeft()和getPaddingTop()的时候会得到一个非常大的值,即使我们指定了padding的值也是这样,也就是说必须设定这两个自定义属性,我们设置的默认padding没有起作用。不想有间距的话可以设成0dp,这个坑我找到解决办法之后再来填。

  • 相关阅读:
    内存映射函数remap_pfn_range学习——代码分析(3)
    内存映射函数remap_pfn_range学习——示例分析(2)
    内存映射函数remap_pfn_range学习——示例分析(1)
    在busybox里使用ulimit命令
    unlocked_ioctl和compat_ioctl
    使用cat读取和echo写内核文件节点的一些问题
    如何成为强大的程序员?
    如何实现“秒杀”系统
    关于博客园代码样式的调整
    Consuming a RESTful Web Service
  • 原文地址:https://www.cnblogs.com/RabbitLx/p/5843433.html
Copyright © 2011-2022 走看看