WidgetLayout
WidgetLayout是一组高效自定义容器集合,支持限定最大最小宽高、按百分比、权重测绘,整体内容支持按 gravity 属性展示,可控制边界描边绘制和子 View 间水平和垂直分割线等,目前实现了以下容器(kotlin
实现见kotlin_master 分支):
ColumnLayout
以等分列方式布局,每列可设置内容居左,中,右,及铺满,可设置最小最大列宽高限定。WrapLayout
自适应换行容器,可限定每行最少和最多 Item 数,行内容可水平和垂直居中,在单行或列时支持weight
权重属性,。ScrollLayout
滑动容器,可安全取代水平和垂直的ScrollView
实现了NestedScrollingChild
接口。PageScrollView
扩展于ScrollLayout
可水平垂直方向布局和滑动吸顶等,支持ScrollView
和ViewPager
的交互方式和接口。NestRefreshLayout
具有可定制性的下拉刷新和加载更多容器,支持任意类型可滚动View
,可添加头部和尾部且可设置悬停模式。NestFloatLayout
支持列表的嵌套滑动和指定子View
悬停顶部,类似NestScrollView
。PageScrollTab
扩展于PageScrollView
支持Tab
场景交互和各种 UI 定制。LabelLayout
扩展于WrapLayout
,以ItemProvider
方式提供内容,有简单的回收复用机制,有 Item 点击监听。HierarchyLayout
调试容器可展示View
树层级关系和 3D 层级图。
实现背景及使用场景
1. 现有系统容器下开发界面经常遇到以下尴尬问题:
- 实现特定的组合布局,我们会用功能鲜明的 4 大常用布局去嵌套实现,增加了严重 OverDraw 的机率;
RelativeLayout
布局功能强大但是measure
过程复杂(每次执行onMeasure
所有直接子View
会有两次 measure)- 为实现复杂交互工作量比开发业务繁重,比如滑动控件的悬停或联动;
- 分割线或是边界线很多人常用
View
来实现,即耗内存,又影响测绘时间。 - 等分布局,使用多层不同方向
LinearLayout
来实现,嵌套太多。 - 常用容器控件还没有对自身做最大宽高限定的,也无对子 View 做最大宽高限定。
2. 适合的使用场景举例按需要选择,可减少布局嵌套和复杂的交互代码。
- 容器自身或对直接子
View
的最大宽高限定,容器内容和直接子View
的gravity
灵活支持。 - 容器需要描边或子
View
间画分割线的,或有各种间距要求,或按下自带激变层效果可使用继承于PressViewGroup
的容器,如WrapLayout,LabelLayout,ColumnLayout
。 - 常见的表格布局或动态等分布局可用
ColumnLayout
,支持每列的Align
方式(左中右上下)和全铺满。 - 水平或垂直布局,标签布局,或需自适应换行可选用
WrapLayout
和LabelLayout
,可设置行最少最多的View
个数和行居中, - 列表需要嵌套滑动和悬停吸顶可用
NestFloatLayout
,类似 NestScrollView 。 PageScrollView
可取代ScrollView&HorizontalScrollView
少嵌套,可设置任意子View
滑动悬停在开始和结束位置,可不限定子View
大小像ViewPager
一样选中居中和滑动的交互。WrapLayout,ColumnLayout
是完全可替代支持不同方向的LinearLayout
并能提供更多的布局约束,和背景,描边,分割等额外装饰。- 以上容器都有一个共通的基类,便于统一监控性能打点如
layout
,measure
过程等。
Demo 演示效果
WrapLayout
和LabelLayout
ColumnLayout
的演示效果。的演示效果。
PageScrollView
和PageScrollTab
的使用示例。
Demo 入口 和 NestFloatLayout
的演示效果。
HiearchyLayout
的静态图,实际是可随手势改变 3D 形态的。
![hierarchyview] [层级 3D 图] ![hierarchyviewnode] [层级关系树]
如何使用:XML 属性和 API 简介
集成需要提供 support-v4 包,然后再 gradle 脚本中添加依赖如下。
compile 'com.rexy.android:widgetlayout:1.0.1'
通用属性说明和介绍
注;所有 xml 中使用自定义属性的地方,请在根标签中加上xmlns:app="http://schemas.android.com/apk/res-auto"
1. 所有容器自身和子 View
对于 maxWidth,maxHeight,gravity,widthPercent,heightPercent
支持。
a. 容器控件自身标签下使用 android:gravity,android:maxWidth ,android:maxHeight
,即可支持容器内容的 align 属和最大宽与高的限制。
java 代码可通过 setGravity
,setMaxWidth
,setMaxHeight
来支持。
b. 容器直接子View
使用android:layout_gravity,android:maxWidth,android:maxHeight
即可支持直接子View
在容器内的Align
和自身大小的限制。
java 代码可通过 BaseViewGroup.LayoutParams lp=(BaseViewGroup.LayoutParams)child.getLayoutParams(); lp.gravity=Gravity.CENTER;lp.maxWidth=100;lp.maxHeight=200
2. 部分容器FloatDrawable
和BorderDivider
的应用,仅限于继承于PressViewGroup
的容器
a. xml 中使用支持FloatDrawable
属性和解释如下,java 都有对应的 set 和 get 方法:
-hover drawable 忽略手势滑动到自身之外取消按下状态-->
<attr name="ignoreForegroundStateWhenTouchOut" format="boolean"/>
<!--hover drawable 颜色-->
<attr name="foregroundColor" format="color"/>
<!--hover drawable 圆角-->
<attr name="foregroundRadius" format="dimension"/>
<!--hover drawable 动画时间-->
<attr name="foregroundDuration" format="integer"/>
<!--hover drawable 最小不透明度-->
<attr name="foregroundAlphaMin" format="integer"/>
<!--hover drawable 最大不透明度-->
<attr name="foregroundAlphaMax" format="integer"/>
b. xml 中使用支持BorderDivider
属性和解释如下,java 都有对应的 set 和 get 方法:
<!--左边线的 drawable,颜色,宽度,和边线 padding-->
<attr name="borderLeft" format="reference"/>
<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"/>
<!--上边线的 drawable,颜色,宽度,和边线 padding-->
<attr name="borderTop" format="reference"/>
<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"/>
<!--右边线 drawable,颜色,宽度,和边线 padding-->
<attr name="borderRight" format="reference"/>
<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"/>
<!--下边线的 drawable,颜色,宽度,和边线 padding-->
<attr name="borderBottom" format="reference"/>
<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"/>
<!--内容四边的间距,不同于 padding -->
<attr name="contentMargin" format="dimension" />
<attr name="contentMarginHorizontal" format="dimension" />
<attr name="contentMarginVertical" format="dimension" />
<attr name="contentMarginLeft" format="dimension"/>
<attr name="contentMarginTop" format="dimension"/>
<attr name="contentMarginRight" format="dimension"/>
<attr name="contentMarginBottom" format="dimension"/>
<!--水平方向和垂直方向 Item 的间距-->
<attr name="itemMargin" format="dimension"/>
<attr name="itemMarginHorizontal" format="dimension"/>
<attr name="itemMarginVertical" format="dimension"/>
<!--水平分割线 drawable-->
<attr name="dividerHorizontal" format="reference" />
<!--水平分割线颜色-->
<attr name="dividerColorHorizontal" format="color"/>
<!--水平分割线宽-->
<attr name="dividerWidthHorizontal" format="dimension"/>
<!--水平分割线开始和结束 padding-->
<attr name="dividerPaddingHorizontal" format="dimension"/>
<attr name="dividerPaddingHorizontalStart" format="dimension"/>
<attr name="dividerPaddingHorizontalEnd" format="dimension"/>
<!--垂直分割线 drawable-->
<attr name="dividerVertical" format="reference" />
<!--垂直分割线颜色-->
<attr name="dividerColorVertical" format="color"/>
<!--垂直分割线宽-->
<attr name="dividerWidthVertical" format="dimension"/>
<!--垂直分割线开始 和结束 padding-->
<attr name="dividerPaddingVertical" format="dimension"/>
<attr name="dividerPaddingVerticalStart" format="dimension"/>
<attr name="dividerPaddingVerticalEnd" format="dimension"/>
具体容器组件的属性和使用介绍
1.ColumnLayout
xml 属性支持属性如下:java 都有对应的 set 和 get 方法就不给示例了,内容分割线间距见以上通用属性。
<!--列个数-->
<attr name="columnNumber" format="integer" />
<!--每行内容垂直居中-->
<attr name="columnCenterVertical" format="boolean"/>
<!--列内内容全展开的索引 * 或 1,3,5 类似列索引 0 开始-->
<attr name="stretchColumns" format="string" />
<!--列内内容全靠中间 * 或 1,3,5 类似列索引 0 开始-->
<attr name="alignCenterColumns" format="string" />
<!--列内内容全靠右 * 或 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
一样用,只支持一个内容。可动态设置头部和尾部。
//可选,设置头部 headerView,true 表示悬停在内容上。
refreshLayout.setHeaderView(headerView, true);
//可选,设置尾部 footerView,false 表示不需要悬停在内容上。
refreshLayout.setFooterView(footerView, false);
//可选,设置覆盖内容的蒙层 View。
refreshLayout.setMaskView(maskView, true);
//设置下拉刷新的指示器。
refreshLayout.setRefreshPullIndicator(new DefaultRefreshIndicator(getActivity()));
//设置上拉加载更多的指示器。
refreshLayout.setRefreshPushIndicator(new DefaultRefreshIndicator(getActivity()));
设置下拉或上推的状态和刷新动作的监听
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 代码如下:
<!--实现了嵌套滑动 NestScrollingChild 接口的滑动的 View 所在的直接子 View 索引-->
<attr name="nestViewIndex" format="integer"/>
java
代码可如下设置:
``` java
mLastFloatLayout.setNestViewId(viewId);
mLastFloatLayout.setFloatViewId(viewId);
或
mLastFloatLayout.setNestViewIndex(viewIndex);
mLastFloatLayout.setFloatViewIndex(viewIndex);
4.PageScrollView,PageScrollTab
使用.
a. 支持的 xml 属性,对应都有 java 相应的 set 和 get;
<!--布局方向,也决定了手势方向,仅支持水平和垂直之一。-->
<attr name="layoutDirection"/>
<!--滑动交互 ViewPager 方式-->
<attr name="viewPagerStyle" format="boolean"/>
<!--所有的 child 居中-->
<attr name="childCenter" format="boolean"/>
<!--所有的 child 填充整个父容器-->
<attr name="childFillParent" format="boolean"/>
<!--内容 item 的间距-->
<attr name="middleMargin" format="dimension"/>
<!--item 的 size 按父容器的 size 百分比-->
<attr name="sizeFixedPercent" format="float"/>
<!--快速滑动松手后的回弹距离-->
<attr name="overFlingDistance" format="dimension"/>
<!--滑动悬停到开始位置的 child 索引-->
<attr name="floatViewStartIndex" format="integer"/>
<!--滑动悬停到结束位置的 child 索引-->
<attr name="floatViewEndIndex" format="integer"/>
b.java 其它接口设置
//接着上面
mPageScrollView.setPageHeadView(headerView); //设置头部 View
mPageScrollView.setPageFooterView(footerView); 设置尾部 View
//设置 PageTransformer 动画,实现滑动视图的变换。
mPageScrollView.setPageTransformer(new PageScrollView.PageTransformer() {
@Override
public void transformPage(View view, float position, boolean horizontal) {
//在这里根据滑动相对偏移量 position,实现该视图的动画效果。
}
@Override
public void recoverTransformPage(View view, boolean horizontal) {
//清除视图的动画效果,在 setPageTransformer(null)时会调用。
}
});
PageScrollView.OnPageChangeListener pagerScrollListener = new PageScrollView.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// ViewPager 滑动视图时,相对偏移适时回调。
}
@Override
public void onPageSelected(int position, int oldPosition) {
// ViewPager 模式时 选中回调。
}
@Override
public void onScrollChanged(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
//视图滑动回调 View.onScrollChanged
}
@Override
public void onScrollStateChanged(int state, int oldState) {
//state 的取值如下,标明着容器的滑动状态。
// SCROLL_STATE_IDLE = 0; // 滑动停止状态。
// SCROLL_STATE_DRAGGING = 1;//用户正开始拖拽滑动 。
// SCROLL_STATE_SETTLING = 2;//开始松开手指快速滑动。
}
};
mPageScrollView.setOnPageChangeListener(pagerScrollListener);
// 设置视图滚动的监听。
mPageScrollView.setOnScrollChangeListener(pagerScrollListener);
//设置可见子 View 发生变化时 可见索引区间的监听。
mPageScrollView.setOnVisibleRangeChangeListener(new OnVisibleRangeChangeListener(){
public void onVisibleRangeChanged(int firstVisible, int lastVisible, int oldFirstVisible, int oldLastVisible){
}
});
//设置动画初始化滑动到第二个 View ,-1 表示动画时间内部计算,如无需动画传 0
mPageScrollView.scrollTo(1,0,-1);
c.PageScrollTab
继承于 PageScrollView
,额外支持以下 xml 属性(java 均有 get 和 set 对应)。
<!--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 设置-->
<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 额外的接口:
//设置 ItemProvider,初始化选中第 0 个索引, 类似上面的 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 都有对应的 set 和 get 方法就不给示例了。当作LinearLayout
如果要支持weight
布局,需要设置 setSupportWeight
为 true 见weightSupport
.
且垂直布局需设置 lineMaxItemCount 为 1,水平布局时 lineMinItemCount 可不设,也可设置为一个大于或等于子视图数的值或设置小于 1 的值. 只有这样才可以应用权重去做垂直或是水平的布局。
<!--每行内容水平居中-->
<attr name="lineCenterHorizontal" format="boolean"/>
<!--每行内容垂直居中-->
<attr name="lineCenterVertical" format="boolean"/>
<!--每一行最少的 Item 个数-->
<attr name="lineMinItemCount" format="integer"/>
<!--每一行最多的 Item 个数-->
<attr name="lineMaxItemCount" format="integer"/>
<!-- 支持 weight 属性,前提条件是单行或单列的情况-->
<attr name="weightSupport" format="boolean" />
6.LabelLayout
继承WrapLayout
有其所有功能接口。
不同是支持 android:textSize,android:textColor
在 xml 中设置 Label 的字号和字色。同样可用 java 代码设置字号字色。
使用可通过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);