自定义控件

您所在的位置:网站首页 VB自定义控件 自定义控件

自定义控件

2023-03-26 02:01| 来源: 网络整理| 查看: 265

View的绘制流程

在写流式布局前,我们先验证一个问题,就是前面View绘制的测量过程一文中,如果子元素没有在onMeasure设置一个宽高,即在xml中使用wrap_content时,是否会和前面那篇文章说的一样,它会完全和父容器一样大小?我们通过一个小例子来看看, 我们先写个简单的自定义控件,他继承自view:

public class MyView extends View { private String TAG="MyView"; public MyView(Context context) { super(context); } public MyView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec); System.out.println("heightSpecMode:"+heightSpecMode+",heightSpecSize:"+heightSpecSize); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }

我们在xml布局中添加它:

我们看下显示出来的效果,他的大小和父容器一致:

image.png

我们在来修改下父容器的大小,看看是否和父容器一样大小:

image.png

我们在onMesure中重写下,当它的测量模式是AT_MOST(wrap_content),给他一个默认值

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec); System.out.println("heightSpecMode:"+heightSpecMode+",heightSpecSize:"+heightSpecSize); // super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measureWidth = widthSpecMode == MeasureSpec.EXACTLY ? widthSpecSize : 200; int measureHeight = heightSpecMode == MeasureSpec.EXACTLY ? heightSpecSize : 200; setMeasuredDimension(measureWidth, measureHeight); } image.png

完美解决!!!我们这篇文章不是要写流式布局吗?为什么要写这个,别着急,这个当然用的上,我们先来看下流式布局的效果:

image.png

我给这个布局加了个背景框,目的就是看看整个布局的大小,我们对这个布局做个简单的分析: 1、如何换行? 2、行高通过谁来确定? 3、当容器的测量模式是AT_MOST时,他的宽高最终由谁来决定。 4、摆放问题的具体细节。 跟着上面几个问题,我们开始写代码,我们知道,上面这个是一个容器,所以我们肯定要继承自ViewGroup,我们从上篇文章了解到,ViewGroup的onMeasure方法和onLayout需要重写,所以我们就写一个FlowViewGroup,让他继承自ViewGroup,上面4个问题,我在FlowViewGroup中也添加了代码注释加以说明了:

public class FlowViewGroup extends ViewGroup { private String TAG = "FlowViewGroup"; //存放所有的子元素 private List viewLists = new ArrayList(); //存放每一行的最高高度 private List heightList = new ArrayList(); //是否已经测量过 private boolean isMeasure = false; public FlowViewGroup(Context context) { super(context); } public FlowViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } public FlowViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 3.1整个布局的宽度,应该由所有行所占宽度的最大值rowMaxWidth决定,所以要拿每一行子元素的总宽度rowCountWidth进行对比。 */ int rowMaxWidth = 0; /** * 3.2整个布局的高度,应该由由所有行的最高度rowMaxHeight的和组成 */ int countHeight = 0; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //获取父容器推荐的大小 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams(); Log.d(TAG, "onMeasure heightSpecSize: " + heightSpecSize); /**获取的其实是自己的测量模式,他的父类是linearLayout,linearLayout会执行onMeasure * onMeasure里面会执行ViewGroup中的onMeasureMargin, * 然后通过父容器(LinearLayout)的MeasurePec和子元素(FlowViewGroup)LayoutParma * 得到一个MeasureSpec然后执行子元素的onMeasure方法,并把得到的measureSpec, * 也就是父容器已经把这个测量模式改成当前元素的了。 */ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); /** * 每一行的子元素的总宽度 */ int rowCountWidth = 0; /** * 2、每一行高的决定:每一行子元素中的最高的元素高度,根据它来设置每一行的高度 */ int rowMaxHeight = 0; /** *内个元素的宽高度 */ int indexChildWidth = 0; int indexChildHeight = 0; //这个list存储每一行的View List rowViews = new ArrayList(); if (!isMeasure) { isMeasure = true; Log.d(TAG, "onMeasure: " + getChildCount()); for (int index = 0; index < getChildCount(); index++) { View child = getChildAt(index); //开始测量子元素 measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams(); //当前子元素的实际宽高 indexChildWidth = child.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin; indexChildHeight = child.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin; /** * 1.怎么判断是否换行?如果当前的一行的所有子元素加上下一个子元素 * 高于测量出来的宽度,就应该换行,下一个子元素应该被记录在下一行 */ Log.d(TAG, "onMeasure: " + widthSpecSize + ",," + (rowCountWidth + indexChildWidth)); if ((rowCountWidth + indexChildWidth) > widthSpecSize) { Log.d(TAG, "onMeasure: next row:" + rowCountWidth + ",add once"); //换行需要记录上一个高度 肯定是拿最高的记录 countHeight += rowMaxHeight; rowMaxWidth = Math.max(rowCountWidth, countHeight); //记录上一个最大高度 heightList.add(rowMaxHeight); //记录上一个行内总子元素 viewLists.add(rowViews); //重新new给下一行子元素 rowViews = new ArrayList(); //将当前行总宽置为0,给下一行使用 rowCountWidth = 0; //将当前行最高置为0,给下一行使用 rowMaxHeight = 0; /** * 记录下一行的第一个子元素 */ Log.d(TAG, "onMeasure: add next first "); //当前行的总宽度也应该加上他 rowCountWidth += indexChildWidth; //添加到当前行子元素到当前行view列表中 //记录所有行的最大宽度 rowMaxWidth = Math.max(rowCountWidth, rowMaxWidth); //记录当前行的最大高度 rowMaxHeight = Math.max(rowMaxHeight, indexChildHeight); rowViews.add(child); } else { Log.d(TAG, "onMeasure: no need change row"); //计算当前行的总宽度 rowCountWidth += indexChildWidth; //记录所有行的最大宽度 rowMaxWidth = Math.max(rowCountWidth, rowMaxWidth); //记录当前行的最大高度 rowMaxHeight = Math.max(rowMaxHeight, indexChildHeight); //添加到当前行子元素到当前行view列表中 rowViews.add(child); } } if (!rowViews.isEmpty()) { //如果最后一个没有换行 或者就一行 viewLists.add(rowViews); } if (rowMaxHeight != 0) { //如果最后一个没有换行 或者 就一行 countHeight += rowMaxHeight; heightList.add(rowMaxHeight); } for (int i = 0; i < heightList.size(); i++) { Log.d(TAG, "onMeasure: widthLists:" + heightList.get(i)); } // Log.d(TAG, "onMeasure: viewList.size:"+viewLists.size()); // for (int i = 0; i < viewLists.size(); i++) { // List list=viewLists.get(i); // for (int j = 0; j


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3