首页 > 代码库 > Android 开发 Tip 6 -- Spinner

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

子类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_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设置了 directiontextAligment 两个属性!!!

技术分享


技术分享

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android 开发 Tip 6 -- Spinner