title: 自定義ViewGroup
tags: ViewGroup,Android,自定義View
初始化
- 重寫構(gòu)造函數(shù)——三個
- 通過this調(diào)用
- init來獲取自定義的屬性
public SwipeLayout(Context context) {
this(context,null);
}
public SwipeLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
獲取自定義屬性
布局文件 attr.xml
<declare-styleable name="SwipeLayout">
<attr name="leftView" format="reference"/>
<attr name="rightView" format="reference"/>
<attr name="contentView1" format="reference"/>
<attr name="canRightSwipe1" format="boolean" />
<attr name="canLeftSwipe1" format="boolean" />
<attr name="fraction1" format="float" />
</declare-styleable>
獲取屬性
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.SwipeLayout);
leftViewId = typedArray.getResourceId(R.styleable.SwipeLayout_leftView,-1);
rightViewId = typedArray.getResourceId(R.styleable.SwipeLayout_rightView,-1);
contentViewId = typedArray.getResourceId(R.styleable.SwipeLayout_contentView1,-1);
canLeftSwipe = typedArray.getBoolean(R.styleable.SwipeLayout_canLeftSwipe1,true);
canRightSwipe = typedArray.getBoolean(R.styleable.SwipeLayout_canRightSwipe1,true);
mFraction = typedArray.getFloat(R.styleable.SwipeLayout_fraction1,0.5f);
typedArray.recycle();
onMeasure
思路
- View是match_parent 的特殊處理,其他的正常處理
- 正常的最終需要調(diào)用 setMeasuredDimension 方法,需要的參數(shù) 寬高、寬高的spec以及state
- match_parent 的最終需要調(diào)用 measure方法,需要參數(shù)widthMeasureSpec, heightMeasureSpec
- onMeasure前面所做的事情都是來獲取這些參數(shù)
setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState)
,resolveSizeAndState(maxHeight,heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));
view.measure(childWidthMeasureSpec,childHeightMeasureSpec);
步驟
- 獲取子View的數(shù)量
代碼
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//1. 獲取childView的個數(shù)
setClickable(true);
int count = getChildCount();
//參考frameLayout測量代碼
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//遍歷childViews
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChildWithMargins(child,widthMeasureSpec,0,heightMeasureSpec,0);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
maxHeight = Math.max(maxHeight,child.getMeasuredHeight()+lp.leftMargin+lp.rightMargin);
maxWidth = Math.max(maxWidth,child.getMeasuredWidth()+lp.topMargin+lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren){
if ( lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
//寬度和高度還要考慮背景的大小
maxHeight = Math.max(maxHeight,getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth,getSuggestedMinimumWidth());
// 設(shè)置具體寬高
setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState)
,resolveSizeAndState(maxHeight,heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
View view = mMatchParentChildren.get(i);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
final int childWidthMeasureSpec;
if ( lp.width == LayoutParams.MATCH_PARENT) {
int width = Math.max(0,getMeasuredWidth()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY);
}else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,lp.leftMargin+lp.rightMargin
,lp.width);
}
final int childHeightMeasureSpec;
if ( lp.height == LayoutParams.MATCH_PARENT) {
int height = Math.max(0,getMeasuredHeight()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
}else{
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,lp.topMargin+lp.bottomMargin
,lp.height);
}
view.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
}
}
onLayout
思路
- 調(diào)用layout方法,需要四個參數(shù)
view.layout(rLeft,rTop,rRight,rBottom)
padding——內(nèi)邊距
margin——外邊距
代碼
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 1. 先找到布局中的View
int count = getChildCount();
int left = 0 + getPaddingLeft();
int right = 0 + getPaddingLeft();
int top = 0 + getPaddingTop();
int bottom = 0 + getPaddingTop();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (mLeftView == null && child.getId() == leftViewId) {
mLeftView = child;
mLeftView.setClickable(true);
}else if (mRightView == null && child.getId() == rightViewId){
mRightView = child;
mRightView.setClickable(true);
}else if (mContentView == null && child.getId() == contentViewId){
mContentView = child;
mContentView.setClickable(true);
}
}
//2. 設(shè)置放置的位置
if (mContentView != null) {
mContentViewLp = (MarginLayoutParams) mContentView.getLayoutParams();
int cTop = mContentViewLp.topMargin + top;
int cLeft = mContentViewLp.leftMargin + left;
int cRight = cLeft + mContentView.getMeasuredWidth();
//TODO 此處能不能使用 ??? mContentViewLp.height
int cBottom = cTop + mContentView.getMeasuredHeight();
mContentView.layout(cLeft, cTop, cRight, cBottom);
}
if (mLeftView != null) {
MarginLayoutParams leftViewLp = (MarginLayoutParams) mLeftView.getLayoutParams();
int cTop = leftViewLp.topMargin + top;
int cRight = 0 - leftViewLp.rightMargin ;
int cLeft = 0 - mLeftView.getMeasuredWidth() + leftViewLp.rightMargin + leftViewLp.leftMargin;
int cBottom = top + mLeftView.getMeasuredHeight() + leftViewLp.bottomMargin;
mLeftView.layout(cLeft,cTop,cRight,cBottom);
}
if (mRightView != null) {
MarginLayoutParams rightViewLp = (MarginLayoutParams) mRightView.getLayoutParams();
int rTop = rightViewLp.topMargin + top;
int rLeft = rightViewLp.leftMargin + mContentView.getRight() + mContentViewLp.rightMargin;
int rRight = rLeft + mRightView.getMeasuredWidth();
int rBottom = rTop + mRightView.getMeasuredHeight();;
mRightView.layout(rLeft,rTop,rRight,rBottom);
}
}
幾個常用函數(shù)
getX() 是表示W(wǎng)idget相對于自身左上角的x坐標(biāo)
getRawX() 是表示相對于屏幕左上角的x坐標(biāo)值
理解 getScrollX()
1是手機(jī)屏幕,在此區(qū)域內(nèi)的人眼可以看見
2是幕布
3是內(nèi)容
getScrollX 其實獲取的值,就是這塊幕布在窗口左邊界時候的值了,而幕布上面哪個點(diǎn)是原點(diǎn)(0,0)呢?就是初始化時內(nèi)容顯示的位置。
- 將幕布往右推動的時候,幕布在窗口左邊界的值就會在0的左邊(-100)
- 向左推動,則其值會是在0的右邊(100)
scrollTo()和scrollBy(x,y)
scrollTo(int x, int y) 是將View中內(nèi)容滑動到相應(yīng)的位置,參考的坐標(biāo)系原點(diǎn)為parent View的左上角。
- 參數(shù)為正的,右移
- 參數(shù)為負(fù)值,左移
scrollTo()指的是移動到指定的(x,y)位置
而scrollBy(x,y)指的是,在當(dāng)前位置在移動(x,y)個位置
阻止父層的View截獲touch事件
調(diào)用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底層View收到touch的action后調(diào)用這個方法那么父層View就不會再調(diào)用onInterceptTouchEvent了,也無法截獲以后的action
ViewGroup generateLayoutParams() 方法的作用
父容器生成 子view 的布局LayoutParams;
如果一個View想要被添加到這個容器中,這個view可以調(diào)用此方法生成和容器類匹配的布局LayoutParams,
這個方法主要是用于父容器添加子View時調(diào)用。
用于生成和此容器類型相匹配的布局參數(shù)類
startScroll方法
第一個參數(shù)是起始移動的x坐標(biāo)值,第二個是起始移動的y坐標(biāo)值,第三個第四個參數(shù)都是移到某點(diǎn)的坐標(biāo)值,而duration 當(dāng)然就是執(zhí)行移動的時間
computeScroll方法
當(dāng)startScroll執(zhí)行過程中即在duration時間內(nèi),computeScrollOffset 方法會一直返回false,但當(dāng)動畫執(zhí)行完成后會返回返加true.
@Override
public void computeScroll() {
//判斷Scroller是否執(zhí)行完畢:
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//通知View重繪-invalidate()->onDraw()->computeScroll()
invalidate();
}
}
onDetachedFromWindow()
銷毀View的時候調(diào)用這個方法,我們可以在里面做一些清理工作(做一些收尾工作)如:取消廣播注冊等等