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的大小。

    @Override
    protected 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排列。

    @Override
    protected 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对象即可。

    @Override
    public android.view.ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {return new NinePhotoView.LayoutParams(getContext(), attrs);
    }@Override
    protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {return new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
    }@Override
    protected android.view.ViewGroup.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams p) {return new LayoutParams(p);
    }@Override
    protected 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 views
    int hSpace = Utils.dpToPx(10, getResources());
    // vertical space among children views
    int vSpace = Utils.dpToPx(10, getResources());// every child view width and height.
    int childWidth = 0;
    int childHeight = 0;// store images res id
    ArrayList<integer> mImageResArrayList = new ArrayList<integer>(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

    @Override
    protected 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

    @Override
    protected 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() {@Overridepublic 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() {@Overridepublic 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.NinePhotoViewandroid: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

教你搞定Android自定义ViewGroup相关推荐

  1. 教你搞定Android自定义View

    Android App开发过程中,很多时候会遇到系统框架中提供的控件无法满足我们产品的设计需求,那么这时候我们可以选择先Google下有没有比较成熟的开源项目可以让我们用,当然现在Github上面的项 ...

  2. android 自定义viewgroup onmeasure,一篇文章搞懂Android 自定义Viewgroup的难点

    本文的目的 目的在于教会大家到底如何自定义viewgroup,自定义布局和自定义测量到底如何写.很多网上随便搜搜的概念和流程图这里不再过多描述了,建议大家看本文之前,先看看基本的自定义viewgrou ...

  3. 小米android手机同步数据,怎样将旧手机里面的数据,丝毫不差的转移到新手机?一键教你搞定...

    怎样将旧手机里面的数据,丝毫不差的转移到新手机?一键教你搞定 距离618大促还有一个月的时间,肯定不少人趁着优惠,给自己换一个新手机. 那么今天就来教大家一招,如何将旧手机全部资料数据,转移到新手机上 ...

  4. Android零基础入门第7节:搞定Android模拟器,开启甜蜜之旅

    原文:Android零基础入门第7节:搞定Android模拟器,开启甜蜜之旅 在前几期中总结分享了Android的前世今生.Android 系统架构和应用组件那些事.带你一起来聊一聊Android开发 ...

  5. android fragment 底部菜单栏,一句话搞定Android底部导航栏,一键绑定Fragment、ViewPager...

    现在大多数App都会用到底部导航栏,比如常见的聊天工具QQ.微信.购物App等等,有了底部导航栏,用户可以随时切换界面,查看不同的内容.它的实现方式也很多,以前大多使用TabHost来实现,但是现在我 ...

  6. Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发

    Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...

  7. android自定义viewgroup之我也玩瀑布流

    先看效果图吧, 继上一篇<android自定义viewgroup实现等分格子布局>中实现的布局效果,这里稍微有些区别,每个格子的高度不规则,就是传说的瀑布流布局,一般实现这种效果,要么用第 ...

  8. android 自定义flowlayout,Android 自定义ViewGroup之实现FlowLayout-标签流容器

    本篇文章讲的是Android 自定义ViewGroup之实现标签流式布局-FlowLayout,开发中我们会经常需要实现类似于热门标签等自动换行的流式布局的功能,网上也有很多这样的FlowLayout ...

  9. python正则匹配日期2019-03-11_都2019年了,正则表达式为啥还是这么难?这里的Python学习教程教你搞定!...

    都9102年了,你还觉得正则表达式很难?难,确实是还难啊! 这里南瓜跟大家总结的最新Python学习教程,教你搞定它! 正则表达式语法 字符与字符类 特殊字符: .^$?+*{}| 以上特殊字符要想使 ...

最新文章

  1. PHP自动加载类和方法,在PHP中自动加载类的最佳方法
  2. 虚幻填坑004:减少starter content占用空间,只保留使用的assets
  3. 数据库元数据数据字典查询_6_列出给定表的外键引用
  4. 开发人员眼中最好的代码编辑器是谁?
  5. CDN对互联网产业的价值和作用
  6. 关于ajax跨域的问题
  7. (转)智能投顾面临的法律合规问题及国际监管经验
  8. wps2000老版本 v3.02.99
  9. ISTQB FL初级认证考试资料(中文)
  10. web绿色服务器单文件,Web个人临时共享服务器
  11. 关于Fragment + RecyclerView + Toolbar + BottomNavigationView的组合应用
  12. 大数据,物联网和人工智能的关系
  13. 2022 CCCC 团体程序设计天梯赛知识点以及题解
  14. 北京地铁,把什么丢了?
  15. 前端js面试题(高级)
  16. Outlook.com的imap和pop服务器
  17. 华为服务器rh-2286远程控制无法使用
  18. C语言键盘方向键的读入
  19. Xilinx PLL
  20. 网通相中中国联通GSM网络 联通暂无意租售

热门文章

  1. C语言-二维数组与指针
  2. 【C 语言】字符串模型 ( strstr-do…while 模型 )
  3. 【Android 逆向】substrate 框架 ( substrate 简介 | substrate 相关文档资料 )
  4. 【Android 插件化】Hook 插件化框架 ( 通过反射获取 “宿主“ 应用中的 Element[] dexElements )
  5. Django安装使用基础
  6. bzoj 2809 Apio2012 dispatching
  7. zip unzip 命令
  8. Vs2005 正在更新 IntelliSense无法通过的解决办法
  9. CallContext和多线程
  10. C#中调用Windows API的要点【转载】