Android自定義ViewGroup(一)
自定義viewgroup,這個東西可以說簡單也簡單,說複雜也複雜。主要是因為用到所以複習了一下,那就順便做個筆記。
暫時只講簡單的用法
一.重要方法
(1)onMeasure 設定viewgroup的大小
(2)onLayout 設定如何擺放子View
(3)generateLayoutParams 設定LayoutParams
最重要的是前面兩個方法,所以說viewgroup很簡單,你只需要知道在onMeasure 和 onLayout中寫什麼內容就行。
注:這裡說的自定義viewgroup是值直接繼承ViewGroup,而不是繼承各種Layout之類已封裝好的ViewGroup。
1.確定自己要做的viewgroup是怎麼樣的
首先要確認自己要做出怎樣的viewgroup,因為onMeasure和onLayout 可以說是關聯很小的,他們是配合使用才能出現自己想要的效果,如果不注意細節的話很容易弄錯,所以要先確定自己想做出來的viewgroup是怎樣的,才開始做。
2.onMeasure
首先要記好自定義onMeasure的流程,他會先呼叫onMeasure再呼叫onLayout,而onMeasure會呼叫多次,這個以後講。
(1)onMeasure做的事很簡單,就是測量ViewGroup的大小,準確來說是根據子View來測量viewgroup的大小。所以在onMeasure方法裡面一般會用measureChildren去測量子view的大小。
(2)onMeasure方法中一般要分兩種情況去測量,viewgroup在xml中定義時,寬高是不是wrap_content
你想想,如果viewgroup固定寬高或者填充父佈局的話,那實際中的寬高肯定是你定義的,但是如果是wrap_content的話,你就需要自己去設定寬高讓它包裹所有子View,所以自定義viewgroup的onMeasure中會分兩種情況去setMeasuredDimension寬高
2.onLayout
設定完viewgroup的寬高之後,就要去擺放子view。
(1)擺放子View的規則是,設定這個view的左上角的點在viewgroup的位置:
child.layout(left, top, right,bottom);
而根據這個座標點和寬高,我們就能在viewgroup中正確的擺放子view
(2)需要注意的是如果子view超出了viewgroup所onMeasure(設定好大小)的部分,那部分不會顯示出來。
(3)獲取子view的方法View child = getChildAt(i); 得到的view一般是addview時新增view的順序,但是還有特殊情況,這個過後再解釋。
3.generateLayoutParams
設定LayoutParams,那麼LayoutParams是什麼東西,一般我們給viewgroup新增view都會用到LayoutParams。
翻譯過來就是佈局引數,通俗點說就是能獲取到佈局一些特定的屬性,比如說佈局的邊距什麼的。反正你正著想,在建立view時LayoutParams設定的屬性,在自定義Viewgroup中都能拿到。
這個類系統有很多子類,包括如果你牛逼的話你可以依照谷歌的這種做法,可以自定義LayoutParams,所以具體情況再說。
二.demo
逼逼了這麼多,還是應該拿個例子來說,比如說流式佈局
流式佈局可以用自定義viewgroup來實現,雖然它也可以用recyclerview來實現,但是它的性質和RelativeLayout這些佈局一樣,應該是一個viewgroup。
我就找了網上一個來說啊,因為我懶得寫演算法。
public class BerFlowLayout extends ViewGroup {
//儲存所有子View
private List<List<View>> mAllChildViews = new ArrayList<>();
//每一行的高度
private List<Integer> mLineHeight = new ArrayList<>();
public BerFlowLayout(Context context) {
super(context);
}
public BerFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BerFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//父控制元件傳進來的寬度和高度以及對應的測量模式
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//如果當前ViewGroup的寬高為wrap_content的情況
int width = 0;//自己測量的 寬度
int height = 0;//自己測量的高度
//記錄每一行的寬度和高度
int lineWidth = 0;
int lineHeight = 0;
//獲取子view的個數
int childCount = getChildCount();
for(int i = 0;i < childCount; i ++){
View child = getChildAt(i);
//測量子View的寬和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//得到LayoutParams
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//子View佔據的寬度
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
//子View佔據的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
//換行時候
if(lineWidth + childWidth > sizeWidth){
//對比得到最大的寬度
width = Math.max(width, lineWidth);
//重置lineWidth
lineWidth = childWidth;
//記錄行高
height += lineHeight;
lineHeight = childHeight;
}else{//不換行情況
//疊加行寬
lineWidth += childWidth;
//得到最大行高
lineHeight = Math.max(lineHeight, childHeight);
}
//處理最後一個子View的情況
if(i == childCount -1){
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
//wrap_content
setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width,
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllChildViews.clear();
mLineHeight.clear();
//獲取當前ViewGroup的寬度
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
//記錄當前行的view
List<View> lineViews = new ArrayList<View>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
//如果需要換行
if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width) {
//記錄LineHeight
mLineHeight.add(lineHeight);
//記錄當前行的Views
mAllChildViews.add(lineViews);
//重置行的寬高
lineWidth = 0;
lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
//重置view的集合
lineViews = new ArrayList();
}
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
lineViews.add(child);
}
//處理最後一行
mLineHeight.add(lineHeight);
mAllChildViews.add(lineViews);
//設定子View的位置
int left = 0;
int top = 0;
//獲取行數
int lineCount = mAllChildViews.size();
for (int i = 0; i < lineCount; i++) {
//當前行的views和高度
lineViews = mAllChildViews.get(i);
lineHeight = mLineHeight.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
//判斷是否顯示
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int cLeft = left + lp.leftMargin;
int cTop = top + lp.topMargin;
int cRight = cLeft + child.getMeasuredWidth();
int cBottom = cTop + child.getMeasuredHeight();
//進行子View進行佈局
child.layout(cLeft, cTop, cRight, cBottom);
left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
left = 0;
top += lineHeight;
}
}
/**
* 與當前ViewGroup對應的LayoutParams
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
}
1.onMeasure
先看onMeasure,看看它怎麼測量整體父佈局的。這裡迴圈處理子view,先獲取到子view的大小
//子View佔據的寬度
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
//子View佔據的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
然後判斷換不換行,如果這個子view的寬度加上前面累計起來的比父佈局的寬度寬,那就加一行。
其實這裡的onMeasure意圖很容易看懂,它的作用就是根據子view來決定高度,所以為什麼我之前說要先弄清楚你想做怎麼樣的效果,比如這裡,我想做的效果就是流式佈局的效果,那這個佈局的高度肯定是根據有多少行來動態決定的吧,而這裡的計算就是覺得這個高度的過程。
1.onLayout
這個他這裡寫得有點麻煩,應該是可以再縮短一些的。
如果累加的寬度+當前子view的寬度+間距 > 一行的寬度,則換行
if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width) {
}
換行就設定累計寬度為0: lineWidth = 0;
mAllChildViews就是它儲存的第一個裝view的二維陣列。
然後再對每一行進行操作 for (int i = 0; i < lineCount; i++) {…},然後對left 和top 進行疊加操作。
其實我覺得這裡可以在最上面的迴圈中就直接child.layout對子View進行佈局,不用兩次迴圈。他這裡的思路是第一次大迴圈來獲取行數並儲存二維陣列,第二次大迴圈再設定子view位置。
3.MarginLayoutParams
這裡寫的
/**
* 與當前ViewGroup對應的LayoutParams
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
是為了設定子view和子view間通過margin設定的間距。
4.呼叫
如果在xml中寫子佈局,可以直接用,這時設定generateLayoutParams會預設呼叫3個種的這個方法,你不用去關係LayoutParams。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
但是如果是動態去新增view,你就需要自己去寫MarginLayoutParams,那麼可以這樣寫。
ViewGroup.LayoutParams lp = new ViewGroup.
LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(lp);
mlp.setMargins(10,10,10,10);
textView.setLayoutParams(mlp);
三.總結
最後做個小結吧。你可以看成自定義ViewGroup不難,它就要求你會用兩個方法去測量和擺放,難的是什麼呢?難的是你要怎麼去寫演算法來完成你這個viewgroup的實現,也就是兩個方法中具體的程式碼實現,還有就是這兩個方法連起來的效果和與LayoutParams配合的效果,主要是onMeasure和onLayout的配合,要非常的注意細節,比如你在onLayout設定間距,但是在onMeasure沒有去開闢這個間距所需要的空間,那就會出問題,這種寫多就會清楚了。
相關文章
- android自定義View&自定義ViewGroup(下)AndroidView
- android自定義View&自定義ViewGroup(上)AndroidView
- Android自定義View:ViewGroup(三)AndroidView
- Android ViewDragHelper 自定義 ViewGroup 神器AndroidView
- ViewGroup篇:玩一下自定義ViewGroupView
- Android ViewDragHelper完全解析 自定義ViewGroup神器AndroidView
- Android自定義控制元件之自定義ViewGroup實現標籤雲Android控制元件View
- 一篇文章搞懂Android 自定義viewgroup的難點AndroidView
- Android動畫效果之自定義ViewGroup新增布局動畫Android動畫View
- Android開發教程:自定義ViewGroup方法總結AndroidView
- android 實現FlowLayout 流線佈局(自定義ViewGroup)AndroidView
- 自定義ViewGroup,實現Android的側滑選單ViewAndroid
- Android自定義ViewGroup View的大小和座標控制AndroidView
- android view 自定義viewgroup 例項--螢幕滑動AndroidView
- Android 中自定義 View、ViewGroup 理論基礎詳解AndroidView
- Android中自定義View、ViewGroup理論基礎詳解AndroidView
- 二、自定義垂直ViewGroup如何設定marginView
- Android進階之自定義ViewGroup—帶你一步步輕鬆實現ViewPagerAndroidViewpager
- 自定義viewgroup(6)--使用adapter適配資料ViewAPT
- 快速開發偷懶必備,搞定所有ViewGroup的Adapter . 支援自定義ViewGroupViewAPT
- Android技術分享| 自定義ViewGroup實現直播間大小屏無縫切換AndroidView
- Android自定義ViewGroup之子控制元件的自動換行和新增刪除AndroidView控制元件
- 自定義流式佈局:ViewGroup的測量與佈局View
- Android 自定義View基礎(一)AndroidView
- Android開發之自定義View(一)AndroidView
- Android 自定義一個輪播圖Android
- 自定義viewgroup(5)--可滾動佈局,GestureDetector手勢監聽View
- Android 自定義viewAndroidView
- Android 自定義 TabLayoutAndroidTabLayout
- Android: 自定義ViewAndroidView
- Android自定義ToastAndroidAST
- Android 自定義 DrawableAndroid
- android自定義view(自定義數字鍵盤)AndroidView
- Android自定義控制元件——自定義屬性Android控制元件
- Android自定義View工具:Paint&Canvas(一)AndroidViewAICanvas
- android自定義鍵盤 自定義身份證鍵盤Android
- Android自定義控制元件之自定義屬性Android控制元件
- (Android自定義控制元件)Android自定義狀態提示圖表Android控制元件