原文链接:http://www.jianshu.com/p/138b98095778

上一篇我们介绍了Android中自定义View的知识,并实现了一个类似Google彩虹进度条的自定义View,今天我们将进一步学习如何去自定义一个ViewGroup。

ViewGroup

我们知道ViewGroup就是View的容器类,我们经常用的LinearLayout,RelativeLayout等都是ViewGroup的子类,因为ViewGroup有很多子View,所以它的整个绘制过程相对于View会复杂一点,但是还是三个步骤measure,layout,draw,我们一次说明。

  • Measure
    Measure过程还是测量ViewGroup的大小,如果layout_widht和layout_height是match_parent或具体的xxxdp,就很简答了,直接调用setMeasuredDimension()方法,设置ViewGroup的宽高即可,如果是wrap_content,就比较麻烦了,我们需要遍历所有的子View,然后对每个子View进行测量,然后根据子View的排列规则,计算出最终ViewGroup的大小。

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  int childCount = this.getChildCount();  for (int i = 0; i < childCount; i++) {      View child = this.getChildAt(i);      this.measureChild(child, widthMeasureSpec, heightMeasureSpec);      int cw = child.getMeasuredWidth();      // int ch = child.getMeasuredHeight();  }}

    你可能需要类似上面的代码,其中getChildCount()方法,返回子View的数量,measureChild()方法,调用子View的测量方法。

  • Layout
    上一篇中,我们稍微提到了,layout过程其实就是对子View的位置进行排列,onLayout方法给我一个机会,来按照我们想要的规则自定义子View排列。
    @Overrideprotected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {  int childCount = this.getChildCount();  for (int i = 0; i < childCount; i++) {      View child = this.getChildAt(i);      LayoutParams lParams = (LayoutParams) child.getLayoutParams();      child.layout(lParams.left, lParams.top, lParams.left + childWidth,              lParams.top + childHeight);  }}
    你同样可能需要类似上面的代码,其中child.layout(left,top,right,bottom)方法可以对子View的位置进行设置,四个参数的意思大家通过变量名都应该清楚了。
  • Draw
    ViewGroup在draw阶段,其实就是按照子类的排列顺序,调用子类的onDraw方法,因为我们只是View的容器, 本身一般不需要draw额外的修饰,所以往往在onDraw方法里面,只需要调用ViewGroup的onDraw默认实现方法即可。

    LayoutParams

    ViewGroup还有一个很重要的知识LayoutParams,LayoutParams存储了子View在加入ViewGroup中时的一些参数信息,在继承ViewGroup类时,一般也需要新建一个新的LayoutParams类,就像SDK中我们熟悉的LinearLayout.LayoutParams,RelativeLayout.LayoutParams类等一样,那么可以这样做,在你定义的ViewGroup子类中,新建一个LayoutParams类继承与ViewGroup.LayoutParams。

    public static class LayoutParams extends ViewGroup.LayoutParams {  public int left = 0;  public int top = 0;  public LayoutParams(Context arg0, AttributeSet arg1) {      super(arg0, arg1);  }  public LayoutParams(int arg0, int arg1) {      super(arg0, arg1);  }  public LayoutParams(android.view.ViewGroup.LayoutParams arg0) {      super(arg0);  }}

    那么现在新的LayoutParams类已经有了,如何让我们自定义的ViewGroup使用我们自定义的LayoutParams类来添加子View呢,ViewGroup同样提供了下面这几个方法供我们重写,我们重写返回我们自定义的LayoutParams对象即可。

    @Overridepublic android.view.ViewGroup.LayoutParams generateLayoutParams(      AttributeSet attrs) {  return new NinePhotoView.LayoutParams(getContext(), attrs);}@Overrideprotected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {  return new LayoutParams(LayoutParams.WRAP_CONTENT,          LayoutParams.WRAP_CONTENT);}@Overrideprotected android.view.ViewGroup.LayoutParams generateLayoutParams(      android.view.ViewGroup.LayoutParams p) {  return new LayoutParams(p);}@Overrideprotected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {  return p instanceof NinePhotoView.LayoutParams;}

    实例

    我们还是做一个实例来说明,我们今天做一个类似微信朋友圈 存储要发送图片的控件,点击+号图片,可以一直加图片,最多9张。那么微信是4个一排,我们这里是3个一排,因为一般常规都是三个一排,这些都是细节不要在意(另外偷偷告诉大家,微信的实现是用TableLayout,-.-)。


    微信朋友圈发送图片
    public class NinePhotoView extends ViewGroup {public static final int MAX_PHOTO_NUMBER = 9;private int[] constImageIds = { R.drawable.girl_0, R.drawable.girl_1,      R.drawable.girl_2, R.drawable.girl_3, R.drawable.girl_4,      R.drawable.girl_5, R.drawable.girl_6, R.drawable.girl_7,      R.drawable.girl_8 };// horizontal space among children viewsint hSpace = Utils.dpToPx(10, getResources());// vertical space among children viewsint vSpace = Utils.dpToPx(10, getResources());// every child view width and height.int childWidth = 0;int childHeight = 0;// store images res idArrayList mImageResArrayList = new ArrayList(9);private View addPhotoView;public NinePhotoView(Context context) {  super(context);}public NinePhotoView(Context context, AttributeSet attrs) {  this(context, attrs, 0);}public NinePhotoView(Context context, AttributeSet attrs, int defStyle) {  super(context, attrs, defStyle);  TypedArray t = context.obtainStyledAttributes(attrs,          R.styleable.NinePhotoView, 0, 0);  hSpace = t.getDimensionPixelSize(          R.styleable.NinePhotoView_ninephoto_hspace, hSpace);  vSpace = t.getDimensionPixelSize(          R.styleable.NinePhotoView_ninephoto_vspace, vSpace);  t.recycle();  addPhotoView = new View(context);  addView(addPhotoView);  mImageResArrayList.add(new integer());}

    目前为止,都跟上一篇说的大致差不多,另外拍照和从相册选择图片不是我们这一篇的重点,所以我们把图片硬编码到代码中(全是美女...),ViewGroup初始化时我们添加了一个+号按钮,给用户点击添加新的图片。

  • Measure

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  int rw = MeasureSpec.getSize(widthMeasureSpec);  int rh = MeasureSpec.getSize(heightMeasureSpec);  childWidth = (rw - 2 * hSpace) / 3;  childHeight = childWidth;  int childCount = this.getChildCount();  for (int i = 0; i < childCount; i++) {      View child = this.getChildAt(i);      //this.measureChild(child, widthMeasureSpec, heightMeasureSpec);      LayoutParams lParams = (LayoutParams) child.getLayoutParams();      lParams.left = (i % 3) * (childWidth + hSpace);      lParams.top = (i / 3) * (childWidth + vSpace);  }  int vw = rw;  int vh = rh;  if (childCount < 3) {      vw = childCount * (childWidth + hSpace);  }  vh = ((childCount + 3) / 3) * (childWidth + vSpace);  setMeasuredDimension(vw, vh);}

    我们的子View三个一排,而且都是正方形,所以我们上面通过循环很好去得到所有子View的位置,注意我们上面把子View的左上角坐标存储到我们自定义的LayoutParams 的left和top二个字段中,Layout阶段会使用,最后我们算得整个ViewGroup的宽高,调用setMeasuredDimension设置。

  • Layout

    @Overrideprotected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {  int childCount = this.getChildCount();  for (int i = 0; i < childCount; i++) {      View child = this.getChildAt(i);      LayoutParams lParams = (LayoutParams) child.getLayoutParams();      child.layout(lParams.left, lParams.top, lParams.left + childWidth,              lParams.top + childHeight);      if (i == mImageResArrayList.size() - 1 && mImageResArrayList.size() != MAX_PHOTO_NUMBER) {          child.setBackgroundResource(R.drawable.add_photo);          child.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View arg0) {                  addPhotoBtnClick();              }          });      }else {          child.setBackgroundResource(constImageIds[i]);          child.setOnClickListener(null);      }  }}public void addPhoto() {  if (mImageResArrayList.size() < MAX_PHOTO_NUMBER) {      View newChild = new View(getContext());      addView(newChild);      mImageResArrayList.add(new integer());      requestLayout();      invalidate();  }}public void addPhotoBtnClick() {  final CharSequence[] items = { "Take Photo", "Photo from gallery" };  AlertDialog.Builder builder = new AlertDialog.Builder(getContext());  builder.setItems(items, new DialogInterface.OnClickListener() {      @Override      public void onClick(DialogInterface arg0, int arg1) {          addPhoto();      }  });  builder.show();}

    最核心的就是调用layout方法,根据我们measure阶段获得的LayoutParams中的left和top字段,也很好对每个子View进行位置排列。然后判断在图片未达到最大值9张时,默认最后一张是+号图片,然后设置点击事件,弹出对话框供用户选择操作。

  • Draw
    不需要重写,使用ViewGroup默认实现即可。

    附上布局文件

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="40dp"android:orientation="vertical" ><com.sw.demo.widget.NinePhotoView    android:id="@+id/photoview"    android:layout_width="match_parent"    android:layout_height="wrap_content"    app:ninephoto_hspace="10dp"    app:ninephoto_vspace="10dp"    app:rainbowbar_color="@android:color/holo_blue_bright" >com.sw.demo.widget.NinePhotoView>LinearLayout>

最后还是加上程序运行的效果图,今天自定义ViewGroup的讲解就这么多了,祝大家每天都有新收获,每天都有好心情~~~


NiewPhotoView.gif

更多相关文章

  1. 【Android】View类详解 (游戏开发必备)
  2. Android(安卓)Jetpack之LiveData源码分析
  3. Android自带音乐播放器专辑图片相同的问题
  4. 保存和重入Activity 状态的最简单方法
  5. android 4.0 屏蔽 HOME_KEY 和 RECENT_APP_KEY
  6. 第75章、再识Intent-调用发送Email程序(从零开始学Android)
  7. Android之NDK开发 Android(安卓)studio 篇
  8. android有了eventbus,一切与关界面通信问题迎刃而解。
  9. Android(安卓)WebView 与 原生的交互

随机推荐

  1. Android 拍照及相册选图的那些坑
  2. android 设置bitmap 设置图片的大小
  3. android SQLiteDatebase 实践
  4. Ubuntu上安装和使用Android Studio
  5. android音频、视频、拍照基础操作
  6. Android SDK与ADT不匹配的问题 This Andr
  7. Android(安卓)UI ListView讲解
  8. Android下读取logcat的信息
  9. 搭建 android 代码镜像服务
  10. Android获取所在地城市名2