Spinner.setSelection(int)方法的执行顺序大致如下步骤所示
- AbsSpinner.setSelection(int)
- AdapterView.setNextSelectedPositionInt(int)
- Spinner.onMeasure(int,int)
- Spinner.onLayout(boolean,int,int,int,int)
- Spinner.layout(int,boolean)
好了,我们来跟着源码一步步来揭开setSelection方法的面纱。
Step1
@Override
public void setSelection(int position) {
//设置选中的postion
setNextSelectedPositionInt(position);
//重新计算布局
requestLayout();
//重绘界面
invalidate();
}
源码很简单,三步走,1、设置position,方法名中加了Next是因为position的状态只有在layout的时候才会真正的被更新。2、重新measure、layout布局。3、重绘布局。
Step2
让我们看看setNextSelectedPositionInt方法做了什么。
void setNextSelectedPositionInt(int position) {
//将position赋值给mNextSelectedPosition变量同时获取相应的itemId
mNextSelectedPosition = position;
mNextSelectedRowId = getItemIdAtPosition(position);
//以下代码对于Spinner没有用处,暂时不管
if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
mSyncPosition = position;
mSyncRowId = mNextSelectedRowId;
}
}
Step3
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
final int measuredWidth = getMeasuredWidth();
setMeasuredDimension(Math.min(Math.max(measuredWidth,
measureContentWidth(getAdapter(), getBackground())),
MeasureSpec.getSize(widthMeasureSpec)),
getMeasuredHeight());
}
}
该方法首先调用了super.onMeasure(int,int),也就是AbsSpinner.onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize;
int heightSize;
// mSpinnerPadding为一个Rect实例用来记录padding值,mSelection*Padding的值为0,因此mSpinnerPadding所记录的就是Spinner本身的padding值
mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft
: mSelectionLeftPadding;
mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop
: mSelectionTopPadding;
mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight
: mSelectionRightPadding;
mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom
: mSelectionBottomPadding;
// mDataChanged是用来记录数据源是否改变,此处为false
if (mDataChanged) {
handleDataChanged();
}
int preferredHeight = 0;
int preferredWidth = 0;
boolean needsMeasuring = true;
// 此处获得的selectedPosition就是Step2中的mNextSelectedPosition
int selectedPosition = getSelectedItemPosition();
if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
// mRecycler中保存着所有已经被measure的view
View view = mRecycler.get(selectedPosition);
if (view == null) {
// 调用adapter的getView方法获取一个view
view = mAdapter.getView(selectedPosition, null, this);
// 暂时不明白干什么用的,不过影响不大
if (view.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
}
if (view != null) {
// 设置缓存
mRecycler.put(selectedPosition, view);
// 如果LayoutParams为null那么生成一个默认布局参数,mBlockLayoutRequests如果使能,那么调用requestLayout会无效
if (view.getLayoutParams() == null) {
mBlockLayoutRequests = true;
view.setLayoutParams(generateDefaultLayoutParams());
mBlockLayoutRequests = false;
}
measureChild(view, widthMeasureSpec, heightMeasureSpec);
// 根据child的尺寸与padding值计算尺寸
preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
needsMeasuring = false;
}
}
if (needsMeasuring) {
// 如果view为null那么直接使用padding值
preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
if (widthMode == MeasureSpec.UNSPECIFIED) {
preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
}
}
// 与最小尺寸比较,取较大值
preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
// 根据mode解析尺寸,state状态此处暂时不管
heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0);
widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0);
// 设置尺寸
setMeasuredDimension(widthSize, heightSize);
//保存measureSpec,这两个东东在接下来的layout过程中会用到
mHeightMeasureSpec = heightMeasureSpec;
mWidthMeasureSpec = widthMeasureSpec;
}
回到Spinner.onMeasure(int, int)方法中来,super.onMeasure之后还有几句代码
// mPopup是一个接口,Spinner通过该接口统一了Dialog和DropDown这两种显示模式
if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
final int measuredWidth = getMeasuredWidth();
// 高度保持不变,对宽度重新处理了一下
setMeasuredDimension(Math.min(Math.max(measuredWidth,
measureContentWidth(getAdapter(), getBackground())),
MeasureSpec.getSize(widthMeasureSpec)),
getMeasuredHeight());
}
好吧,我们来看一下Math.min…这么一长串东西里面到底是些什么
Math.min中有两个参数
参数1.Math.max(measuredWidth,measureContentWidth(getAdapter(), getBackground()))
参数2.MeasureSpec.getSize(widthMeasureSpec))
参数2很好理解,获取了父布局给定的宽度,我们看看参数1,一个Math.max函数,也包含两个参数
参数1.measuredWidth
参数2.measureContentWidth(getAdapter(), getBackground())
参数1为AbsSpinner.onMeasure(int,int)中设置好的宽度,我们看看measureContentWidth干了什么
int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
if (adapter == null) {
return 0;
}
int width = 0;
View itemView = null;
int itemType = 0;
// 不对子视图大小进行约束
final int widthMeasureSpec =
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int heightMeasureSpec =
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
// 下面四句代码主要是为了计算for循环中的start和end值,使end-start的值尽量靠近MAX_ITEMS_MEASURED,MAX_ITEMS_MEASURED默认为15
int start = Math.max(0, getSelectedItemPosition());
final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
final int count = end - start;
start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
// 此for循环主要是为了尽可能多的计算子视图以获取最大的宽度
for (int i = start; i < end; i++) {
final int positionType = adapter.getItemViewType(i);
if (positionType != itemType) {
itemType = positionType;
itemView = null;
}
itemView = adapter.getView(i, itemView, this);
if (itemView.getLayoutParams() == null) {
itemView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
}
itemView.measure(widthMeasureSpec, heightMeasureSpec);
width = Math.max(width, itemView.getMeasuredWidth());
}
// 如果存在背景图片,那么将背景图的padding值计算进宽度中
if (background != null) {
background.getPadding(mTempRect);
width += mTempRect.left + mTempRect.right;
}
return width;
}
至此,measure的过程就结束了。
Step4
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 仅仅调用getHeight方法获取了高度保存在mLayoutHeight变量中
super.onLayout(changed, l, t, r, b);
mInLayout = true;
layout(0, false);
mInLayout = false;
}
mInLayout变量会在另一篇文章中进行讨论,暂时不去理会。我们先看看layout(innt,boolean)方法
@Override
void layout(int delta, boolean animate) {
int childrenLeft = mSpinnerPadding.left;
// 注:mRight、mLeft的值是在layout的过程中被系统通过调用setRight和setLeft进行设置的
int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
// 此处mDataChanged为false
if (mDataChanged) {
handleDataChanged();
}
// itemCount为0那么移除所有view,然后返回
if (mItemCount == 0) {
resetList();
return;
}
// 将mNextSelectedPosition保存为mSelectedPosition
if (mNextSelectedPosition >= 0) {
setSelectedPositionInt(mNextSelectedPosition);
}
// 缓存在mRecycler中,以便在之后的measure和下面的makeView方法中使用
recycleAllViews();
// 该方法其实就是ViewGroup中removeAllViews()方法的实现,只不过removeAllViews()多调用了requestLayout()和invalidate()方法
removeAllViewsInLayout();
// 将mSelectedPosition设置为首位置
mFirstPosition = mSelectedPosition;
if (mAdapter != null) {
// 创建视图,makeView其实干了好多事情,后面再细说
View sel = makeView(mSelectedPosition, true);
int width = sel.getMeasuredWidth();
int selectedOffset = childrenLeft;
// 根据layoutDirection获取绝对gravity
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
// 根据gravity计算横向偏移量
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
break;
case Gravity.RIGHT:
selectedOffset = childrenLeft + childrenWidth - width;
break;
}
sel.offsetLeftAndRight(selectedOffset);
}
// 清空所有视图缓存
mRecycler.clear();
// 刷新界面
invalidate();
// 检查selection是否改变
checkSelectionChanged();
// 重新初始化一些状态
mDataChanged = false;
mNeedSync = false;
setNextSelectedPositionInt(mSelectedPosition);
}
至此,整个setSelection方法的大致流程就已经走完了,想不到,仅仅设置一个selection就做了这么多事情。
上面提到makeView方法,现在来看看这个方法到底干了些什么。源码如下:
private View makeView(int position, boolean addChild) {
View child;
// 如果数据源未改变,那么优先从mRecycler中获取视图。
if (!mDataChanged) {
child = mRecycler.get(position);
if (child != null) {
setUpChild(child, addChild);
return child;
}
}
// mRecycler返回null,那么调用adapter的getView方法生成一个view
child = mAdapter.getView(position, null, this);
setUpChild(child, addChild);
return child;
}
makeView方法在返回之前都调用了setUpChild方法,我们再来看看这个方法
private void setUpChild(View child, boolean addChild) {
// 获取LayoutParam,如果为null那么生成一个默认参数
ViewGroup.LayoutParams lp = child.getLayoutParams();
if (lp == null) {
lp = generateDefaultLayoutParams();
}
// 如果addChild为true,那么将该view添加到当前的ViewGroup中。addViewInLayout为ViewGroup的一个方法,用以在layout过程中添加视图。
if (addChild) {
addViewInLayout(child, 0, lp);
}
// 设置child的selected以及enable状态
child.setSelected(hasFocus());
if (mDisableChildrenWhenDisabled) {
child.setEnabled(isEnabled());
}
// 构造measure参数,这里使用到的mHeightMeasureSpec和mWidthMeasureSpec就是在AbsSpinner.onMeasure方法中保存的两个东东
int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
// 计算child大小
child.measure(childWidthSpec, childHeightSpec);
int childLeft;
int childRight;
int childTop = mSpinnerPadding.top
+ ((getMeasuredHeight() - mSpinnerPadding.bottom -
mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
int childBottom = childTop + child.getMeasuredHeight();
int width = child.getMeasuredWidth();
childLeft = 0;
childRight = childLeft + width;
// 计算视图位置
child.layout(childLeft, childTop, childRight, childBottom);
}
总结:makeView做了四件事情,1.生成一个view;2.添加到Spinner中;3.计算view的大小;4.计算视图位置。几乎把一个视图的绘制流程走完了。
好了,setSelection的源码解析到这里就差不多了,接下来我会再扩展一下写一篇itemSelected回调的文章。
以上。