前面的文章已经介绍横向ListView的基础实现、快速滑动和事件响应实现;能够说,经过前面两篇文章已经实现了一个完整可用的横向ListView控件,而这之后的文章将介绍的是整个控件的扩展功能,以知足平常开发过程当中的特殊需求java
本文将介绍列表头/尾的添加功能实现以及整个视图在没有足够item能够铺满控件时,让显示内容剧中显示。android
为何要实现添加头尾视图,这个我我的也不是很清楚,毕竟在开发过程当中不多会有使用头尾视图的须要;不过为了学习,还为了之后也许可能有这方面的需求,因此仍是选择了实现这个功能;对于内容剧中显示功能,是由于在使用这个控件时恰好有这个需求。api
有一点值得注意:头/尾视图在设计和使用的概念上不是做为列表中的item,若是这一点没有弄清楚,那么在阅读源代码时会较为困难,其中八个概念性封装的方法就尤其体现出这一点(八个方法具体见代码)缓存
先上代码:ide
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<ListAdapter> { private Queue<View> 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<adapter.getCount()) 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); } }else requestLayout(); } 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&&child != null && child.getRight() + dx <= getShowStartEdge()) { displayOffset += child.getMeasuredWidth(); cacheView.offer(child); removeViewInLayout(child); firstItemIndex++; child = getChildAt(getStartItemIndex()); } //移除列表尾 child = getChildAt(getEndItemIndex()); while (getListItemCount()>0&&child != 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()<endEdge) canAdjust=true; }else if(getListItemCount()>0){ if(getChildAt(getEndItemIndex()).getRight()<endEdge) canAdjust=true; }else if(hasHeadView){ if(headView.getRight()<endEdge) canAdjust=true; } if(canAdjust){ //居中显示 int itemsWidth=getChildAt(getChildCount()-1).getRight()-getShowStartEdge(); int left=(getShowWidth()-itemsWidth)/2+getShowStartEdge(); int right; View child; for(int i=0;i<getChildCount();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<getWidth()) { 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<getChildCount();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,若是这一点没有弄清楚,那么在读代码时会更加困难,其中八个概念性封装的方法就尤其体现出这一点post
在没有须要实现头/尾视图的时候,在不少代码段中用到0表示第一个item下标,也用到getPaddingLeft()这个方法的取值做为可视区域的左边界值;当须要加入头/尾视图时,这些值都须要作出些改变,于是须要引入八个概念性封装的方法,以下降代码之间的耦合度,使扩展功能后的代码和扩展前的代码有明显分割线,这样不只有利于加强代码的可读性,又有利于代码的维护和扩展。学习
这样的实现方式不只让横行ListView的基础实现保持其原型,让扩展的代码彻底分离出来,并且对于之后的扩展更加有利,对于功能的剔除也方便不少而不容易出错。(对于这八个方法的做用,在代码里已经有了相应的注解,这里就再也不作讨论)ui
在加入头/尾视图以前,这八个概念性封装的方法实现以下:this
//如下八个方法为概念性封装方法,有助于日后的扩展和维护 /** * 得到列表视图中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及相应的设置和访问方法
public boolean isCanShowInMid() { return canShowInMid; } public void setCanShowInMid(boolean canShowInMid) { this.canShowInMid = canShowInMid; }
2.实现adjustShow()方法
private void adjustShow(){ if(isCanShowInMid()){//能够居中显示 int endEdge=getShowEndEdge(); boolean canAdjust=false; if(hasFootView){ if(footView.getRight()<endEdge) canAdjust=true; }else if(getListItemCount()>0){ if(getChildAt(getEndItemIndex()).getRight()<endEdge) canAdjust=true; }else if(hasHeadView){ if(headView.getRight()<endEdge) canAdjust=true; } if(canAdjust){ //居中显示 int itemsWidth=getChildAt(getChildCount()-1).getRight()-getShowStartEdge(); int left=(getShowWidth()-itemsWidth)/2+getShowStartEdge(); int right; View child; for(int i=0;i<getChildCount();i++){ child= getChildAt(i); right=left+child.getMeasuredWidth(); child.layout(left,child.getTop(),right,child.getBottom()); left=right; } } } }
3.在onLayout(boolean changed, int left, int top, int right, int bottom)方法中全部视图布局完成时调用