转载请注明出处:http://blog.csdn.net/crazy1235/article/details/70903974


设置Spinner 文字居中

默认情况下,Spinner控件的效果是这样的:

Android 开发 Tip 6 -- Spinner 文字 & icon 居中显示_第1张图片

想让文字居中显示怎么办???

在布局文件中设置

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 adapter = new ArrayAdapter(                    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

子类Spinner重写了setAdapter 函数

@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_verticalitem>

此时尝试把这个布局文件拿出来重写。

simple_spinner_dropdown_item.xml

"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 arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, dates);arrayAdapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);spinner.setAdapter(arrayAdapter);

运行之后,发现并没有居中!

Smiley face

现在,回过头来看看 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设置了 directiontextAligment 两个属性!!

好嘛! 将 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 文字 & icon 居中显示_第2张图片

这个时候发现弹出来的列表文字是居中的!!


那现在另外一个问题是怎么让默认的那一条数据的问题也居中呢!????

继续来看源码!

我们知道了列表是怎么显示的了!!
那默认的那一条是怎么显示出来的呢!?

来看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构造函数中的布局决定!
列表item的布局由 ArrayAdapter 函数setDropDownViewResource() 传入的布局决定!
如果没有调用此方法,则默认 mResource = mDropDownResource = resource


所以此时,想要把默认的文字居中,把系统的 android.R.layout.simple_spinner_item 拷贝出来修改一下!

<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 arrayAdapter = new ArrayAdapter(this, R.layout.simple_spinner_item, dates);        arrayAdapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);        spinner.setAdapter(arrayAdapter);

运行结果为:

Android 开发 Tip 6 -- Spinner 文字 & icon 居中显示_第3张图片

ALL RIGHT!

这个时候的结果就是我们想要的了!!!


箭头居中

如果,你看着这个布局还不顺眼,想要把箭头也放到中间去(文字右边挨着)!那么请往下看!

从目前的运行效果来看,即是对Spinner设置如下属性

android:dropDownWidth="match_parent"

弹出来的ListView宽度还不是match_parent!

Smiley face

why?

其实右侧那个箭头是Spinner的背景图片里面的一部分!

查看Spinner的style,不管是v21以下,还是v21以上,都是指了默认的background!

v21以下

<item name="android:background">@drawable/abc_spinner_mtrl_am_alphaitem>

Android 开发 Tip 6 -- Spinner 文字 & icon 居中显示_第4张图片

Android 开发 Tip 6 -- Spinner 文字 & icon 居中显示_第5张图片

v21及以上

 <item name="background">@drawable/spinner_background_materialitem>

spinner_background_material.xml

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" />list>

可以发现就是一个背景,然后右侧是箭头,左侧是编辑区域!

可编辑区域是除了箭头区域的左侧,所以弹出列表时候,即是设置了match_parent属性,运行出来的效果也不是宽度全屏!


把箭头去掉很简单,在布局文件中设置一个background即可!

比如:

android:background="@android:color/white"

既然知道了默认显示的这个布局与ArrayAdapter传入的布局有关系。那么还是拿simple_spinner_item开刀!

Android 开发 Tip 6 -- Spinner 文字 & icon 居中显示_第6张图片

这是ArrayAdapter中 createViewFromResource() 函数中的片段!

**当mFieldId 是0 时,将view布局强转成TextView !
不是0时,通过findViewId找到对应的TextView布局!**

mFieldId 是ArrayAdapter 的四个参数构造函数传入来的值,默认就是0

所以修改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 arrayAdapter = new ArrayAdapter(this, R.layout.simple_spinner_item, android.R.id.text1,  dates);

此时的运行效果图如下:

Android 开发 Tip 6 -- Spinner 文字 & icon 居中显示_第7张图片


箭头添加波纹效果

针对v21的style设置ripple即可!

"@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    style>

直接显示箭头图片

在 values-v21\style.xml 中

<style name="Spinner_Arrow">        <item name="android:src">@drawable/ic_arrow_drop_down_ripple    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 直接拷贝系统的来用!

"http://schemas.android.com/apk/res/android"        android:color="@color/control_highlight_material"        android:radius="20dp" />

OK!

此时运行在21以上手机就有波纹效果了!

Android 开发 Tip 6 -- Spinner 文字 & icon 居中显示_第8张图片


Android 开发 Tip 6 -- Spinner 文字 & icon 居中显示_第9张图片

更多相关文章

  1. Android中的五大布局和logcat打印日志
  2. Android 如何实现带滚动条的TextView,在更新文字时自动滚动到最后
  3. Android相对布局管理器RelativeLayout
  4. android开发小技巧(2)背景按钮等布局的好朋友shape应用
  5. [Android1.5]标签TabHost图片文字覆盖的问题
  6. android 4.0横屏重复调用onCreate()函数
  7. Android RecyclerView 的瀑布流式布局

随机推荐

  1. Android中AsyncTask使用教程及源码分析
  2. Android中静态变量的生命周期
  3. Android/iOS逆向&调试难度对比
  4. Android(安卓)手机app三种方法获取定位地
  5. Android音频系统之AudioTrack起播线与und
  6. AIDL中使用Interface接口回调
  7. Android(安卓)进阶:JSON数据与Java对象转
  8. Android(安卓)游戏存档位置分析
  9. Android数据传输加密(一):Base64转码算法
  10. Android(安卓)8.0 SystemUI 源码分析(二):启