了解自定义View和继承View,继承ViewGroup,继承已有View,继承已有ViewGroup实例ji

本文详细介绍了自定义View的四种方式:继承View、继承ViewGroup、继承已有View和继承已有ViewGroup,并提供了具体实例代码。

自定义View的分类

继承View

当我们需要实现的效果是一个不规则效果的时候,那么这时就需要继承 View 来实现了,我们需要重写 onDraw 方法,在该方法里实现各种不规则的图形和效果。当我们使用这种方式的时候,需要自己去处理 warp_content 和 padding。

继承ViewGroup

当系统所提供的 LinearLayout、FrameLayout 等布局控件无法满足我们的需求时,这时我们就需要使用这种方式来实现自己想要的布局效果了。当我们使用这种方式的时候,需要重写 onLayout 方法来对子 View 进行布局,以及测量本身和子 View 宽高,还需要处理本身的 padding 和子 View 的 margin。

 

继承已有View

当我们需要基于已有的 View 进行扩展或修改的时候,那么就可以使用这种方式。比如说,我们需要一个圆角的 ImageView,那么这时就可以继承 ImageView 进行修改了。当我们使用这种方式的时候,一般不需要自己去处理 wrap_content 和 padding 等,因为系统控件已经帮我们做好了。

继承已有布局

这种方式也叫做:自定义组合 View。该方式比较简单,当我们需要将一组 View 组合在一起,方便后期复用的时候,就可以使用该方法。当我们使用这种方式的时候,不需要去处理 ViewGroup 的测量和布局流程,因为系统控件已经帮我们做好了。

 

那么下面我们就从实例的角度来看看自定义View吧

继承View的实例

当我们自定义View继承子View时,我们需要注意的细节有:

1 View是wrap_content时需要手动测量View的宽高

2 View有padding值的时候需要处理

 

在这里我们写一个规范的自定义View, 画出一个圆

注意: 要对 View 的 padding 和 LayoutParams 是 wrap_content 的情况进行处理,否则 padding 将会无法生效、wrap_content 的效果会和 match_parent 一样

 

其中重写onMeasure方法, 判断当是wrap_content的情况时,自己测量view的宽或高

[java] view plain copy

  1. package com.example.mycustomviewdemo;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.graphics.Paint;  
  7. import android.util.AttributeSet;  
  8. import android.view.View;  
  9.   
  10. /** 
  11.  * 继承View的自定义控件 
  12.  * 注意 view是wrap_content时需要手动测量View的宽高 
  13.  * View有padding值时需要处理 
  14.  */  
  15.   
  16. public class MyCircleView extends View {  
  17.   
  18.     private Paint mPaint;  
  19.     private int mRadius;  
  20.   
  21.     public MyCircleView(Context context) {  
  22.         this(context,null);  
  23.     }  
  24.   
  25.     public MyCircleView(Context context, AttributeSet attrs) {  
  26.         this(context, attrs,0);  
  27.     }  
  28.   
  29.     public MyCircleView(Context context, AttributeSet attrs, int defStyleAttr) {  
  30.         super(context, attrs, defStyleAttr);  
  31.         init();  
  32.     }  
  33.   
  34.     private void init() {  
  35.         mPaint = new Paint();   //初始化画笔  
  36.         mPaint.setColor(Color.GREEN);  
  37.         mPaint.setAntiAlias(true);  
  38.   
  39.         mRadius = 80;  
  40.     }  
  41.   
  42.     @Override  
  43.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  44.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  45.   
  46.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  47.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  48.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  49.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  50.   
  51.         int width = 0;  
  52.         int height =0;  
  53.         if(widthMode == MeasureSpec.EXACTLY) {  
  54.             width = widthSize;  
  55.         }else {  
  56.             //widthMode == MeasureSpec.AT_MOST模式 自己设置控件宽度  
  57.             //当是wrap_content或者给具体dp的时候会走这里  
  58.             width = mRadius * 2 +  getPaddingRight() + getPaddingLeft();  
  59.         }  
  60.         if(heightMode == MeasureSpec.EXACTLY) {  
  61.             height = heightSize;  
  62.         }else {  
  63.             height = mRadius * 2 + getPaddingTop() + getPaddingBottom();  
  64.         }  
  65.         //注意最后 调用这个方法 让属性生效  
  66.         setMeasuredDimension(width,height);  
  67.     }  
  68.   
  69.     @Override  
  70.     protected void onDraw(Canvas canvas) {  
  71.         super.onDraw(canvas);  
  72.         //处理padding  
  73.         int pl = getPaddingLeft();  
  74.         int pr = getPaddingRight();  
  75.         int pt = getPaddingTop();  
  76.         int pb = getPaddingBottom();  
  77.   
  78.         int width = getWidth() - pl - pr;  //控件本身的宽度  
  79.         int height = getHeight() - pt - pb; //控件本身的高度  
  80.   
  81.   
  82.         int centerX = width /2 + pl;  //中心点的横坐标  
  83.         int centerY = height /2  + pt;  //中心点的纵坐标  
  84.   
  85.   
  86.         canvas.drawCircle(centerX,centerY,mRadius,mPaint);  
  87.     }  
  88. }  

 

继承ViewGroup实例

当我们自定义View继承自ViewGroup的时候,需要实现孩子的onLayout方法指定子View的摆放位置,并且需要重写 onMeasure 方法来测量大小。在这个实例当中,我们简单模仿下 LinearLayout ,只不过只实现其 Vertical 模式,在这个实例当中,我们需要注意的细节有:

1 ViewGroup是wrap_content时需要手动测量

2 当ViewGroup本身有padding值的时候需要处理

3 当子View有margin值时需要处理

 

规范自定义ViewGroup, 这几个细节我们要处理,代码:[java] view plain copy

  1. package com.example.mycustomviewdemo;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.view.View;  
  6. import android.view.ViewGroup;  
  7.   
  8. /** 
  9.  * 继承ViewGroup实例 
  10.  * 
  11.  * 注意: 
  12.  * ViewGroup是wrap_content需要手动测量 
  13.  * 当ViewGroup本身有padding值时要处理 
  14.  * 当子view有margin值时要处理 
  15.  */  
  16.   
  17. public class MySimpleVerticalLayout extends ViewGroup {  
  18.     private Context mContext;  
  19.   
  20.     public MySimpleVerticalLayout(Context context) {  
  21.         this(context,null);  
  22.     }  
  23.   
  24.     public MySimpleVerticalLayout(Context context, AttributeSet attrs) {  
  25.         this(context, attrs,0);  
  26.     }  
  27.   
  28.     public MySimpleVerticalLayout(Context context, AttributeSet attrs, int defStyleAttr) {  
  29.         super(context, attrs, defStyleAttr);  
  30.         mContext = context;  
  31.   
  32.     }  
  33.   
  34.     @Override  
  35.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  36.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  37.         //获取ViewGroup测量模式  大小  
  38.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  39.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  40.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  41.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  42.   
  43.         //获取ViewGroup的padding(内边距)值  
  44.         int pt = getPaddingTop();  
  45.         int pb = getPaddingBottom();  
  46.         int pl = getPaddingLeft();  
  47.         int pr = getPaddingRight();  
  48.   
  49.         //先测量孩子, 才能得到孩子具体的宽高;    ------->> 这一步很重要  
  50.         measureChildren(widthMeasureSpec,heightMeasureSpec);  
  51.   
  52.         int width = 0;  
  53.         int height = 0;  
  54.         int maxWidth = 0;  
  55.         if(widthMode == MeasureSpec.AT_MOST) {  
  56.             for(int i = 0; i < getChildCount();i++) {  
  57.                 View childAt = getChildAt(i);  
  58.                 if(childAt.getVisibility() == GONE) {  
  59.                     continue;  
  60.                 }  
  61.                 //宽度为孩子中 最宽的一个  
  62.   
  63.   
  64.                 //孩子还有个MarginLayoutParams属性  
  65.                 MarginLayoutParams marginLayoutParams = (MarginLayoutParams) childAt.getLayoutParams();  
  66.                 int childWidth = childAt.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;  
  67.                 maxWidth  = maxWidth > childWidth ? maxWidth : childWidth;  
  68.             }  
  69.             //将遍历后的最宽的宽度加上左右内边距 赋值  
  70.             width = maxWidth + pl + pr;  
  71.   
  72.         }  
  73.         if(heightMode == MeasureSpec.AT_MOST) {  
  74.             for(int i = 0; i < getChildCount();i++) {  
  75.                 View childAt = getChildAt(i);  
  76.                 if(childAt.getVisibility() == GONE) {  
  77.                     continue;  
  78.                 }  
  79.                 //高度为所有的孩子高度之和加上内边距之和  
  80.                 MarginLayoutParams marginLayoutParams = (MarginLayoutParams) childAt.getLayoutParams();  
  81.                 height += childAt.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;  
  82.             }  
  83.             //最终的高度  
  84.             height += (pt + pb);  
  85.         }  
  86.   
  87.         //做判断, 并将值设置  
  88.         setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? width : widthSize,heightMode == MeasureSpec.AT_MOST ? height : heightSize);  
  89.   
  90.     }  
  91.   
  92.     /** 
  93.      * 对子View进行摆放 
  94.      * @param changed 
  95.      * @param l 
  96.      * @param t 
  97.      * @param r 
  98.      * @param b 
  99.      */  
  100.     @Override  
  101.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  102.         //viewGroup的padding值影响孩子的摆放  
  103.         int pt = getPaddingTop();  
  104.         int pb = getPaddingBottom();  
  105.         int pl = getPaddingLeft();  
  106.         int pr = getPaddingRight();  
  107.   
  108.         int cl = 0;  
  109.         int ct = 0;  
  110.         int cr = 0;  
  111.         int cb = 0;  
  112.         int bm = 0;     //这个bm很神奇  
  113.   
  114.         for(int i =0; i < getChildCount();i++) {  
  115.             //判断当子view没有被Gone掉时候  
  116.             View childAt = getChildAt(i);  
  117.             if(childAt.getVisibility() != GONE) {  
  118.                 //计算每个View的位置  
  119.                 MarginLayoutParams marginLayoutParams= (MarginLayoutParams) childAt.getLayoutParams();  
  120.                 cl = marginLayoutParams.leftMargin;  
  121.                 ct += marginLayoutParams.topMargin;  
  122.                 cr = childAt.getMeasuredWidth() + marginLayoutParams.leftMargin;  
  123.                 cb += childAt.getMeasuredHeight() + marginLayoutParams.topMargin;  
  124.                 //对子View进行布局,  注意 一定要调用childAt.layout()方法  
  125.                 childAt.layout(cl + pl, ct + pt + bm, cr + pr,cb + pb + bm);  
  126.                 ct += childAt.getMeasuredHeight();  
  127.                 bm += marginLayoutParams.bottomMargin;  
  128.             }  
  129.         }  
  130.     }  
  131.   
  132.   
  133.   
  134.     @Override  
  135.     public LayoutParams generateLayoutParams(AttributeSet attrs) {  
  136.         return new MarginLayoutParams(mContext, attrs);  
  137.     }  
  138. }  


 

继承已有View的实例
 

继承自系统已有View时,一般是对其原有功能进行扩展或者修改, 比如一个Button  在这里注意监听器的使用

 

 

继承已有ViewGroup的实例

这种自定义 View 的实现方式也叫做:“自定义组合控件”,是一种比较简单的自定义 View 方式。使用这种方式时,由于是继承已有的系统控件,所以我们不需去测量、布局、处理 margin、padding等,因为系统控件本身已经处理好了。


当我们的项目中有一些布局在很多地方都要用到的话,那么第一时间肯定就要想到复用了。复用的话,有人可能会想到使用 include 复用布局,但是如果这样的话,当布局改动性很大时,使用 include 并不是很灵活。这时候,就可以使用 ”继承已有 ViewGroup“ 这种方式了。


下面一个实例,就拿我们平时可能经常要写的 Item 为例吧:

 

[java] view plain copy

  1. package com.example.mycustomviewdemo;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.util.AttributeSet;  
  6. import android.view.LayoutInflater;  
  7. import android.view.View;  
  8. import android.widget.FrameLayout;  
  9. import android.widget.ImageView;  
  10. import android.widget.TextView;  
  11.   
  12. /** 
  13.  * 继承已有的ViewGroup 自定义View的实例,常用item布局 
  14.  */  
  15.   
  16. public class MyCustomItemLayout extends FrameLayout {  
  17.     private Context mContext;  
  18.   
  19.     private String mLeftText;  
  20.     private int mRightImageResourceId;  
  21.     private String mRightText;  
  22.     private TextView mTxt_left;  
  23.     private TextView mTxt_right;  
  24.     private ImageView mImg_right;  
  25.   
  26.     public void setLeftText(String leftText) {  
  27.         mLeftText = leftText;  
  28.     }  
  29.   
  30.     public void setRightImageResourceId(int rightImageResourceId) {  
  31.         mRightImageResourceId = rightImageResourceId;  
  32.     }  
  33.   
  34.     public void setRightText(String rightText) {  
  35.         mRightText = rightText;  
  36.     }  
  37.   
  38.     public MyCustomItemLayout(Context context) {  
  39.         this(context,null);  
  40.     }  
  41.   
  42.     public MyCustomItemLayout(Context context, AttributeSet attrs) {  
  43.         this(context, attrs,0);  
  44.     }  
  45.   
  46.     public MyCustomItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {  
  47.         super(context, attrs, defStyleAttr);  
  48.         this.mContext = context;  
  49.   
  50.         //取出自定义属性  
  51.         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomItemLayout);  
  52.         mLeftText = typedArray.getString(R.styleable.MyCustomItemLayout_leftText);  
  53.         //默认图片为箭头  
  54.         mRightImageResourceId = typedArray.getResourceId(R.styleable.MyCustomItemLayout_rightImage, R.drawable.ic_arrow_right);  
  55.         mRightText = typedArray.getString(R.styleable.MyCustomItemLayout_rightText);  
  56.         typedArray.recycle();  //回收释放资源  
  57.   
  58.         initView();  
  59.   
  60.         initData();  
  61.     }  
  62.   
  63.     private void initData() {  
  64.         //两种初始化数据的方法,  外界通过set方法进行设置; 布局中直接定义  
  65.         mTxt_left.setText(mLeftText);  
  66.         mTxt_right.setText(mRightText);  
  67.         mImg_right.setImageResource(mRightImageResourceId);  
  68.     }  
  69.   
  70.     private void initView() {  
  71.         //注意  这第二个参数传 this;  两个参数的方法默认会调用三个参数的方法,  第二个参数不为null时,相当于三个参数中root不为null,attach为true  
  72.         View view = LayoutInflater.from(mContext).inflate(R.layout.layout_customitem, this);  
  73.         mTxt_left = (TextView) findViewById(R.id.txt_left);  
  74.         mTxt_right = (TextView) findViewById(R.id.txt_right);  
  75.         mImg_right = (ImageView) findViewById(R.id.img_right);  
  76.     }  
  77.   
  78.   
  79. }  


首先自定义一个类,继承自 FrameLayout,当然,这里你也可以选择继承 LinearLayout 或者其他,根据具体需求来。其中在构造中获取了自定义属性,最主要的地方就是填充布局那里,将布局填充到了当前控件也就是自定义的 ViewGroup 上。填充的布局如下:
[html] view plain copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.               android:layout_width="match_parent"  
  4.               android:layout_height="wrap_content"  
  5.               android:background="?android:selectableItemBackground"  
  6.               android:gravity="center_vertical"  
  7.               android:padding="15dp">  
  8.   
  9.     <TextView  
  10.         android:id="@+id/txt_left"  
  11.         android:layout_width="wrap_content"  
  12.         android:layout_height="wrap_content"  
  13.         android:drawablePadding="5dp"  
  14.         android:ellipsize="end"  
  15.         android:maxLines="1"  
  16.         android:textColor="@color/text_black"  
  17.         android:textSize="@dimen/txt14"/>  
  18.   
  19.     <TextView  
  20.         android:id="@+id/txt_right"  
  21.         android:layout_width="0dp"  
  22.         android:layout_height="wrap_content"  
  23.         android:layout_marginLeft="10dp"  
  24.         android:layout_weight="1"  
  25.         android:ellipsize="end"  
  26.         android:gravity="right"  
  27.         android:maxLines="1"  
  28.         android:textSize="@dimen/txt14"/>  
  29.   
  30.     <ImageView  
  31.         android:id="@+id/img_right"  
  32.         android:layout_width="wrap_content"  
  33.         android:layout_height="wrap_content"  
  34.         android:layout_marginLeft="5dp"  
  35.         android:src="@mipmap/ic_arrow_right"/>  
  36. </LinearLayout>  


 

在项目中 有相类似的Item布局的使用时, 可以直接在布局中通过自定义属性设置数据:

[html] view plain copy

  1. <com.example.mycustomviewdemo.MyCustomItemLayout  
  2.        android:layout_width="match_parent"  
  3.        android:layout_height="wrap_content"  
  4.        android:layout_marginTop="10dp"  
  5.        app:leftText="版本更新"  
  6.        app:rightText="V1.1"  
  7.        app:rightImage="@drawable/ic_arrow_right"  
  8.        />  

也可以通过暴露的方法设置数据
 

 

至此,自定义控件四种继承方式讲解完毕,  下面看一三个自定义控件的效果


 

 

转载于:https://my.oschina.net/u/1177694/blog/1648485

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值