1. 实践:Tablayout(流式布局)
1.1 测绘以及排版
ViewGroup的自定义首先需要解决的问题是子View的排版以及ViewGroup的测绘。也就是说会设计到两个重要的方法:onMeasure以及onLayout
1. 测绘
在流式布局中,最显著的特征是一行一行地往下排view如果在该行中,不能塞下了,则另起一行。
同时值得注意的是margin怎样去获取。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 清空集合
mChildViews.clear();
/*获取高度以及宽度*/
/*这个没有太大的变化*/
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = getPaddingTop() + MeasureSpec.getSize(heightMeasureSpec);
/*计算lineWidth*/
int lineWidth = getPaddingLeft();
int childCount = getChildCount();
ArrayList<View> childViews = new ArrayList<>();
mChildViews.add(childViews);
// 子View高度不一致的情况下
int maxHeight = 0;
for (int i = 0; i < childCount; i++) {
/*获取childView*/
View childView = getChildAt(i);
if(childView.getVisibility()==GONE){
continue;
}
/*这段话执行完之后,可以获取子view的宽gao,因为会调用子View的onMeasure方法。*/
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
/*根据子View计算和指定自己的布局*/
// margin值 ViewGroup.LayoutParams 没有 就用系统的MarginLayoutParams
// 想想 LinearLayout为什么有?
// LinearLayout有自己的 LayoutParams 会复写一个非常重要的方法
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
/*换行计算*/
if (lineWidth + (childView.getMeasuredWidth() + params.rightMargin + params.leftMargin) > width) {
// 换行,累加高度 加上一行条目中最大的高度
height += childView.getMeasuredHeight() + params.bottomMargin + params.topMargin;
lineWidth = childView.getMeasuredWidth() + params.rightMargin + params.leftMargin;
childViews = new ArrayList<>();
mChildViews.add(childViews);
} else {
/*不换行*/
lineWidth += childView.getMeasuredWidth() + params.rightMargin + params.leftMargin;
maxHeight = Math.max(childView.getMeasuredHeight() + params.bottomMargin + params
.topMargin, maxHeight);
}
childViews.add(childView);
}
/*获取该行View中的最大值作为View的高*/
height += maxHeight;
/*计算ViewGroup自己的宽高*/
setMeasuredDimension(width, height);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
2. 排版
在代码块中有几个值得注意的点:
- 这里为了不重复测量,使用了一个
List<List<View>>的集合,每行的view塞进一个集合。因为有集合,同时onMeasure可能会被多次调用,所以每次进入前先清空mChilsViews中的数据是必不可少的。
/**
* 指定子view的位置
*
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int left, top = getPaddingTop(), right, bottom;
for (List<View> childViews : mChildViews) {
/*每行中的View*/
left = getPaddingLeft();
int maxHeight = 0;
for (View view : childViews) {
if(view.getVisibility() == GONE){
continue;
}
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
left += params.leftMargin;
int childTop = top + params.topMargin;
right = left + view.getMeasuredWidth();
bottom = childTop + view.getMeasuredHeight();
/*摆放*/
view.layout(left, top, right, bottom);
/*叠加*/
left += view.getMeasuredWidth() + params.rightMargin;
// 不断的叠加top值
int childHeight = view.getMeasuredHeight()+ params.topMargin+params.bottomMargin;
maxHeight = Math.max(maxHeight,childHeight);
}
// 不断的叠加top值
top += maxHeight;
}
}
还有个值得注意的是:因为有margin,所以在每行中应该取子view的高最高的那个高作为该行的高。
3. 思考
如果数据是从后台获取的,怎样设置TAg呢,这就需要适配器了。
public abstract class TabBaseAdapter {
public abstract int getCount();
public abstract View getView(int position, ViewGroup parent);
public void notifyDatasetChanged(){}
}
// tablayout中的
public void setmAdapter(TabBaseAdapter mAdapter) {
if (mAdapter==null){
/*空指针异常*/
throw new NullPointerException("TabBaseAdapter is not allowed to be null");
}
/*清空所有的子View*/
removeAllViews();
this.mAdapter=null;
this.mAdapter = mAdapter;
int childCount=mAdapter.getCount();
for(int i=0;i<childCount;i++){
View view=mAdapter.getView(i,this);
addView(view);
}
}
// 在activity中调用TabLayout
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_day9);
mTabLayout=findViewById(R.id.tab_layout);
mTabLayout.setmAdapter(new TabBaseAdapter() {
@Override
public int getCount() {
return 0;
}
@Override
public View getView(int position, ViewGroup parent) {
View view=LayoutInflater.from(Day9Activity.this).inflate(R.layout.item_tag,parent,false);
return view;
}
});
}
}
ViewGroup的事件分发
|---ViewGroup
| |---dispatchTouchEvent
| |---onInterceptTouchEvent
事件分发流程
// 第一次 down
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
//第二次 move
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
// 第三次 up
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent->onClick
如果在activity中不使用onclick
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent->viewGroup.onTouchEvent
它不会进入view的touchevent的事件分发
在view的onTouchEvent()返回true.
// 第一次 down
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
//第二次 move
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
// 第三次 up(不调用 super.onTouchEvent(event))
// 不会调用onClick
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
在viewGroup中的onIntercept返回true
// 会进入ViewGroup.onTouchEvent()
ViewGroup.dispatchTouchEvent->ViewGroup.onInterceptTouchEvent->ViewGroup.onTouchEvent