點此進入:從零快速構建APP系列目錄導圖
點此進入:UI編程系列目錄導圖
點此進入:四大組件系列目錄導圖
點此進入:數據網絡和線程系列目錄導圖
實現一個知識點的標籤顯示,每個標籤的長度未知,如下圖所示:
本篇的控件涉及到的內容比較多,所以先介紹下View的繪製流程、相關回調方法等,避免後面用到的時候不知道什麼意思。
一、View繪製流程
1、mesarue() 測量過程
主要作用:爲整個 View 樹計算實際的大小,即設置實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:mMeasureWidth),每個 View 的控件的實際寬高都是由父視圖和本身視圖決定的。
具體的調用鏈如下:ViewRoot 根對象的屬性 mView(其類型一般爲 ViewGroup 類型)調用 measure()方法去計算 View 樹的大小,回調 View/ViewGroup 對象的 onMeasure() 方法,該方法實現的功能如下:
1、設置本 View 視圖的最終大小,該功能的實現通過調用 setMeasuredDimension()方法去設置實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:mMeasureWidth)。
2 、如果該 View 對象是個 ViewGroup 類型,需要重寫該 onMeasure() 方法,對其子視圖進行遍歷的measure() 過 程 。 對 每 個 子 視 圖 的 measure() 過 程 , 是 通 過 調 用 父 類 ViewGroup.java 類 裏 的measureChildWithMargins() 方法去實現,該方法內部只是簡單地調用了 View 對象的 measure() 方法。
2、layout() 佈局過程
主要作用:爲將整個根據子視圖的大小以及佈局參數將 View 樹放到合適的位置上。
具體的調用鏈如下:
1、layout 方法會設置該 View 視圖位於父視圖的座標軸,即 mLeft,mTop,mLeft,mBottom(調用setFrame()函數去實現)接下來回調 onLayout()方法(如果該 View 是 ViewGroup 對象,需要實現該方法,對每個子視圖進行佈局)。
2、如果該 View 是個 ViewGroup 類型,需要遍歷每個子視圖 childView,調用該子視圖的 layout() 方法去設置它的座標值。
3、draw()繪圖過程
由 ViewRoot 對象的 performTraversals() 方法調用 draw() 方法發起繪製該 View 樹,值得注意的是每次發起繪圖時,並不會重新繪製每個 View 樹的視圖,而只會重新繪製那些“需要重繪”的視圖,View 類內部變量包含了一個標誌位 DRAWN,當該視圖需要重繪時,就會爲該 View 添加該標誌位。
調用流程 :
1 、繪製該 View 的背景
2 、爲顯示漸變框做一些準備操作(大多數情況下,不需要改漸變框)
3、調用 onDraw() 方法繪製視圖本身(每個 View 都需要重載該方法,ViewGroup 不需要實現該方法)
4、調用 dispatchDraw() 方法繪製子視圖(如果該 View 類型不爲 ViewGroup,即不包含子視圖,不需要重載該方法)
值得說明的是,ViewGroup 類已經爲我們重寫了 dispatchDraw() 的功能實現,應用程序一般不需要重寫該方法,但可以重載父類函數實現具體的功能。
另外,關於 invalidate() 方法的介紹,大家可以參照這篇:Android中View繪製流程以及invalidate()等相關方法分析
二、自定義標籤雲類的實現
1、自定義屬性
<declare-styleable name="TagsLayout">
<attr name="tagVerticalSpace" format="dimension" />
<attr name="tagHorizontalSpace" format="dimension" />
</declare-styleable>
2、構造函數中獲取自定義屬性值
public TagsLayout(Context context) {
super(context);
mContext = context;
init();
}
public TagsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mAttributeSet = attrs;
init();
}
public TagsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
mAttributeSet = attrs;
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TagsLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
mAttributeSet = attrs;
init();
}
private void init() {
TypedArray attrArray = mContext.obtainStyledAttributes(mAttributeSet, R.styleable.TagsLayout);
if (attrArray != null) {
mChildHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagHorizontalSpace, 0);
mChildVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagVerticalSpace, 0);
attrArray.recycle();
}
}
3、onMeasure函數測量子控件大小,然後設置當前控件大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 獲得它的父容器爲它設置的測量模式和大小
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 遍歷每個子元素
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE)
continue;
// 測量每一個child的寬和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到child的lp
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 當前子空間實際佔據的寬度
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + childHorizontalSpace;
// 當前子空間實際佔據的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + childVerticalSpace;
/**
* 如果加入當前child,則超出最大寬度,則的到目前最大寬度給width,類加height 然後開啓新行
*/
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
width = Math.max(lineWidth, childWidth);// 取最大的
lineWidth = childWidth; // 重新開啓新行,開始記錄
// 疊加當前高度,
height += lineHeight;
// 開啓記錄下一行的高度
lineHeight = childHeight;
child.setTag(new Location(left, top + height, childWidth + left - childHorizontalSpace, height + child.getMeasuredHeight() + top));
} else {// 否則累加值lineWidth,lineHeight取最大高度
child.setTag(new Location(lineWidth + left, top + height, lineWidth + childWidth - childHorizontalSpace + left, height + child.getMeasuredHeight() + top));
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
}
width = Math.max(width, lineWidth) + getPaddingLeft() + getPaddingRight();
height += lineHeight;
sizeHeight += getPaddingTop() + getPaddingBottom();
height += getPaddingTop() + getPaddingBottom();
setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height);
}
通過遍歷所有子控件調用measureChild函數獲取每個子控件的大小,然後通過寬度疊加判斷是否換行,疊加控件的高度,同時記錄下當前子控件的座標,這裏記錄座標引用了自己寫的一個內部類Location.java。
4、onLayout函數對所有子控件重新佈局
private class Location {
private int left;
private int top;
private int right;
private int bottom;
private Location(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
}
三、自定義標籤雲的使用
1、在佈局文件中直接引用
<com.wgh.willflowcloudtag.TagsLayout
android:id="@+id/tagsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
</com.wgh.willflowcloudtag.TagsLayout>
2、在MainActivity中用代碼添加標籤
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TagsLayout tagsLayout = (TagsLayout) findViewById(R.id.tagsLayout);
ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
String[] string={"鋤禾日當午","汗滴禾下土", "metacognition", "WillFlow","誰知盤中餐","粒粒皆辛苦"};
for (int i = 0; i < string.length; i++) {
TextView textView = new TextView(this);
textView.setText(string[i]);
textView.setTextColor(getResources().getColor(R.color.colorAccent));
textView.setBackgroundResource(R.drawable.a);
tagsLayout.addView(textView, lp);
}
}
至此有關簡單的自定義控件已經介紹的差不多了,項目中很複雜的控件現在涉及的比較少,以後用到之後再做記錄。
最後,給大家介紹一款開源3D標籤雲:3D標籤雲
點此進入:GitHub開源項目“愛閱”。“愛閱”專注於收集優質的文章、站點、教程,與大家共分享。下面是“愛閱”的效果圖:
聯繫方式: