1.View与ViewGroup
View是Android所有控件的基类 ViewGroup继承View是所有组合控件的基类
2.View坐标系
简单来说 view的getRawX 和getRawY方法是指拿到屏幕左上角的距离View的距离,getTop getLeft getBottom getRight 是指 view相对父控件距离,getX 和getY也是距离父控件距离
3.view 的滑动
(1) layout()方法是 四个参数 left top right bottom 分别对应view的是四个坐标进行滑动
(2)offsetLeftAndRight offsetTopAndBottom 简单一个参数 就是移动的正和负 分别左移还是右移上还是下
(3)LayoutParams 简单来说就是去view的父控件,然后拿到布局参数,设置layoutParams.leftMargin 之类的 然后设置进去
(4)使用动画去移动它比如使用属性动画
ObjectAnimator.ofFloat(mCustomView,"translationX",0,300).setDuration(1000)
(5) 使用scrollTo 与scrollBy
scrollTo表示移动到一个具体坐标,scrollBy表示移动距离参数都为 x和y 可以正数和负数,
(6)使用Scroller
我们再使用scrollTo和scrollBy的时候移动是瞬间完成的,如果想有过度的效果的话可以使用scroller 可以控制多长时间完成核心代码mScroller.startScroll(scrollx,0,delta,0,2000)
invalidate()
3.View的事件分发机制
当事件点击之后,事件首先传递当前Activity,调用activity的dispatchTouchEvent()分发事件,当然具体的事件是交给Activity的PhoneWindow然后PhoneWindow再交给DecorView再由DecorView交给根ViewGroup来,所以一切的分析从ViewGroup的dispatchTouchEvent分析起。
(1)dispatchTouchEvent 事件分发
核心源码(1)
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
事件分发机制,先判断是否是down事件,如果是的话,初始化会将上面代码的mFirstTouchTarget 设置为空 mFirstTouchTarget的意义是viewGroup是否拦截了 null 为拦截,如果是不拦截的话,会调用 onInterceptTouchEvent 这个方法默认是返回false是不拦截,如果需要viewGroup 去拦截的话 那么你就需要再子类重写返回true了 ,mFirstTouchTarget==null 如果拦截的话会返回true ,后面的所有操作都归这个viewGroup处理了
核心源码(2)
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
首先是一个倒序遍历判断子view是符合条件(比如在放动画,在不在父控件的范围内,不符合肯定不能有分发事件的资格啦),接下来有一个#### dispatchTransformedTouchEvent 这个方法 我们来看一下源码
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
如果有子控件 那么调用子控件的事件分发方法 如果没有子控件 那么调用父控件的事件分发方法我们来看一下view的事件分发方法是怎么设计的
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
我们可以看到 if (!result && onTouchEvent(event)) {result = true;} 表示如果事件被消费了就不会执行TouchEvent(e)方法 说明onTouch优先级要高于TouchEvent方法就是说触摸方法优先级要低于点击方法,接下来我们看一下TouchEvent 方法
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
###上面代码可以看到,只要View的click和longClick有一个为true 那么onTouchEvent就会返回true这个消费事件,ClickAble和longClickAble代表view可以被点击和长按点击 可以通过view的setClickable和setLongClickAble来设置,他们会将click和longClick设置为true 接下来action_Up事件中会调用performclick方法
我们来看一下performClick方法
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
在这个方法中很明显看出直接调用了 mOnClickListener的点击方法,所以我们的监听事件就收到了
总结点击事件分发规则
用伪代码来表示事假分发机制为
public boolean dispatchTouchEvent(MotionEvent ev){
boolean result=false
if(onInterceptTouchEvent(ev){
result=onTouchEvent(ev)
}else{
result=child.dispatchTouchEvent(ev)
}}
首先 我们来讲一下事件由上而下的传递规则,当事件从Activity后会传递到phoneWindow,再传递到decorView,最后传递到顶层的viewGroup,一般事件中考虑onInterceptTouchEvent()方法因为一般情况下我们不会重写dispatchTouchEvent对于根viewGroup我们首先传递到他的dispatchTouchEvent方法中如果onInterceptTouchEvent返回的是true那么说明需要拦截事件,那么事件就交给了onTouchEvent()方法去处理,如果onInterceptTouchEvent()返回的是false那么表示不拦截这个事件这个事件会交给它的子元素的dispatchTouchEvent来处理如此反复最后传递给最底层的view如果有点击事件就给点击事件处理 没有点击事件就给onTouchEvent()方法,
接下来我们将从下到上处理,如果最底层onTouchEvent返回是false就会传递给父view的onTouchEvent做处理这样一直反复循环下去,总体来说就是事件从上往下 再从下往上做处理
4.View的工作流程
1.知识点1.MeasureSpce 它是一个代表32位的一个东西 前面两位代表了SpceMode(测量模式) 后面30位代表SpecSize (测量大小)
SpceMode UNSPECIFIED 对应未指定模式 表示想多大就多大一般是用在手机系统中我们开发用不到
AT_MOST最大模式,对应wrap_content 子view最终大小是父控件指定的不能大于这个值
EXACTLY 精准模式,对应match_parent属性和具体指,父容器测量出的这个具体的值
对于每个view都持有一个MeasureSpec这个东西保存了该view尺寸规格 宽高
view的measure()方法
view的measure()是测量view的宽高,viewGroup的measure()是测量所有子view的宽高
(1) 我们看一下view测量的源码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
specMode是测量模式,specSize是测量长度就是说在 AT_MOST和EXACTLY下是返回specSize所以说如何直接继承view的话match_patent和wrap_content是一样的如果要实现wrap_content效果的话需要自己去实现
例如实现方法为
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
super.onMeasure(int widthMeasureSpec,int heightMeasureSpec)
int widthSpecMode= MeasureSpec.getMode(widthMeasureSpec)
int heightSpecMode= MeasureSpec.getMode(heightMeasureSpec)
int heightSpecSize= MeasureSpec.getSize(heightMeasureSpec)
int widthSpecSize= MeasureSpec.getSize(widthMeasureSpec)
if(heightSpecMode==MeasureSpec.AT_MOST&&widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(600,600)
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(600,heightMeasureSpec)
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthMeasureSpec,600)
}
}
(4)接下来我们看一下ViewGroup实现方式简单来说就是ViewGroup不但要测量自己 还要测量子控件的大小
(1)源码
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {//遍历获取子View调用其测量方法
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();//获取父组件的参数 调用measure方法
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);//获取子View的宽度和高度
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
子View的layout方法
layout方法是用来确定元素的位置的 layout 的四个参数分别代表左上右下相对父容器的距离,在setFrame方法的时候会调用onLayout方法,这这个是一个空的方法 让子类去实现,一般情况下只有自定义viewGroup这类的控件才会使用layout来摆放控件,我们就看一下LinearLayout的onLayout实现
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
我们先分析layoutVertical
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
这个方法会遍历子元素设置setChildFrame其中childTop是不断累加这样元素就会不重叠子一起,接着看setChildFrame
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
其实就是调用了layout方法来摆放位置
(2)view的draw流程
View的draw 流程还是比较简单的
(1) 如果需要 就绘制背景
(2) 如果需要 保存当前canvas层
(3)绘制view的内容
(4)如果需要 绘制View褪色边缘,这类似阴影效果
(5)绘制装饰
绘制背景(1) 绘制背景调用了view 的drawBackground方法
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mHardwareRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
步骤3 绘制view的内容调用view的onDraw方法这个方法是空实现,因为不同的内容这个需要我们自己去实现,在自定义view中重写该方法的实现
protected void onDraw(Canvas canvas){}
步骤4 绘制子View的dispatchDraw方法 这个方法是一个空实现
ViewGroup重写了这个方法,紧接着看看ViewGroup的dispatchView方法,其实就是对子View进行遍历,然后调用drawChild方法 这里主要调用了view Draw方法
自定义View
自定义控件分三大类 一种是自定义View 一种是自定义ViewGroup一种是组合自定义控件
(1)继承系统的自定义控件,这种 其实是在已有的控件上做了一直扩展,比如在有的TextView上画出一条中线,那么就是定义一个类继承TextView,初始化 一个画笔 然后在onDraw方法中画出一条线
(2)继承View的自定义View
这种稍微复杂一点 不止要实现onDraw还要考虑onMeasure,考虑到wrap_content padding margin等属性还会对外提供属性 甚至要重写OnTouchEvent方法 大概流程是
(1)初始化构造方法 初始化一个画笔 实现OnDraw方法 实现onMeasure方法 这些方法还要考虑padding和margin的处理
public void onDraw(cancas){
int paddingleft=getPaddingLeft()
int paddingRight=getPaddingRight()
int paddingTop=getPaddingTop()
int paddingBottom=getPaddingBottom
int width=getWidth()-paddingLeft-paddingRight
int heigth=getHeigth()-paddingTop-paddingBottom
canvas.DrawRect(0+paddingleft,0+paddingRight,width+paddingleft,heigth+paddingRight,mPaint)
}
比如对wrap_content的处理
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
super.onMeasure(int widthMeasureSpec,int heightMeasureSpec)
int widthSpecMode= MeasureSpec.getMode(widthMeasureSpec)
int heightSpecMode= MeasureSpec.getMode(heightMeasureSpec)
int heightSpecSize= MeasureSpec.getSize(heightMeasureSpec)
int widthSpecSize= MeasureSpec.getSize(widthMeasureSpec)
if(heightSpecMode==MeasureSpec.AT_MOST&&widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(600,600)
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(600,heightMeasureSpec)
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthMeasureSpec,600)
}
}