Android自定义控件<二>
来源:互联网 发布:js placeholder颜色 编辑:程序博客网 时间:2024/06/05 06:26
PercentFrameLayout源码分析
开始学习继承ViewGroup的自定义控件,还是从分析源码开始,这次挑了一个谷歌官方的PercentFrameLayout来学习。
首先 PercentFrameLayout 继承FrameLayout,重写了以下方法:
@Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHelper.handleMeasuredStateTooSmall()) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mHelper.restoreOriginalParams(); }
generateDefaultLayoutParams() 和generateLayoutParams(AttributeSet attrs) 是来生成自己所定义扩展的LayoutParams,再看它实现方法中的内部类LayoutParams代码:
public static class LayoutParams extends FrameLayout.LayoutParams implements PercentLayoutHelper.PercentLayoutParams { private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(int width, int height, int gravity) { super(width, height, gravity); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public LayoutParams(MarginLayoutParams source) { super(source); } public LayoutParams(FrameLayout.LayoutParams source) { super((MarginLayoutParams) source); gravity = source.gravity; } public LayoutParams(LayoutParams source) { this((FrameLayout.LayoutParams) source); mPercentLayoutInfo = source.mPercentLayoutInfo; } @Override public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() { if (mPercentLayoutInfo == null) { mPercentLayoutInfo = new PercentLayoutHelper.PercentLayoutInfo(); } return mPercentLayoutInfo; } @Override protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr); } }
通过源码看出LayoutParams 除了继承父类的FrameLayout.LayouParams还实现了自己定义的一个接口PercentLayoutHelper.PercentLayoutParams。方法中setBaseAttributes是重写ViewGroup的方法,这个方法的意思就是从属性中提取Params; getPercentLayoutInfo是实现PercentLayoutParams的方法,目的是返回一个自定义的PercentLayoutInfo,PercentLayoutInfo这个类定义了PercentLayout的各种属性,以及设置这些属性:
public static class PercentLayoutInfo { /** The decimal value of the percentage-based width. */ public float widthPercent; /** The decimal value of the percentage-based height. */ public float heightPercent; /** The decimal value of the percentage-based left margin. */ public float leftMarginPercent; /** The decimal value of the percentage-based top margin. */ public float topMarginPercent; /** The decimal value of the percentage-based right margin. */ public float rightMarginPercent; /** The decimal value of the percentage-based bottom margin. */ public float bottomMarginPercent; /** The decimal value of the percentage-based start margin. */ public float startMarginPercent; /** The decimal value of the percentage-based end margin. */ public float endMarginPercent; /** The decimal value of the percentage-based aspect ratio. */ public float aspectRatio; /* package */ final PercentMarginLayoutParams mPreservedParams; public PercentLayoutInfo() { widthPercent = -1f; heightPercent = -1f; leftMarginPercent = -1f; topMarginPercent = -1f; rightMarginPercent = -1f; bottomMarginPercent = -1f; startMarginPercent = -1f; endMarginPercent = -1f; mPreservedParams = new PercentMarginLayoutParams(0, 0); } /** * Fills the {@link ViewGroup.LayoutParams#width} and {@link ViewGroup.LayoutParams#height} * fields of the passed {@link ViewGroup.LayoutParams} object based on currently set * percentage values. */ public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint, int heightHint) { // Preserve the original layout params, so we can restore them after the measure step. mPreservedParams.width = params.width; mPreservedParams.height = params.height; // We assume that width/height set to 0 means that value was unset. This might not // necessarily be true, as the user might explicitly set it to 0. However, we use this // information only for the aspect ratio. If the user set the aspect ratio attribute, // it means they accept or soon discover that it will be disregarded. final boolean widthNotSet = (mPreservedParams.mIsWidthComputedFromAspectRatio || mPreservedParams.width == 0) && (widthPercent < 0); final boolean heightNotSet = (mPreservedParams.mIsHeightComputedFromAspectRatio || mPreservedParams.height == 0) && (heightPercent < 0); if (widthPercent >= 0) { params.width = Math.round(widthHint * widthPercent); } if (heightPercent >= 0) { params.height = Math.round(heightHint * heightPercent); } if (aspectRatio >= 0) { if (widthNotSet) { params.width = Math.round(params.height * aspectRatio); // Keep track that we've filled the width based on the height and aspect ratio. mPreservedParams.mIsWidthComputedFromAspectRatio = true; } if (heightNotSet) { params.height = Math.round(params.width / aspectRatio); // Keep track that we've filled the height based on the width and aspect ratio. mPreservedParams.mIsHeightComputedFromAspectRatio = true; } } if (DEBUG) { Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")"); } } /** * @deprecated Use * {@link #fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)} * for proper RTL support. */ @Deprecated public void fillMarginLayoutParams(ViewGroup.MarginLayoutParams params, int widthHint, int heightHint) { fillMarginLayoutParams(null, params, widthHint, heightHint); } /** * Fills the margin fields of the passed {@link ViewGroup.MarginLayoutParams} object based * on currently set percentage values and the current layout direction of the passed * {@link View}. */ public void fillMarginLayoutParams(View view, ViewGroup.MarginLayoutParams params, int widthHint, int heightHint) { fillLayoutParams(params, widthHint, heightHint); // Preserve the original margins, so we can restore them after the measure step. mPreservedParams.leftMargin = params.leftMargin; mPreservedParams.topMargin = params.topMargin; mPreservedParams.rightMargin = params.rightMargin; mPreservedParams.bottomMargin = params.bottomMargin; MarginLayoutParamsCompat.setMarginStart(mPreservedParams, MarginLayoutParamsCompat.getMarginStart(params)); MarginLayoutParamsCompat.setMarginEnd(mPreservedParams, MarginLayoutParamsCompat.getMarginEnd(params)); if (leftMarginPercent >= 0) { params.leftMargin = Math.round(widthHint * leftMarginPercent); } if (topMarginPercent >= 0) { params.topMargin = Math.round(heightHint * topMarginPercent); } if (rightMarginPercent >= 0) { params.rightMargin = Math.round(widthHint * rightMarginPercent); } if (bottomMarginPercent >= 0) { params.bottomMargin = Math.round(heightHint * bottomMarginPercent); } boolean shouldResolveLayoutDirection = false; if (startMarginPercent >= 0) { MarginLayoutParamsCompat.setMarginStart(params, Math.round(widthHint * startMarginPercent)); shouldResolveLayoutDirection = true; } if (endMarginPercent >= 0) { MarginLayoutParamsCompat.setMarginEnd(params, Math.round(widthHint * endMarginPercent)); shouldResolveLayoutDirection = true; } if (shouldResolveLayoutDirection && (view != null)) { // Force the resolve pass so that start / end margins are propagated to the // matching left / right fields MarginLayoutParamsCompat.resolveLayoutDirection(params, ViewCompat.getLayoutDirection(view)); } if (DEBUG) { Log.d(TAG, "after fillMarginLayoutParams: (" + params.width + ", " + params.height + ")"); } } @Override public String toString() { return String.format("PercentLayoutInformation width: %f height %f, margins (%f, %f, " + " %f, %f, %f, %f)", widthPercent, heightPercent, leftMarginPercent, topMarginPercent, rightMarginPercent, bottomMarginPercent, startMarginPercent, endMarginPercent); } /** * Restores the original dimensions and margins after they were changed for percentage based * values. You should call this method only if you previously called * {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)}. */ public void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params) { restoreLayoutParams(params); params.leftMargin = mPreservedParams.leftMargin; params.topMargin = mPreservedParams.topMargin; params.rightMargin = mPreservedParams.rightMargin; params.bottomMargin = mPreservedParams.bottomMargin; MarginLayoutParamsCompat.setMarginStart(params, MarginLayoutParamsCompat.getMarginStart(mPreservedParams)); MarginLayoutParamsCompat.setMarginEnd(params, MarginLayoutParamsCompat.getMarginEnd(mPreservedParams)); } /** * Restores original dimensions after they were changed for percentage based values. * You should call this method only if you previously called * {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams(ViewGroup.LayoutParams, int, int)}. */ public void restoreLayoutParams(ViewGroup.LayoutParams params) { if (!mPreservedParams.mIsWidthComputedFromAspectRatio) { // Only restore the width if we didn't compute it based on the height and // aspect ratio in the fill pass. params.width = mPreservedParams.width; } if (!mPreservedParams.mIsHeightComputedFromAspectRatio) { // Only restore the height if we didn't compute it based on the width and // aspect ratio in the fill pass. params.height = mPreservedParams.height; } // Reset the tracking flags. mPreservedParams.mIsWidthComputedFromAspectRatio = false; mPreservedParams.mIsHeightComputedFromAspectRatio = false; } }
还剩下最后一个类PercentLayoutHelper,这个类就是完成百分比布局的类:
public class PercentLayoutHelper { private static final String TAG = "PercentLayout"; private static final boolean DEBUG = false; private static final boolean VERBOSE = false; private final ViewGroup mHost; public PercentLayoutHelper(@NonNull ViewGroup host) { if (host == null) { throw new IllegalArgumentException("host must be non-null"); } mHost = host; } /** * Helper method to be called from {@link ViewGroup.LayoutParams#setBaseAttributes} override * that reads layout_width and layout_height attribute values without throwing an exception if * they aren't present. */ public static void fetchWidthAndHeight(ViewGroup.LayoutParams params, TypedArray array, int widthAttr, int heightAttr) { params.width = array.getLayoutDimension(widthAttr, 0); params.height = array.getLayoutDimension(heightAttr, 0); } /** * Iterates over children and changes their width and height to one calculated from percentage * values. * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup. * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup. */ public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) { if (DEBUG) { Log.d(TAG, "adjustChildren: " + mHost + " widthMeasureSpec: " + View.MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: " + View.MeasureSpec.toString(heightMeasureSpec)); } // Calculate available space, accounting for host's paddings int widthHint = View.MeasureSpec.getSize(widthMeasureSpec) - mHost.getPaddingLeft() - mHost.getPaddingRight(); int heightHint = View.MeasureSpec.getSize(heightMeasureSpec) - mHost.getPaddingTop() - mHost.getPaddingBottom(); for (int i = 0, N = mHost.getChildCount(); i < N; i++) { View view = mHost.getChildAt(i); ViewGroup.LayoutParams params = view.getLayoutParams(); if (DEBUG) { Log.d(TAG, "should adjust " + view + " " + params); } if (params instanceof PercentLayoutParams) { PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo(); if (DEBUG) { Log.d(TAG, "using " + info); } if (info != null) { if (params instanceof ViewGroup.MarginLayoutParams) { info.fillMarginLayoutParams(view, (ViewGroup.MarginLayoutParams) params, widthHint, heightHint); } else { info.fillLayoutParams(params, widthHint, heightHint); } } } } } /** * Constructs a PercentLayoutInfo from attributes associated with a View. Call this method from * {@code LayoutParams(Context c, AttributeSet attrs)} constructor. */ public static PercentLayoutInfo getPercentLayoutInfo(Context context, AttributeSet attrs) { PercentLayoutInfo info = null; TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout); float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent width: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.widthPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent height: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.heightPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.leftMarginPercent = value; info.topMarginPercent = value; info.rightMarginPercent = value; info.bottomMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent left margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.leftMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent top margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.topMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent right margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.rightMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent bottom margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.bottomMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent start margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.startMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "percent end margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.endMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_aspectRatio, 1, 1, -1f); if (value != -1f) { if (VERBOSE) { Log.v(TAG, "aspect ratio: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.aspectRatio = value; } array.recycle(); if (DEBUG) { Log.d(TAG, "constructed: " + info); } return info; } /** * Iterates over children and restores their original dimensions that were changed for * percentage values. Calling this method only makes sense if you previously called * {@link PercentLayoutHelper#adjustChildren(int, int)}. */ public void restoreOriginalParams() { for (int i = 0, N = mHost.getChildCount(); i < N; i++) { View view = mHost.getChildAt(i); ViewGroup.LayoutParams params = view.getLayoutParams(); if (DEBUG) { Log.d(TAG, "should restore " + view + " " + params); } if (params instanceof PercentLayoutParams) { PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo(); if (DEBUG) { Log.d(TAG, "using " + info); } if (info != null) { if (params instanceof ViewGroup.MarginLayoutParams) { info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params); } else { info.restoreLayoutParams(params); } } } } } /** * Iterates over children and checks if any of them would like to get more space than it * received through the percentage dimension. * * If you are building a layout that supports percentage dimensions you are encouraged to take * advantage of this method. The developer should be able to specify that a child should be * remeasured by adding normal dimension attribute with {@code wrap_content} value. For example * he might specify child's attributes as {@code app:layout_widthPercent="60%p"} and * {@code android:layout_width="wrap_content"}. In this case if the child receives too little * space, it will be remeasured with width set to {@code WRAP_CONTENT}. * * @return True if the measure phase needs to be rerun because one of the children would like * to receive more space. */ public boolean handleMeasuredStateTooSmall() { boolean needsSecondMeasure = false; for (int i = 0, N = mHost.getChildCount(); i < N; i++) { View view = mHost.getChildAt(i); ViewGroup.LayoutParams params = view.getLayoutParams(); if (DEBUG) { Log.d(TAG, "should handle measured state too small " + view + " " + params); } if (params instanceof PercentLayoutParams) { PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo(); if (info != null) { if (shouldHandleMeasuredWidthTooSmall(view, info)) { needsSecondMeasure = true; params.width = ViewGroup.LayoutParams.WRAP_CONTENT; } if (shouldHandleMeasuredHeightTooSmall(view, info)) { needsSecondMeasure = true; params.height = ViewGroup.LayoutParams.WRAP_CONTENT; } } } } if (DEBUG) { Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure); } return needsSecondMeasure; } private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info) { int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK; return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent >= 0 && info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT; } private static boolean shouldHandleMeasuredHeightTooSmall(View view, PercentLayoutInfo info) { int state = ViewCompat.getMeasuredHeightAndState(view) & ViewCompat.MEASURED_STATE_MASK; return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.heightPercent >= 0 && info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT; }
现在挨个说他的方法:
/** * Helper method to be called from {@link ViewGroup.LayoutParams#setBaseAttributes} override * that reads layout_width and layout_height attribute values without throwing an exception if * they aren't present. */ public static void fetchWidthAndHeight(ViewGroup.LayoutParams params, TypedArray array, int widthAttr, int heightAttr) { }
我理解英文的意思是:这个方法被ViewGroup.LayoutParams#setBaseAttributes调用,从属性读取宽和高属性。
/** * Iterates over children and changes their width and height to one calculated from percentage * values. * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup. * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup. */ public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec)
作用:计算调整子View的宽和高
/** * Constructs a PercentLayoutInfo from attributes associated with a View. Call this method from * {@code LayoutParams(Context c, AttributeSet attrs)} constructor. */ public static PercentLayoutInfo getPercentLayoutInfo(Context context, AttributeSet attrs) {
作用:从属性中读取参数,设置给PercentLayoutInfo
/** * Iterates over children and restores their original dimensions that were changed for * percentage values. Calling this method only makes sense if you previously called * {@link PercentLayoutHelper#adjustChildren(int, int)}. */ public void restoreOriginalParams()
作用:保存属性
handleMeasuredStateTooSmall() 作用:重新测量子View的空间,判断子View是否需要调整。
- Android自定义控件二
- Android自定义控件<二>
- android 自定义控件之二
- Android自定义简单控件(二)
- Android 自定义控件(二)
- android 自定义控件(二)
- android 自定义控件(二)
- Android自定义控件(二)
- Android自定义控件(二)组合控件
- Android控件架构与自定义控件(二)
- Android 控件架构与自定义控件(二)
- 【android自定义控件】button样式自定义<二>
- Android自定义控件之自定义View(二)
- android自定义控件(二) 入门,继承View
- android自定义控件(二) 入门,继承View
- Android 自定义控件开发入门(二)
- Android 自定义控件开发入门(二)
- android自定义注解初始化布局控件(二)
- 欢迎使用CSDN-markdown编辑器
- 怎样在一个项目里用logger在控制台打印信息
- 关于安卓端集成高德3D地图的那些事...实时定位+展示用户的运动轨迹
- CocoaPods安装最新流程
- design库下的CollapsingToolbarLayout和Toolbar加RecyclerView compileSdkVersion 24
- Android自定义控件<二>
- Runtime全方位装逼指南
- 如何解决wampsever 中mysql无法显示中文的问题
- 380. Insert Delete GetRandom O(1)
- git学习(2) 远程连接github
- jms中消息接收的实现原理
- 私人整理mysql笔记
- ES6/ES2015核心内容
- linux 手动源码安装lnmp(亲测)