男儿欲遂平生志,六经勤向窗前读。这篇文章主要讲述Android 开发 Tip 6 -- Spinner相关的知识,希望能为你提供帮助。
转载请注明出处:
http://blog.csdn.net/crazy1235/article/details/70903974
设置Spinner 文字居中 默认情况下,
Spinner控件的效果是这样的:
文章图片
想让文字居中显示怎么办? ? ?在布局文件中设置
android:gravity=
"
center"
也不起作用! !
源码走读 先来看 Spinner 的构造函数
public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode,
Theme popupTheme) {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a =
context.obtainStyledAttributes(
attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
// 省略代码if (mode =
=
MODE_THEME) {
mode =
a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
}// 判断弹出模式 dialog or dropdown
switch (mode) {
case MODE_DIALOG: {
mPopup =
new DialogPopup();
// DialogPopup
mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
break;
}case MODE_DROPDOWN: {
final DropdownPopup popup =
new DropdownPopup(
mPopupContext, attrs, defStyleAttr, defStyleRes);
// DropdownPopup
// 省略代码
break;
}
}// ...
a.recycle();
// 设置adapter
if (mTempAdapter !=
null) {
setAdapter(mTempAdapter);
mTempAdapter =
null;
}
}
当mTempAdapter 不为空时, 调用了setAdapter() 设置适配器!
但是我们如果在xml中设置了entries属性, 并没有设置adapter
android:entries=
"
@
array/date_spinner_items"
上图的列表是怎么出来的呢? !
来看父类~~
AbsSpinner
public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initAbsSpinner();
final TypedArray a =
context.obtainStyledAttributes(
attrs, R.styleable.AbsSpinner, defStyleAttr, defStyleRes);
final CharSequence[] entries =
a.getTextArray(R.styleable.AbsSpinner_entries);
if (entries !=
null) {
final ArrayAdapter<
CharSequence>
adapter =
new ArrayAdapter<
CharSequence>
(
context, R.layout.simple_spinner_item, entries);
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
setAdapter(adapter);
}a.recycle();
}
从构造器函数中看出, 当entries属性不为空时, 调用了 setAdapter() 函数!
注意这里, 用到的是 ArrayAdapter 适配器 。还有两个重要的布局文件:
- simple_spinner_item
- simple_spinner_dropdown_item
@
Override
public void setAdapter(SpinnerAdapter adapter) {
// ...
super.setAdapter(adapter);
// ...
mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
}
mPopup 是一个接口对象, 里面封装了 设置适配器、显示列表、关闭列表等操作!
不管是Spinner是 dialog 形式还是 dropdown 形式, 都实现了该接口!
private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener
private class DropdownPopup extends ListPopupWindow implements SpinnerPopup
OK, 现在来看 DropDownAdapter
private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
private SpinnerAdapter mAdapter;
private ListAdapter mListAdapter;
public DropDownAdapter(@
Nullable SpinnerAdapter adapter,
@
Nullable Resources.Theme dropDownTheme) {mAdapter =
adapter;
// 注意这里!
!
!
// 省略代吗
}public int getCount() {
return mAdapter =
=
null ? 0 : mAdapter.getCount();
}public Object getItem(int position) {
return mAdapter =
=
null ? null : mAdapter.getItem(position);
}public long getItemId(int position) {
return mAdapter =
=
null ? -1 : mAdapter.getItemId(position);
}public View getView(int position, View convertView, ViewGroup parent) {
return getDropDownView(position, convertView, parent);
}public View getDropDownView(int position, View convertView, ViewGroup parent) {
return (mAdapter =
=
null) ? null : mAdapter.getDropDownView(position, convertView, parent);
}// 省略代吗
}
弹出来的列表每个item的View渲染通过 getDropDownView 函数!
而mAdapter是通过构造函数传进来的!
再回到这里:
mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
这时adapter是Spinner父类AbsSpinner构造函数中new出来的 ArrayAdapter然后看 ArrayAdapter 类中的 getDropDownView 函数
@
Override
public View getDropDownView(int position, @
Nullable View convertView,
@
NonNull ViewGroup parent) {
final LayoutInflater inflater =
mDropDownInflater =
=
null ? mInflater : mDropDownInflater;
return createViewFromResource(inflater, position, convertView, parent, mDropDownResource);
}
private @
NonNull View createViewFromResource(@
NonNull LayoutInflater inflater, int position,
@
Nullable View convertView, @
NonNull ViewGroup parent, int resource) {
final View view;
final TextView text;
if (convertView =
=
null) {
view =
inflater.inflate(resource, parent, false);
} else {
view =
convertView;
}try {
if (mFieldId =
=
0) {
text =
(TextView) view;
} else {
text =
(TextView) view.findViewById(mFieldId);
if (text =
=
null) {
throw new RuntimeException("
Failed to find view with ID "
+
mContext.getResources().getResourceName(mFieldId)
+
"
in item layout"
);
}
}
} catch (ClassCastException e) {
Log.e("
ArrayAdapter"
, "
You must supply a resource ID for a TextView"
);
throw new IllegalStateException(
"
ArrayAdapter requires the resource ID to be a TextView"
, e);
}// 省略代码!
!
!
return view;
}
源码看到这里就能发现, 通过映射 mDropDownResource这个布局文件, 来得到Spinner列表的item布局!
而, 恰恰在AbsSpinner 的构造函数中设置了这一布局文件
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
文字居中
所以, 现在想要改变Spinner的文字居中显示! 则需要设置相应的adapter!OK。现在就来看这个布局文件
<
CheckedTextView xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:id=
"
@
android:id/text1"
style=
"
?android:attr/spinnerDropDownItemStyle"
android:singleLine=
"
true"
android:layout_width=
"
match_parent"
android:layout_height=
"
?android:attr/dropdownListPreferredItemHeight"
android:ellipsize=
"
marquee"
/>
可以看出item布局文件只是一个 CheckedTextView
我们现在想要把这个列表的文字居中显示!跟踪style文件发现设置的gravity属性是 center_vertical
<
item name=
"
android:gravity"
>
center_vertical<
/item>
此时尝试把这个布局文件拿出来重写。
simple_spinner_dropdown_item.xml
<
CheckedTextView xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:id=
"
@
android:id/text1"
style=
"
?android:attr/spinnerDropDownItemStyle"
android:layout_width=
"
match_parent"
android:layout_height=
"
?attr/listPreferredItemHeightSmall"
android:ellipsize=
"
marquee"
android:gravity=
"
center"
// !
!
!
android:maxLines=
"
1"
/>
然后在Activity中对Spinner对象设置适配器!
ArrayAdapter<
String>
arrayAdapter =
new ArrayAdapter<
String>
(this, android.R.layout.simple_spinner_item, dates);
arrayAdapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(arrayAdapter);
运行之后, 发现并没有居中!
文章图片
现在, 回过头来看看 mPopup.show() 显示列表的函数
加入我们选择的是dropdown模式!
DropdownPopup.show()
public void show(int textDirection, int textAlignment) {
final boolean wasShowing =
isShowing();
computeContentWidth();
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
super.show();
final ListView listView =
getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setTextDirection(textDirection);
listView.setTextAlignment(textAlignment);
setSelection(Spinner.this.getSelectedItemPosition());
// ... 省略代码
}
可以看出, 弹出来的视图就是一个ListView
针对listview设置了 direction 和 textAligment 两个属性! !
好嘛! 将 simple_spinner_dropdown_item.xml 这个布局再修改一下
<
CheckedTextView xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:id=
"
@
android:id/text1"
style=
"
?android:attr/spinnerDropDownItemStyle"
android:layout_width=
"
match_parent"
android:layout_height=
"
?attr/listPreferredItemHeightSmall"
android:ellipsize=
"
marquee"
android:gravity=
"
center"
android:maxLines=
"
1"
android:textAlignment=
"
gravity"
/>
加入了textAlignment属性!
再次运行效果如下:
文章图片
【Android 开发 Tip 6 -- Spinner】这个时候发现弹出来的列表文字是居中的! !
那现在另外一个问题是怎么让默认的那一条数据的问题也居中呢! ? ? ? ?继续来看源码!
我们知道了列表是怎么显示的了! !
那默认的那一条是怎么显示出来的呢! ?
来看Spinner的, onLayout() 函数!
@
Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout =
true;
layout(0, false);
// !!!
mInLayout =
false;
}
@
Override
void layout(int delta, boolean animate) {
// ...if (mAdapter !=
null) {
View sel =
makeView(mSelectedPosition, true);
// !!!
// ...
}// ...
}
先来看下 mSelectedPosition 的值是多少
网上找父类, 在AdapterView 中找到该变量的声明
public static final int INVALID_POSITION =
-1;
int mSelectedPosition =
INVALID_POSITION;
难道 layout(0, false); 的时候 mSelectedPosition 就是 -1 了吗? ? ?
当然不会! 因为我们知道默认选中的是列表数据的第一条
然后又是一顿狂翻代码! ~~
最后在父类的AbsSpinner中找到
@
Override
public void setAdapter(SpinnerAdapter adapter) {
// ...mOldSelectedPosition =
INVALID_POSITION;
mOldSelectedRowId =
INVALID_ROW_ID;
if (mAdapter !=
null) {
mOldItemCount =
mItemCount;
mItemCount =
mAdapter.getCount();
// ...// 当entries不为空时,
position =
0
int position =
mItemCount >
0 ? 0 : INVALID_POSITION;
setSelectedPositionInt(position);
// 省略代吗
}
子类Spinner重写了setAdapter() 函数, 所以设置适配器时, 调用了 setSelectedPositionInt(0)
而 setSelectedPosition() 函数是在父类AdapterView中!
void setSelectedPositionInt(int position) {
mSelectedPosition =
position;
mSelectedRowId =
getItemIdAtPosition(position);
}
好! 分析到这, 就会发现mSelectedPosition 是0!
接着上面的onLayout()函数分析
makeView(0, true);
private View makeView(int position, boolean addChild) {
View child;
// 省略代码child =
mAdapter.getView(position, null, this);
setUpChild(child, addChild);
return child;
}
这里的mAdpater就是系统默认设置或者我们设置的ArrayAdaper 对象。!
而在 ArrayAdapter 类中:
@
Override
public @
NonNull View getView(int position, @
Nullable View convertView,
@
NonNull ViewGroup parent) {
return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
与 createViewFromResource() 函数一样也是调用了 createViewFromResource()
只不过这里是mResource 就是我们构造ArrayAdapter时, 构造函数传入的那个布局! !
R.layout.simple_spinner_item
所以, Spinner 默认显示的布局由 ArrayAdapter构造函数中的布局决定!所以此时, 想要把默认的文字居中, 把系统的 android.R.layout.simple_spinner_item 拷贝出来修改一下!
列表item的布局由 ArrayAdapter 函数setDropDownViewResource() 传入的布局决定!
如果没有调用此方法, 则默认 mResource = mDropDownResource = resource
<
TextView xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:id=
"
@
android:id/text1"
style=
"
?android:attr/spinnerItemStyle"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
android:layout_centerInParent=
"
true"
android:ellipsize=
"
marquee"
android:gravity=
"
center"
android:maxLines=
"
1"
android:textAlignment=
"
inherit"
/>
系统并没有为 spinnerItemStyle 设置gravity属性
ArrayAdapter<
String>
arrayAdapter =
new ArrayAdapter<
String>
(this, R.layout.simple_spinner_item, dates);
arrayAdapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(arrayAdapter);
运行结果为:
文章图片
ALL RIGHT!
这个时候的结果就是我们想要的了! ! !
箭头居中
如果, 你看着这个布局还不顺眼, 想要把箭头也放到中间去( 文字右边挨着) ! 那么请往下看!
从目前的运行效果来看, 即是对Spinner设置如下属性
android:dropDownWidth=
"
match_parent"
弹出来的ListView宽度还不是match_parent!
文章图片
why?
其实右侧那个箭头是Spinner的背景图片里面的一部分!
查看Spinner的style, 不管是v21以下, 还是v21以上, 都是指了默认的background!
v21以下
<
item name=
"
android:background"
>
@
drawable/abc_spinner_mtrl_am_alpha<
/item>
文章图片
文章图片
v21及以上
<
item name=
"
background"
>
@
drawable/spinner_background_material<
/item>
spinner_background_material.xml
<
layer-list xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:paddingMode=
"
stack"
android:paddingStart=
"
0dp"
android:paddingEnd=
"
48dp"
android:paddingLeft=
"
0dp"
android:paddingRight=
"
0dp"
>
<
item
android:gravity=
"
end|fill_vertical"
android:width=
"
48dp"
android:drawable=
"
@
drawable/control_background_40dp_material"
/>
<
item
android:drawable=
"
@
drawable/ic_spinner_caret"
android:gravity=
"
end|center_vertical"
android:width=
"
24dp"
android:height=
"
24dp"
android:end=
"
12dp"
/>
<
/layer-list>
可以发现就是一个背景, 然后右侧是箭头, 左侧是编辑区域!
可编辑区域是除了箭头区域的左侧, 所以弹出列表时候, 即是设置了match_parent属性, 运行出来的效果也不是宽度全屏!把箭头去掉很简单, 在布局文件中设置一个background即可!
比如:
android:background=
"
@
android:color/white"
既然知道了默认显示的这个布局与ArrayAdapter传入的布局有关系。那么还是拿simple_spinner_item开刀!
文章图片
这是ArrayAdapter中 createViewFromResource() 函数中的片段!
**当mFieldId 是0 时, 将view布局强转成TextView !mFieldId 是ArrayAdapter 的四个参数构造函数传入来的值, 默认就是0
不是0时, 通过findViewId找到对应的TextView布局! **
所以修改mResource布局即可!
使用一个布局包括TextView, 然后设置ItemView在这个TextView的右边
<
RelativeLayout xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
>
<
TextView xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:id=
"
@
android:id/text1"
style=
"
?android:attr/spinnerItemStyle"
android:layout_width=
"
wrap_content"
android:layout_height=
"
wrap_content"
android:layout_centerInParent=
"
true"
android:ellipsize=
"
marquee"
android:gravity=
"
center"
android:maxLines=
"
1"
android:textAlignment=
"
inherit"
/>
<
ImageView
android:layout_width=
"
wrap_content"
android:layout_height=
"
wrap_content"
android:layout_centerVertical=
"
true"
android:layout_toEndOf=
"
@
android:id/text1"
android:layout_toRightOf=
"
@
android:id/text1"
android:src=
"
@
drawable/ic_arrow_drop_down_black_24dp"
/>
<
/RelativeLayout>
但是必须要保证构造ArrayAdapter的时候传入这个TextView的ID
ArrayAdapter<
String>
arrayAdapter =
new ArrayAdapter<
String>
(this, R.layout.simple_spinner_item, android.R.id.text1,dates);
此时的运行效果图如下:
文章图片
箭头添加波纹效果
针对v21的style设置ripple即可!
<
ImageView
style=
"
@
style/Spinner_Arrow"
android:layout_width=
"
wrap_content"
android:layout_height=
"
wrap_content"
android:layout_centerVertical=
"
true"
android:layout_toEndOf=
"
@
android:id/text1"
android:layout_toRightOf=
"
@
android:id/text1"
/>
这是箭头布局, 定义了一个style – Spinner_Arrow
在 values\\style.xml 中
<
style name=
"
Spinner_Arrow"
>
<
item name=
"
android:src"
>
@
drawable/ic_arrow_drop_down_black_24dp<
/item>
<
/style>
直接显示箭头图片
在 values-v21\\style.xml 中
<
style name=
"
Spinner_Arrow"
>
<
item name=
"
android:src"
>
@
drawable/ic_arrow_drop_down_ripple<
/item>
<
/style>
在这个drawable布局中定义 layer-list
<
?xml version=
"
1.0"
encoding=
"
utf-8"
?>
<
layer-list xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:paddingEnd=
"
48dp"
android:paddingLeft=
"
0dp"
android:paddingMode=
"
stack"
android:paddingRight=
"
0dp"
android:paddingStart=
"
0dp"
>
<
item
android:width=
"
48dp"
android:height=
"
48dp"
android:drawable=
"
@
drawable/control_background_40dp_material"
android:gravity=
"
end|fill_vertical"
/>
<
item
android:width=
"
24dp"
android:height=
"
24dp"
android:drawable=
"
@
drawable/ic_arrow_drop_down_black_24dp"
android:end=
"
12dp"
android:gravity=
"
end|center_vertical"
/>
<
/layer-list>
第一个item就是扩散布局, 第二个item是箭头布局
这里 control_background_40dp_material.xml 直接拷贝系统的来用!
<
ripple xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:color=
"
@
color/control_highlight_material"
android:radius=
"
20dp"
/>
OK!
此时运行在21以上手机就有波纹效果了!
文章图片
文章图片
推荐阅读
- AndroidActivity间动画跳转
- Android在项目中接入腾讯TBS浏览器WebView的教程与注意的地方
- Android 成功 使用GPS获取当前地理位置(解决getLastKnownLocation 返回 null)
- android解决webview,页面关闭之后,视频或者音频还在播放的问题
- 细数Android5.0到Android7.X 多媒体技术新特性
- 如何使用Contact Form 7创建联系表单(设置、配置和自动回复)
- WordPress如何使用MailChimp构建电子邮件列表()
- 如何在WordPress中Nofollow外部链接(解决办法)
- 如何进行销售分析(逐步使用方法和指标教程)