WidgetLayout 介紹
WidgetLayout是一組繼承于ViewGroup的自定義容器集合,大部分支持描邊和內(nèi)容分割線,目前實(shí)現(xiàn)了以下實(shí)用容器:
-
ColumnLayout以等分列方式布局,每列可設(shè)置內(nèi)容居左,中,右,及鋪滿,可設(shè)置最小最大列寬高限定。 -
NestRefreshLayout一個(gè)精簡(jiǎn)強(qiáng)大的支持任意類型View的下拉刷新,上拉加載更多,可添加頭部和尾部且可設(shè)置懸停模式。 -
NestFloatLayout支持列表的嵌套滑動(dòng)和指定子View懸停頂部,類似NestScrollView。 -
PageScrollView可水平垂直方向布局和滑動(dòng)吸頂?shù)龋瑹o需嵌套,支持ScrollView和ViewPager的交互和接口。 -
PageScrollTab在PageScrollView上擴(kuò)展支持Tab場(chǎng)景交互和各種UI定制。 -
WrapLayout支持水平布局,并自適應(yīng)換行,單行或列時(shí)支持weight屬性,可限定每行最少和最多Item數(shù),行內(nèi)容可水平和垂直居中。 -
LabelLayout繼承自WrapLayout,以ItemProvider方式提供內(nèi)容,有簡(jiǎn)單的回收復(fù)用機(jī)制,有Item點(diǎn)擊監(jiān)聽。 -
HierarchyLayout一個(gè)展示View樹層級(jí)關(guān)系和工具容器,可畫出結(jié)束依賴圖和3D層級(jí)圖,計(jì)算出平均層級(jí)和最近一次measure,layout,draw的時(shí)間。
以下是各容器實(shí)現(xiàn)的結(jié)構(gòu)類圖, 查看工程地址 github 點(diǎn)我

實(shí)現(xiàn)背景及使用場(chǎng)景
1. 現(xiàn)有系統(tǒng)容器下開發(fā)界面經(jīng)常遇到以下尷尬問題:
- 實(shí)現(xiàn)特定的組合布局,我們會(huì)用功能鮮明的4大常用布局去嵌套實(shí)現(xiàn),增加了嚴(yán)重OverDraw的機(jī)率;
-
RelativeLayout布局功能強(qiáng)大但是measure過程復(fù)雜(每次執(zhí)行onMeasure所有直接子View會(huì)有兩次measure) - 為實(shí)現(xiàn)復(fù)雜交互工作量比開發(fā)業(yè)務(wù)繁重,比如滑動(dòng)控件的懸?;蚵?lián)動(dòng);
-
分割線或是邊界線很多人常用
View來實(shí)現(xiàn),即耗內(nèi)存,又影響測(cè)繪時(shí)間。 -
等分布局,使用多層不同方向
LinearLayout來實(shí)現(xiàn),嵌套太多。 - 常用容器控件還沒有對(duì)自身做最大寬高限定的,也無對(duì)子 View 做最大寬高限定。
2. 適合的使用場(chǎng)景舉例按需要選擇,可減少布局嵌套和復(fù)雜的交互代碼。
-
容器自身或?qū)χ苯幼?code>View的最大寬高限定,容器內(nèi)容和直接子
View的gravity靈活支持。 - 容器需要描邊或子
View間畫分割線的,或有各種間距要求,或按下自帶激變層效果可使用繼承于PressViewGroup的容器,如WrapLayout,LabelLayout,ColumnLayout。 - 常見的表格布局或動(dòng)態(tài)等分布局可用
ColumnLayout,支持每列的Align方式(左中右上下)和全鋪滿。 - 標(biāo)簽布局或需自適應(yīng)換行可選用
WrapLayout和LabelLayout,可設(shè)置行最少最多的View個(gè)數(shù)和行居中, - 列表需要嵌套滑動(dòng)和懸停吸頂可用
NestFloatLayout,類似NestScrollView 。 -
PageScrollView可取代ScrollView&HorizontalScrollView少嵌套,可設(shè)置任意子View滑動(dòng)懸停在開始和結(jié)束位置,可不限定子View大小像ViewPager一樣選中居中和滑動(dòng)的交互。 -
WrapLayout,ColumnLayout是完全可替代支持不同方向的LinearLayout并能提供更多的布局約束,和背景,描邊,分割等額外裝飾。 - 以上容器都有一個(gè)共通的基類,便于統(tǒng)一監(jiān)控性能打點(diǎn)如
layout,measure過程等。
Demo演示效果
WrapLayout和LabelLayout ColumnLayout的演示效果。的演示效果。


PageScrollView和PageScrollTab的使用示例。


Demo 入口 和 NestFloatLayout的演示效果。


HiearchyLayout的靜態(tài)圖,實(shí)際是可隨手勢(shì)改變 3D 形態(tài)的。


如何使用:XML 屬性和 API 簡(jiǎn)介
compile 'com.rexy.android:widgetlayout:1.0.0'
通用屬性說明和介紹
注;所有xml 中使用自定義屬性的地方,請(qǐng)?jiān)诟鶚?biāo)簽中加上xmlns:app="http://schemas.android.com/apk/res-auto"
1. 所有容器自身和子 View 對(duì)于 maxWidth,maxHeight,gravity 支持。
a. 容器控件自身標(biāo)簽下使用 android:gravity,android:maxWidth ,android:maxHeight,即可支持容器內(nèi)容的align 屬和最大寬與高的限制。
java 代碼可通過 setGravity ,setMaxWidth,setMaxHeight 來支持。
b. 容器直接子View使用android:layout_gravity,android:maxWidth,android:maxHeight 即可支持直接子View在容器內(nèi)的Align和自身大小的限制。
java 代碼可通過 BaseViewGroup.LayoutParams lp=(BaseViewGroup.LayoutParams)child.getLayoutParams(); lp.gravity=Gravity.CENTER;lp.maxWidth=100;lp.maxHeight=200
2. 部分容器FloatDrawable和DividerMargin的應(yīng)用,僅限于繼承于PressViewGroup的容器
a. xml 中使用支持FloatDrawable屬性和解釋如下,java 都有對(duì)應(yīng)的set 和get 方法:
-hover drawable 忽略手勢(shì)滑動(dòng)到自身之外取消按下狀態(tài)-->
<attr name="ignoreForegroundStateWhenTouchOut" format="boolean"/>
<!--hover drawable 顏色-->
<attr name="foregroundColor" format="color"/>
<!--hover drawable 圓角-->
<attr name="foregroundRadius" format="dimension"/>
<!--hover drawable 動(dòng)畫時(shí)間-->
<attr name="foregroundDuration" format="integer"/>
<!--hover drawable 最小不透明度-->
<attr name="foregroundAlphaMin" format="integer"/>
<!--hover drawable 最大不透明度-->
<attr name="foregroundAlphaMax" format="integer"/>
b. xml 中使用支持DividerMargin屬性和解釋如下,java 都有對(duì)應(yīng)的set 和get 方法:
<!--左邊線的顏色,寬度,和邊線padding-->
<attr name="borderLeftColor" format="color"/>
<attr name="borderLeftWidth" format="dimension"/>
<attr name="borderLeftMargin" format="dimension"/>
<attr name="borderLeftMarginStart" format="dimension"/>
<attr name="borderLeftMarginEnd" format="dimension"/>
<!--上邊線的顏色,寬度,和邊線padding-->
<attr name="borderTopColor" format="color"/>
<attr name="borderTopWidth" format="dimension"/>
<attr name="borderTopMargin" format="dimension"/>
<attr name="borderTopMarginStart" format="dimension"/>
<attr name="borderTopMarginEnd" format="dimension"/>
<!--右邊線的顏色,寬度,和邊線padding-->
<attr name="borderRightColor" format="color"/>
<attr name="borderRightWidth" format="dimension"/>
<attr name="borderRightMargin" format="dimension"/>
<attr name="borderRightMarginStart" format="dimension"/>
<attr name="borderRightMarginEnd" format="dimension"/>
<!--下邊線的顏色,寬度,和邊線padding-->
<attr name="borderBottomColor" format="color"/>
<attr name="borderBottomWidth" format="dimension"/>
<attr name="borderBottomMargin" format="dimension"/>
<attr name="borderBottomMarginStart" format="dimension"/>
<attr name="borderBottomMarginEnd" format="dimension"/>
<!--內(nèi)容四邊的間距,不同于padding -->
<attr name="contentMarginLeft" format="dimension"/>
<attr name="contentMarginTop" format="dimension"/>
<attr name="contentMarginRight" format="dimension"/>
<attr name="contentMarginBottom" format="dimension"/>
<!--水平方向和垂直方向Item 的間距-->
<attr name="contentMarginHorizontal" format="dimension"/>
<attr name="contentMarginVertical" format="dimension"/>
<!--水平分割線顏色-->
<attr name="dividerColorHorizontal" format="color"/>
<!--水平分割線寬-->
<attr name="dividerWidthHorizontal" format="dimension"/>
<!--水平分割線開始和結(jié)束padding-->
<attr name="dividerPaddingHorizontal" format="dimension"/>
<attr name="dividerPaddingHorizontalStart" format="dimension"/>
<attr name="dividerPaddingHorizontalEnd" format="dimension"/>
<!--垂直分割線顏色-->
<attr name="dividerColorVertical" format="color"/>
<!--垂直分割線寬-->
<attr name="dividerWidthVertical" format="dimension"/>
<!--垂直分割線開始 和結(jié)束padding-->
<attr name="dividerPaddingVertical" format="dimension"/>
<attr name="dividerPaddingVerticalStart" format="dimension"/>
<attr name="dividerPaddingVerticalEnd" format="dimension"/>
具體容器組件的屬性和使用介紹
1.ColumnLayout xml 屬性支持屬性如下:java 都有對(duì)應(yīng)的set和get方法就不給示例了,分割線和各種間距見上面的通用屬性。
<!--列個(gè)數(shù)-->
<attr name="columnNumber" format="integer" />
<!--每行內(nèi)容垂直居中-->
<attr name="columnCenterVertical" format="boolean"/>
<!--列內(nèi)內(nèi)容全展開的索引 * 或 1,3,5 類似列索引0 開始-->
<attr name="stretchColumns" format="string" />
<!--列內(nèi)內(nèi)容全靠中間 * 或 1,3,5 類似列索引0 開始-->
<attr name="alignCenterColumns" format="string" />
<!--列內(nèi)內(nèi)容全靠右 * 或 1,3,5 類似列索引0 開始-->
<attr name="alignRightColumns" format="string" />
<!--列寬和高的最大最小值限定-->
<attr name="columnMinWidth" format="dimension" />
<attr name="columnMaxWidth" format="dimension" />
<attr name="columnMinHeight" format="dimension" />
<attr name="columnMaxHeight" format="dimension" />
2.NestRefreshLayout 在xml 中可像ScrollView一樣用,只支持一個(gè)內(nèi)容??蓜?dòng)態(tài)設(shè)置頭部和尾部。
//可選,設(shè)置頭部headerView,true 表示懸停在內(nèi)容上。
refreshLayout.setHeaderView(headerView, true);
//可選,設(shè)置尾部footerView,false 表示不需要懸停在內(nèi)容上。
refreshLayout.setFooterView(footerView, false);
//可選,設(shè)置覆蓋內(nèi)容的蒙層 View。
refreshLayout.setMaskView(maskView, true);
//設(shè)置下拉刷新的指示器。
refreshLayout.setRefreshPullIndicator(new DefaultRefreshIndicator(getActivity()));
//設(shè)置上拉加載更多的指示器。
refreshLayout.setRefreshPushIndicator(new DefaultRefreshIndicator(getActivity()));
設(shè)置下拉或上推的狀態(tài)和刷新動(dòng)作的監(jiān)聽
mRefreshLayout.setOnRefreshListener(new NestRefreshLayout.OnRefreshListener() {
@Override
public void onRefreshStateChanged(NestRefreshLayout parent, int state, int preState, int moveAbsDistance) {
}
@Override
public void onRefresh(NestRefreshLayout parent, boolean refresh) {
}
});
3.NestFloatLayou xml 屬性支持屬性和 java 代碼如下:
<!--實(shí)現(xiàn)了嵌套滑動(dòng)NestScrollingChild 接口的滑動(dòng)的 View 所在的直接子 View 索引-->
<attr name="nestViewIndex" format="integer"/>```
<!--需要吸頂?shù)巾敳康?View 所在的直接子 View 索引-->
<attr name="floatViewIndex" format="integer"/>`java`代碼可如下設(shè)置:
mLastFloatLayout.setNestViewId(viewId);
mLastFloatLayout.setFloatViewId(viewId);
或
mLastFloatLayout.setNestViewIndex(viewIndex);
mLastFloatLayout.setFloatViewIndex(viewIndex);
4.PageScrollView,PageScrollTab 使用.
a. 支持的xml 屬性,對(duì)應(yīng)都有java 相應(yīng)的set 和 get;
<!--布局方向,也決定了手勢(shì)方向,僅支持水平和垂直之一。-->
<attr name="android:orientation"/>
<!--滑動(dòng)交互 ViewPager 方式-->
<attr name="viewPagerStyle" format="boolean"/>
<!--所有的child居中-->
<attr name="childCenter" format="boolean"/>
<!--所有的child填充整個(gè)父容器-->
<attr name="childFillParent" format="boolean"/>
<!--內(nèi)容item 的間距-->
<attr name="middleMargin" format="dimension"/>
<!--item 的size 按父容器的size 百分比-->
<attr name="sizeFixedPercent" format="float"/>
<!--快速滑動(dòng)松手后的回彈距離-->
<attr name="overFlingDistance" format="dimension"/>
<!--滑動(dòng)懸停到開始位置的child 索引-->
<attr name="floatViewStartIndex" format="integer"/>
<!--滑動(dòng)懸停到結(jié)束位置的child 索引-->
<attr name="floatViewEndIndex" format="integer"/>
b.java 其它接口設(shè)置
//接著上面
mPageScrollView.setPageHeadView(headerView); //設(shè)置頭部 View
mPageScrollView.setPageFooterView(footerView); 設(shè)置尾部 View
//設(shè)置 PageTransformer 動(dòng)畫,實(shí)現(xiàn)滑動(dòng)視圖的變換。
mPageScrollView.setPageTransformer(new PageScrollView.PageTransformer() {
@Override
public void transformPage(View view, float position, boolean horizontal) {
//在這里根據(jù)滑動(dòng)相對(duì)偏移量 position,實(shí)現(xiàn)該視圖的動(dòng)畫效果。
}
@Override
public void recoverTransformPage(View view, boolean horizontal) {
//清除視圖的動(dòng)畫效果,在setPageTransformer(null)時(shí)會(huì)調(diào)用。
}
});
PageScrollView.OnPageChangeListener pagerScrollListener = new PageScrollView.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// ViewPager 滑動(dòng)視圖時(shí),相對(duì)偏移適時(shí)回調(diào)。
}
@Override
public void onPageSelected(int position, int oldPosition) {
// ViewPager 模式時(shí) 選中回調(diào)。
}
@Override
public void onScrollChanged(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
//視圖滑動(dòng)回調(diào) View.onScrollChanged
}
@Override
public void onScrollStateChanged(int state, int oldState) {
//state 的取值如下,標(biāo)明著容器的滑動(dòng)狀態(tài)。
// SCROLL_STATE_IDLE = 0; // 滑動(dòng)停止?fàn)顟B(tài)。
// SCROLL_STATE_DRAGGING = 1;//用戶正開始拖拽滑動(dòng) 。
// SCROLL_STATE_SETTLING = 2;//開始松開手指快速滑動(dòng)。
}
};
mPageScrollView.setOnPageChangeListener(pagerScrollListener);
// 設(shè)置視圖滾動(dòng)的監(jiān)聽。
mPageScrollView.setOnScrollChangeListener(pagerScrollListener);
//設(shè)置可見子 View 發(fā)生變化時(shí) 可見索引區(qū)間的監(jiān)聽。
mPageScrollView.setOnVisibleRangeChangeListener(new OnVisibleRangeChangeListener(){
public void onVisibleRangeChanged(int firstVisible, int lastVisible, int oldFirstVisible, int oldLastVisible){
}
});
//設(shè)置動(dòng)畫初始化滑動(dòng)到第二個(gè) View ,-1 表示動(dòng)畫時(shí)間內(nèi)部計(jì)算,如無需動(dòng)畫傳0
mPageScrollView.scrollTo(1,0,-1);
c.PageScrollTab 繼承于 PageScrollView ,額外支持以下xml 屬性(java 均有g(shù)et 和set 對(duì)應(yīng))。
<!--tab item 的背景-->
<attr name="tabItemBackground" format="reference"/>
<attr name="tabItemBackgroundFirst" format="reference"/>
<attr name="tabItemBackgroundLast" format="reference"/>
<attr name="tabItemBackgroundFull" format="reference"/>
<!--底部指示線-->
<attr name="tabIndicatorColor" format="color"/>
<attr name="tabIndicatorHeight" format="dimension"/>
<attr name="tabIndicatorOffset" format="dimension"/>
<attr name="tabIndicatorWidthPercent" format="float"/>
<!--頂部水平分界線-->
<attr name="tabTopLineColor" format="color"/>
<attr name="tabTopLineHeight" format="dimension"/>
<!--底部水平分界線-->
<attr name="tabBottomLineColor" format="color"/>
<attr name="tabBottomLineHeight" format="dimension"/>
<!-- item 之間垂直分割線-->
<attr name="tabItemDividerColor" format="color"/>
<attr name="tabItemDividerWidth" format="dimension"/>
<attr name="tabItemDividerPadding" format="dimension"/>
<!-- item 的最小 Padding 設(shè)置-->
<attr name="tabItemMinPaddingHorizontal" format="dimension"/>
<attr name="tabItemMinPaddingTop" format="dimension"/>
<attr name="tabItemMinPaddingBottom" format="dimension"/>
<!--item文字大寫開-->
<attr name="tabItemTextCaps" format="boolean"/>
<!--item 文字顏色-->
<attr name="tabItemTextColor" format="reference"/>
java 額外的接口:
//設(shè)置ItemProvider,初始化選中第0 個(gè)索引, 類似上面的 LabelLayout 的初始化。
mPageScrollTab.setTabProvider(mItemProvider,0);
mPageScrollTab.setTabClickListener(new PageScrollTab.ITabClickEvent() {
@Override
public boolean onTabClicked(PageScrollTab parent, View cur, int curPos, View pre, int prePos) {
return false;
}
});
5.WrapLayout xml 屬性支持屬性如下:java 都有對(duì)應(yīng)的set 和get 方法就不給示例了。當(dāng)作LinearLayout如果要支持weight布局,需要設(shè)置 setSupportWeight為true.
且如果是單行需要設(shè)置lineMinItemCount為一個(gè)大于或等于子視圖數(shù)的值,如果是單列需要設(shè)置lineMaxItemCount為1. 才可以應(yīng)用權(quán)重去布局。
<!--每行內(nèi)容水平居中-->
<attr name="lineCenterHorizontal" format="boolean"/>
<!--每行內(nèi)容垂直居中-->
<attr name="lineCenterVertical" format="boolean"/>
<!--每一行最少的Item 個(gè)數(shù)-->
<attr name="lineMinItemCount" format="integer"/>
<!--每一行最多的Item 個(gè)數(shù)-->
<attr name="lineMaxItemCount" format="integer"/>
<!-- 支持weight 屬性,前提條件是單行或單列的情況-->
<attr name="weightSupport" format="boolean" />
6.LabelLayout 繼承WrapLayout有其所有功能接口。
不同是支持 android:textSize,android:textColor 在xml 中設(shè)置Label 的字號(hào)和字色。同樣可用java 代碼設(shè)置字號(hào)字色。
使用可通過ItemProvider 接口來初始化 Label,本工程中的示例初始化如下。
final String[] mLabels = new String[]{
"A", "B", "C", "D", "E", "F", "G", "H"
};
labelLayout.setItemProvider(new ItemProvider.ViewProvider() {
@Override
public int getViewType(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return buildView(getTitle(position),true);
}
@Override
public CharSequence getTitle(int position) {
return mLabels[position];
}
@Override
public Object getItem(int position) {
return mLabels[position];
}
@Override
public int getCount() {
return mLabels == null ? 0 : mLabels.length;
}
});
final LabelLayout.OnLabelClickListener mLabelClicker = new LabelLayout.OnLabelClickListener() {
@Override
public void onLabelClick(LabelLayout parent, View labelView) {
Object tag = labelView.getTag();
CharSequence text = tag == null ? null : String.valueOf(tag);
if (text == null && labelView instanceof TextView) {
text = ((TextView) labelView).getText();
}
if (text != null) {
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
}
};
mLabelLayout.setOnLabelClickListener(mLabelClicker);