横向ListView(三) —— 添加头/尾视图及居中显示

前面的文章已经介绍横向ListView的基础实现、快速滑动和事件响应实现;可以说,通过前面两篇文章已经实现了一个完整可用的横向ListView控件,而这以后的文章将介绍的是整个控件的扩展功能,以满足日常开发过程中的特殊需求

本文将介绍列表头/尾的添加功能实现以及整个视图在没有足够item可以铺满控件时,让显示内容剧中显示。

为什么要实现添加头尾视图,这个我个人也不是很清楚,毕竟在开发过程中很少会有使用头尾视图的需要;不过为了学习,还为了以后也许可能有这方面的需求,所以还是选择了实现这个功能;对于内容剧中显示功能,是因为在使用这个控件时刚好有这个需求。

有一点值得注意:头/尾视图在设计和使用的概念上不是作为列表中的item,如果这一点没有弄清楚,那么在阅读源代码时会较为困难,其中八个概念性封装的方法就尤为体现出这一点(八个方法具体见代码)

先上代码:

package com.hss.os.horizontallistview.history_version;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.Scroller;
import java.util.LinkedList;
import java.util.Queue;
/**
* 添加头/尾视图及居中显示
* Created by sxyx on 2017/8/8.
*/

public class HorizontalListView3 extends AdapterView {
private Queue cacheView = new LinkedList<>();//列表项缓存视图
private ListAdapter adapter = null;
private GestureDetector mGesture;
private int firstItemIndex = 0;//显示的第一个子项的下标
private int lastItemIndex = -1;//显示的最后的一个子项的下标
private int scrollValue=0;//列表已经发生有效滚动的位移值
private int hasToScrollValue=0;//接下来列表发生滚动所要达到的位移值
private int maxScrollValue=Integer. MAX_VALUE;//列表发生滚动所能达到的最大位移值(这个由最后显示的列表项决定)
private int displayOffset=0;//列表显示的偏移值(用于矫正列表显示的所有子项的显示位置)
private Scroller mScroller;
private int firstItemLeftEdge=0;//第一个子项的左边界
private int lastItemRightEdge=0;//最后一个子项的右边界
private View headView;
private View footView;
private boolean hasHeadView=false;
private boolean hasFootView=false;
private boolean canShowInMid=false;public HorizontalListView3(Context context) {
super(context);
init(context);
}
public HorizontalListView3(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public HorizontalListView3(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@RequiresApi(api = Build.VERSION_CODES. LOLLIPOP)
public HorizontalListView3(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context){
mGesture = new GestureDetector(getContext(), mOnGesture);
mScroller=new Scroller(context);
}private void initParams(){
mScroller.forceFinished(true);//避免在滑动过程中变换视图内容时,出现列表无法滚动的情况
removeAllViewsInLayout();
if(adapter!=null&;&;lastItemIndex hasToScrollValue=scrollValue;//保持显示位置不变
else hasToScrollValue=0;//滚动到列表头
scrollValue=0;//列表已经发生有效滚动的位移值
firstItemIndex = 0;//显示的第一个子项的下标
lastItemIndex = -1;//显示的最后的一个子项的下标
maxScrollValue=Integer. MAX_VALUE;//列表发生滚动所能达到的最大位移值(这个由最后显示的列表项决定)
displayOffset=0;//列表显示的偏移值(用于矫正列表显示的所有子项的显示位置)
firstItemLeftEdge=0;//第一个子项的左边界
lastItemRightEdge=0;//最后一个子项的右边界
if(hasHeadView||hasFootView) {
if (hasHeadView) {
scrollValue = headView.getMeasuredWidth();
headView.layout(0, 0, 0, 0);
setHeadView(headView);
}
if (hasFootView) {
footView.layout(0, 0, 0, 0);
setFootView(footView);
}
}elserequestLayout();
}private DataSetObserver mDataObserver = new DataSetObserver() {
@Override
public void onChanged() {
//执行Adapter数据改变时的逻辑
initParams();
}
@Override
public void onInvalidated() {
//执行Adapter数据失效时的逻辑
initParams();
}
};
@Override
public ListAdapter getAdapter() {
return adapter;
}
@Override
public void setAdapter(ListAdapter adapter) {
if(adapter!=null){
adapter.registerDataSetObserver(mDataObserver);
}
if(this.adapter!=null){
this.adapter.unregisterDataSetObserver(mDataObserver);
}
this.adapter=adapter;
requestLayout();
}
@Override
public View getSelectedView() {
return null;
}
@Override
public void setSelection(int i) {
}
private void addAndMeasureChild(View child, int viewIndex) {
LayoutParams params = child.getLayoutParams();
params = params==null ? new LayoutParams(LayoutParams. WRAP_CONTENT, LayoutParams. MATCH_PARENT):params;
addViewInLayout(child, viewIndex, params, true);
child.measure(MeasureSpec. makeMeasureSpec(getWidth(), MeasureSpec. UNSPECIFIED),
MeasureSpec. makeMeasureSpec(getHeight(), MeasureSpec. UNSPECIFIED));
}@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log. e("","============>>>>>left:"+left+" top:"+top+" right:"+right+" bottom:"+bottom);
//需要先布局列表项再根据余下的空间布局列表头尾
//布局列表项
/*
1.计算这一次整体滚动偏移量
2.根据偏移量提取需要缓存视图
3.根据偏移量显示新的列表项
4.根据整体偏移值整顿所有列表项位置
5.计算最大滚动位移值,记录已经发生有效滚动的位移值
6.根据显示的最终效果,判断是否要居中显示
*/
int dx=calculateScrollValue();
removeNonVisibleItems(dx);
showListItem(dx);
adjustItems();
//布局列表头、尾
adjustHeadAndFootView(dx);
calculateMaxScrollValue();
adjustShow();
//继续滚动
if(!mScroller.isFinished()){
post(new Runnable(){
@Override
public void run() {
requestLayout();
}
});
}
}
/**
* 计算这一次整体滚动偏移量
* @return
*/
private int calculateScrollValue(){
int dx=0;
if(mScroller.computeScrollOffset()){
hasToScrollValue = mScroller.getCurrX();
}
if(hasToScrollValue<=0){
hasToScrollValue=0;
mScroller.forceFinished(true);
}
if(hasToScrollValue >= maxScrollValue) {
hasToScrollValue = maxScrollValue;
mScroller.forceFinished(true);
}
dx=hasToScrollValue-scrollValue;
scrollValue=hasToScrollValue;
return -dx;
}
/**
* 计算最大滚动值
*/
private void calculateMaxScrollValue(){
if(getListItemCount()>0) {
if(lastItemIndex==adapter.getCount()-1) {//已经显示了最后一项
if(getChildAt(getChildCount() – 1).getRight()>=getShowEndEdge()) {
maxScrollValue = scrollValue + getChildAt(getChildCount() – 1).getRight() – getShowEndEdge();
}else{
maxScrollValue=0;
}
}
}else{
if(adapter!=null&;&;adapter.getCount()>0){
}else {
if (getChildCount() > 0
&;&; getChildAt(getChildCount() – 1).getRight() >= getShowEndEdge()) {
maxScrollValue = scrollValue + getChildAt(getChildCount() – 1).getRight() – getShowEndEdge();
} else {
maxScrollValue = 0;
}
}
}
}
/**
* 根据偏移量提取需要缓存视图
* @param dx
*/
private void removeNonVisibleItems(int dx) {
if(getListItemCount()>0) {
//移除列表头
View child = getChildAt(getStartItemIndex());
while (getListItemCount()>0&;χld != null &;&; child.getRight() + dx <= getShowStartEdge()) {
displayOffset += child.getMeasuredWidth();
cacheView.offer(child);
removeViewInLayout(child);
firstItemIndex++;
child = getChildAt(getStartItemIndex());
}
//移除列表尾
child = getChildAt(getEndItemIndex());
while (getListItemCount()>0&;χld != null &;&; child.getLeft() + dx >= getShowEndEdge()) {
cacheView.offer(child);
removeViewInLayout(child);
lastItemIndex–;
child = getChildAt(getEndItemIndex());
}
}
}
/**
* 根据偏移量显示新的列表项
* @param dx
*/
private void showListItem(int dx) {
if(adapter==null)return;
int firstItemEdge = getFirstItemLeftEdge()+dx;
int lastItemEdge = getLastItemRightEdge()+dx;
displayOffset+=dx;//计算偏移量
//显示列表头视图
while(firstItemEdge > getShowStartEdge() &;&; firstItemIndex-1 >= 0) {
firstItemIndex–;//往前显示一个列表项
View child = adapter.getView(firstItemIndex, cacheView.poll(), this);
addAndMeasureChild(child, getStartItemIndex());
firstItemEdge -= child.getMeasuredWidth();
displayOffset -= child.getMeasuredWidth();
}
//显示列表未视图
while(lastItemEdge < getShowEndEdge() &;&; lastItemIndex+1 < adapter.getCount()) {
lastItemIndex++;//往后显示一个列表项
View child = adapter.getView(lastItemIndex, cacheView.poll(), this);
addAndMeasureChild(child, getEndItemIndex()+1);
lastItemEdge += child.getMeasuredWidth();
}
}
/**
* 调整各个item的位置
*/
private void adjustItems() {
if(getListItemCount() > 0){
int left = displayOffset+getShowStartEdge();
int top = getPaddingTop();
int endIndex = getEndItemIndex();
int startIndex = getStartItemIndex();
int childWidth,childHeight;
for(int i=startIndex;i<=endIndex;i++){
View child = getChildAt(i);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
child.layout(left, top, left + childWidth, top + childHeight);
left += childWidth;
}
firstItemLeftEdge=getChildAt(getStartItemIndex()).getLeft();
lastItemRightEdge=getChildAt(getEndItemIndex()).getRight();
}
}
/**
* 调整列表头、尾
*/
private void adjustHeadAndFootView(int dx){
if(hasHeadView){
int left,right;
if(getListItemCount()>0){
right=firstItemLeftEdge;
}else{
if(headView.getRight()>0)
right=headView.getRight()+dx;
else
right=getShowStartEdge()+headView.getMeasuredWidth();
}
left=right-headView.getMeasuredWidth();
headView.layout(left, getPaddingTop(), right, headView.getMeasuredHeight()+getPaddingTop());
}
if(hasFootView){
int left,right;
if(getListItemCount()>0){
left=getChildAt(getEndItemIndex()).getRight();
}else{
if(hasHeadView)
left=headView.getRight();
else {
if(footView.getLeft()==0&;&;dx==0){//第一次赋值
left=getShowStartEdge();
}else{
left=footView.getLeft()+dx;
}
}
}
right=left+footView.getMeasuredWidth();
footView.layout(left, getPaddingTop(), right, footView.getMeasuredHeight()+getPaddingTop());
}
}
private void adjustShow(){
if(isCanShowInMid()){//可以居中显示
int endEdge=getShowEndEdge();
boolean canAdjust=false;
if(hasFootView){
if(footView.getRight() }else if(getListItemCount()>0){
if(getChildAt(getEndItemIndex()).getRight() }else if(hasHeadView){
if(headView.getRight() }
if(canAdjust){
//居中显示
int itemsWidth=getChildAt(getChildCount()-1).getRight()-getShowStartEdge();
int left=(getShowWidth()-itemsWidth)/2+getShowStartEdge();
int right;
View child;
for(int i=0;i child= getChildAt(i);
right=left+child.getMeasuredWidth();
child.layout(left,child.getTop(),right,child.getBottom());
left=right;
}
}
}
}
//以下八个方法为概念性封装方法,有助于往后的扩展和维护 /**
* 获得列表视图中item View的总数
* @return
*/
private int getListItemCount(){
int itemCount=getChildCount();
if(hasHeadView)itemCount-=1;
if(hasFootView)itemCount-=1;
return itemCount;
}
/**
* 获得列表视图中第一个item View下标
* @return
*/
private int getStartItemIndex(){
if(hasHeadView) return 1;
return 0;
}
/**
* 获得列表视图中最后一个item View下标
* @return
*/
private int getEndItemIndex(){
if(hasFootView) return getChildCount()-2;
return getChildCount()-1;
}
/**
* 获得列表视图中第一个item View左边界值
* @return
*/
private int getFirstItemLeftEdge(){
if(getListItemCount()>0) {
return firstItemLeftEdge;
}else{
if(hasHeadView) return headView.getRight();
else return 0;
}
}
/**
* 获得列表视图中最后一个item View右边界值
* @return
*/
private int getLastItemRightEdge(){
if(getListItemCount()>0) {
return lastItemRightEdge;
}else{
if(hasFootView) return footView.getLeft();
else return 0;
}
}
/**
* 取得视图可见区域的左边界
* @return
*/
private int getShowStartEdge(){
return getPaddingLeft();
}
/**
* 取得视图可见区域的右边界
* @return
*/
private int getShowEndEdge(){
return getWidth()-getPaddingRight();
}
/**
* 取得视图可见区域的宽度
* @return
*/
private int getShowWidth(){
return getWidth()-getPaddingLeft()-getPaddingRight();
}public void setHeadView(View view){
if(view!=null) {
int headRight=-1;
int width=0;
if (hasHeadView&;&;headView!=null) {
headRight=headView.getRight();
width=headView.getWidth();
removeViewInLayout(headView);
}
hasHeadView = true;
headView=view;
addAndMeasureChild(headView, 0);
if(getListItemCount()>0) {//有列表内容
if (headRight == -1) {
//新增列表头
if (firstItemIndex == 0) {//第一个显示的是第一个列表项
//滚动整个列表,让其显示完整列表头(让列表往回滚)
scrollValue = headView.getMeasuredWidth()
+ getShowStartEdge() – firstItemLeftEdge;
hasToScrollValue=0;
} else {//不是显示第一个列表项
//不滚动列表项,增加历史滚动值
hasToScrollValue += headView.getMeasuredWidth();
scrollValue = hasToScrollValue;
}
} else {
//替换列表头
hasToScrollValue += headView.getMeasuredWidth()-width;
}
}
maxScrollValue=Integer. MAX_VALUE;
requestLayout();
}
}
public void removeHeadView(){
if(hasHeadView&;&;headView!=null){
hasHeadView=false;
int left=headView.getLeft();
int width=headView.getMeasuredWidth();
removeViewInLayout(headView);
if(headView.getRight()>=getShowStartEdge()) {//列表头有显示
scrollValue = -(width+left-getShowStartEdge());
hasToScrollValue=0;
}else{
scrollValue-=width;
hasToScrollValue-=width;
}
requestLayout();
}else{
hasHeadView=false;
}
}
public void setFootView(View view){
if(view!=null) {
if (hasFootView&;&;footView!=null) {
removeViewInLayout(footView);
}
hasFootView=true;
footView=view;
addAndMeasureChild(footView, -1);
requestLayout();
}
}
public void removeFootView(){
if(hasFootView&;&;footView!=null){
hasFootView=false;
int left=footView.getLeft();
removeViewInLayout(footView);
if(left hasToScrollValue -= getWidth()-left-getShowStartEdge();
}
requestLayout();
}else{
hasFootView=false;
}
} /**
* 在onTouchEvent处理事件,让子视图优先消费事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGesture.onTouchEvent(event);
}
private GestureDetector.OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
mScroller.forceFinished(true);//点击时停止滚动
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
mScroller.fling(hasToScrollValue, 0, (int)-velocityX, 0, 0, maxScrollValue, 0, 0);
requestLayout();
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
synchronized(HorizontalListView3.this){
hasToScrollValue += (int)distanceX;
}
requestLayout();
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
for(int i=0;i View child = getChildAt(i);
if (isEventWithinView(e, child)) {
if(hasHeadView&;&;i==0){
//点击列表头
}else if(hasFootView&;&;i==getChildCount()-1){
//点击列表尾
}else {
int position=firstItemIndex + i;
if(hasHeadView) position–;
if (getOnItemClickListener() != null) {
getOnItemClickListener().onItemClick(HorizontalListView3.this, child, position, adapter.getItemId(position));
}
if (getOnItemSelectedListener() != null) {
getOnItemSelectedListener().onItemSelected(HorizontalListView3.this, child, position, adapter.getItemId(position));
}
}
break;
}
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (isEventWithinView(e, child)) {
if(hasHeadView&;&;i==0){
//点击列表头
}else if(hasFootView&;&;i==getChildCount()-1){
//点击列表尾
}else {
int position=firstItemIndex + i;
if(hasHeadView) position–;
if (getOnItemLongClickListener() != null) {
getOnItemLongClickListener().onItemLongClick(HorizontalListView3.this, child, position, adapter.getItemId(position));
}
}
break;
}
}
}
private boolean isEventWithinView(MotionEvent e, View child) {
Rect viewRect = new Rect();
int[] childPosition = new int[2];
child.getLocationOnScreen(childPosition);
int left = childPosition[0];
int right = left + child.getWidth();
int top = childPosition[1];
int bottom = top + child.getHeight();
viewRect.set(left, top, right, bottom);
return viewRect.contains((int) e.getRawX(), (int) e.getRawY());
}
};
public synchronized void scrollTo(int x) {
mScroller.startScroll(hasToScrollValue, 0, x – hasToScrollValue, 0);
requestLayout();
}
public boolean isCanShowInMid() {
return canShowInMid;
}
public void setCanShowInMid(boolean canShowInMid) {
this.canShowInMid = canShowInMid;
}
}

这里实现的头尾视图只提供单视图模式,没有提供像ListView一样列表模式,也就是说,头/尾视图只能各自设置一个,而不能设置多个。

有一点值得注意:头/尾视图在设计和使用的概念上不是作为列表中的item,如果这一点没有弄清楚,那么在读代码时会更加困难,其中八个概念性封装的方法就尤为体现出这一点

在没有需要实现头/尾视图的时候,在很多代码段中用到0表示第一个item下标,也用到getPaddingLeft()这个方法的取值作为可视区域的左边界值;当需要加入头/尾视图时,这些值都需要做出些改变,因而需要引入八个概念性封装的方法,以降低代码之间的耦合度,使扩展功能后的代码和扩展前的代码有明显分割线,这样不仅有利于增强代码的可读性,又有利于代码的维护和扩展。

这样的实现方式不仅让横行ListView的基础实现保持其原型,让扩展的代码完全分离出来,而且对于以后的扩展更加有利,对于功能的剔除也方便很多而不容易出错。(对于这八个方法的作用,在代码里已经有了相应的注解,这里就不再做讨论)

在加入头/尾视图之前,这八个概念性封装的方法实现如下:

//以下八个方法为概念性封装方法,有助于往后的扩展和维护
/**
* 获得列表视图中item View的总数
* @return
*/
private int getListItemCount(){
int itemCount=getChildCount();
return itemCount;
}
/**
* 获得列表视图中第一个item View下标
* @return
*/
private int getStartItemIndex(){
return 0;
}
/**
* 获得列表视图中最后一个item View下标
* @return
*/
private int getEndItemIndex(){
return getChildCount()-1;
}
/**
* 获得列表视图中第一个item View左边界值
* @return
*/
private int getFirstItemLeftEdge(){
if(getListItemCount()>0) {
return firstItemLeftEdge;
}else{
return 0;
}
}
/**
* 获得列表视图中最后一个item View右边界值
* @return
*/
private int getLastItemRightEdge(){
if(getListItemCount()>0) {
return lastItemRightEdge;
}else{
return 0;
}
}
/**
* 取得视图可见区域的左边界
* @return
*/
private int getShowStartEdge(){
return getPaddingLeft();
}
/**
* 取得视图可见区域的右边界
* @return
*/
private int getShowEndEdge(){
return getWidth()-getPaddingRight();
}
/**
* 取得视图可见区域的宽度
* @return
*/
private int getShowWidth(){
return getWidth()-getPaddingLeft()-getPaddingRight();
}

添加了头/尾视图后,这八个概念性封装的方法实现如下:

//以下八个方法为概念性封装方法,有助于往后的扩展和维护
/**
* 获得列表视图中item View的总数
* @return
*/
private int getListItemCount(){
int itemCount=getChildCount();
if(hasHeadView)itemCount-=1;
if(hasFootView)itemCount-=1;
return itemCount;
}
/**
* 获得列表视图中第一个item View下标
* @return
*/
private int getStartItemIndex(){
if(hasHeadView) return 1;
return 0;
}
/**
* 获得列表视图中最后一个item View下标
* @return
*/
private int getEndItemIndex(){
if(hasFootView) return getChildCount()-2;
return getChildCount()-1;
}
/**
* 获得列表视图中第一个item View左边界值
* @return
*/
private int getFirstItemLeftEdge(){
if(getListItemCount()>0) {
return firstItemLeftEdge;
}else{
if(hasHeadView) return headView.getRight();
else return 0;
}
}
/**
* 获得列表视图中最后一个item View右边界值
* @return
*/
private int getLastItemRightEdge(){
if(getListItemCount()>0) {
return lastItemRightEdge;
}else{
if(hasFootView) return footView.getLeft();
else return 0;
}
}
/**
* 取得视图可见区域的左边界
* @return
*/
private int getShowStartEdge(){
return getPaddingLeft();
}
/**
* 取得视图可见区域的右边界
* @return
*/
private int getShowEndEdge(){
return getWidth()-getPaddingRight();
}
/**
* 取得视图可见区域的宽度
* @return
*/
private int getShowWidth(){
return getWidth()-getPaddingLeft()-getPaddingRight();
}

实现添加头/尾视图功能的步骤如下:

1.实现getHeadView()、getFootView()、removeHeadView()、removeFootView()这四个方法

在实现这四个方法时会遇到需要调整整个列表显示的情况,这个时候整个列表的显示调整要通过滚动操作进行调整(即调整scrollValue和hasToScrollValue的值,且调用requestLayout()方法重新布局视图),而不是直接操作left/top/right/bottom来调整,

2.实现头/尾视图布局方法adjustHeadAndFootView(int dx);

头视图的显示是以第一个显示的item View的左边界作为基准,即以第一个显示的itemView的左边界作为头视图显示的右边界(第一个itemView 的left值作为headView的right值)。

同理,尾视图的显示是以最后一个item View的右边界作为基准(最后一个itemView 的right值作为footView的left值)

头尾视图的显示不考虑其是否可见,即不需要考虑头尾视图是否在横向ListView的可见区域内。

3.选择修改八个概念性封装的方法,以满足需求

4.在initParams()方法中添加入头/尾视图的初始化操作

如果在initPatams方法中没有对头/尾视图做出调整,那么整个执行逻辑会出错。

5.在onLayout(boolean changed, int left, int top, int right, int bottom)方法中添加头/尾视图的布局操作。必须在adjustItems()和calculateMaxScrollValue()方法之间调用,如下代码:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int dx=calculateScrollValue();
removeNonVisibleItems(dx);
showListItem(dx);
adjustItems();
//布局列表头、尾
adjustHeadAndFootView(dx);
calculateMaxScrollValue();
adjustShow();
//继续滚动
if(!mScroller.isFinished()){
post(new Runnable(){
@Override
public void run() {
requestLayout();
}
});
}
}

实现居中显示的功能步骤如下:

1.添加居中显示开关boolean canShowInMid及相应的设置和访问方法

2.实现adjustShow()方法,

3.在onLayout(boolean changed, int left, int top, int right, int bottom)方法中所有视图布局完成时调用