美文网首页Android人生几何?
Android控件<第十篇>:Spinner和AppCompat

Android控件<第十篇>:Spinner和AppCompat

作者: NoBugException | 来源:发表于2021-08-30 01:45 被阅读0次

Spinner和AppCompatSpinner是实现下拉菜单的一种方式,本章将详细讲解下它的使用。两者其实是一个东西,只是在不同的包下罢了,本章就针对Spinner讲解了。
点击Spinner控件,可以弹出下拉列表,选择列表中的某项即为选中状态。

最简单的Spinner是使用entries适配列表数据,代码如下:

<Spinner
    android:id="@+id/spinner"
    android:layout_marginTop="10dp"
    android:layout_width="200dp"
    android:layout_height="50dp"
    android:entries="@array/spinner_values" />
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="spinner_values">
        <item>嫦娥</item>
        <item>上官婉儿</item>
        <item>猪八戒</item>
        <item>司马懿</item>
        <item>盘古</item>
        <item>伽罗</item>
        <item>艾琳</item>
        <item>韩信</item>
    </array>
</resources>

效果如下:

144.gif

Spinner有一个父类:AbsSpinner,AbsSpinner是一个抽象类,是不可以直接拿来使用的,但是它有一个直观重要的属性:AbsSpinner_entries

图片.png

这个属性就是以上使用的entries属性。

当然,SpinnersetAdapter方法可以配置列表值,setAdapterd的形参是SpinnerAdapter对象,SpinnerAdapter是一个接口,所以这里实际adapter必须实现SpinnerAdapter接口。

现在已知实现SpinnerAdapter接口,并且用public修饰的非内部类的adapter有BaseRecipientAdapterSuggestedLocaleAdapterMenuAdapterAccountViewAdapterActionsAdapterArrayAdapterRemoteViewsAdapterMenuAdapterSimpleAdapterOverflowMenuAdapterFakeAdapterPreferenceGroupAdapterItemAdapterTimeZoneResultAdapterTimeZoneFilterTypeAdapter,这些都是SDK自带的,当然,也可以自定义一个实现SpinnerAdapter接口的Adapter。

SDK自带的Adapter有很多,但是暂时无法选择,那么就先自定义一个Adapter来举例吧。

自定义Adapter如下:

public class MyAdapter extends BaseAdapter {

    private List<String> dataList;
    private Context mContext;

    public MyAdapter(Context mContext, List<String> list){
        this.mContext = mContext;
        dataList = list;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = layoutInflater.inflate(R.layout.layout_spinner, parent, false);
        TextView textView = view.findViewById(R.id.textview);
        textView.setText(dataList.get(position));
        return view;
    }

    @Override
    public int getCount() {
        return dataList == null ? 0 : dataList.size();
    }

    @Override
    public String getItem(int position) {
        return dataList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

}

MyAdapter继承了BaseAdapter,BaseAdapter实现了SpinnerAdapter接口,符合Spinner的Adapter参数类型。

使用方法如下:

    dataList.add("嫦娥");
    dataList.add("上官婉儿");
    dataList.add("猪八戒");
    dataList.add("司马懿");
    dataList.add("盘古");
    dataList.add("伽罗");
    dataList.add("艾琳");
    dataList.add("韩信");

    spinner.setAdapter(new MyAdapter(MainActivity.this, dataList));

Item布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="20dp">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>

    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="测试"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_gravity="center"
        android:layout_marginLeft="30dp"
        android:textColor="#92C457"/>

</LinearLayout>

效果如下:

145.gif

我们继续分析setAdapter源码,源码如下:

@Override
public void setAdapter(SpinnerAdapter adapter) {
    // The super constructor may call setAdapter before we're prepared.
    // Postpone doing anything until we've finished construction.
    if (mPopup == null) {
        mTempAdapter = adapter;
        return;
    }

    super.setAdapter(adapter);

    mRecycler.clear();

    final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
    if (targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
            && adapter != null && adapter.getViewTypeCount() != 1) {
        throw new IllegalArgumentException("Spinner adapter view type count must be 1");
    }

    final Context popupContext = mPopupContext == null ? mContext : mPopupContext;
    mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
}

可以看到,adapter被DropDownAdapter所装饰(装饰设计模式),继续看一下DropDownAdapter源码

    public DropDownAdapter(@Nullable SpinnerAdapter adapter,
            @Nullable Resources.Theme dropDownTheme) {
        mAdapter = adapter;

        if (adapter instanceof ListAdapter) {
            mListAdapter = (ListAdapter) adapter;
        }

        if (dropDownTheme != null && adapter instanceof ThemedSpinnerAdapter) {
            final ThemedSpinnerAdapter themedAdapter = (ThemedSpinnerAdapter) adapter;
            if (themedAdapter.getDropDownViewTheme() == null) {
                themedAdapter.setDropDownViewTheme(dropDownTheme);
            }
        }
    }

当popup设置了主题,并且adapter直接或间接是ThemedSpinnerAdapter的子类,那么才可以真正成功设置主题。

图片.png

如图,ThemedSpinnerAdapter的父类是SpinnerAdapter ,上面已经指出,直接或间接实现SpinnerAdapter 接口的Adapter有:BaseRecipientAdapterSuggestedLocaleAdapterMenuAdapterAccountViewAdapterActionsAdapterArrayAdapterRemoteViewsAdapterMenuAdapterSimpleAdapterOverflowMenuAdapterFakeAdapterPreferenceGroupAdapterItemAdapterTimeZoneResultAdapterTimeZoneFilterTypeAdapter,那么,这些Adapter中直接或间接实现ThemedSpinnerAdapter接口的有哪些呢?

只有两个:分别是SimpleAdapterArrayAdapter,这两个Adapter也是Spinner常用的Adapter了。

ArrayAdapter使用比较简单,根据它的构造方法传递参数即可,构造方法如下:

/**
 * Constructor
 *
 * @param context The current context.
 * @param resource The resource ID for a layout file containing a TextView to use when
 *                 instantiating views.
 * @param objects The objects to represent in the ListView.
 */
public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
        @NonNull List<T> objects) {
    this(context, resource, 0, objects);
}
/**
 * Constructor
 *
 * @param context The current context.
 * @param resource The resource ID for a layout file containing a layout to use when
 *                 instantiating views.
 * @param textViewResourceId The id of the TextView within the layout resource to be populated
 * @param objects The objects to represent in the ListView.
 */
public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
        @IdRes int textViewResourceId, @NonNull List<T> objects) {
    this(context, resource, textViewResourceId, objects, false);
}

ArrayAdapter的构造方法大致分为两种,一种需要指定TextView的ID,另一种不需要指定TextView的ID。

构造方法有几个参数需要说明一下:

objects:存放数据的数组,一般是字符串。
resource:资源文件
textViewResourceId:指定textview的ID

如果需要指定textview的ID,那么资源布局可能是这样的

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="20dp">

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>

    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="测试"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_gravity="center"
        android:layout_marginLeft="30dp"
        android:textColor="#92C457"/>

</LinearLayout>

如果不需要指定textview的ID,那么资源布局必然是这样的

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

ArrayAdapter的存在大大说短了构造Adapter的时间,但是它只能适配文本列表,而其它数据(比如图片)只能默认写死。

SimpleAdapter可以解决ArrayAdapter数据单一的弊端,它的使用方法大致如下:

    List<HashMap<String, ?>> list = new ArrayList<>();
    HashMap<String, Object> hashMap = new HashMap<>();
    hashMap.put("文字", "嫦娥");
    hashMap.put("图片", R.mipmap.ic_launcher);
    list.add(hashMap);
    hashMap = (HashMap<String, Object>) hashMap.clone();
    hashMap.put("文字", "上官婉儿");
    hashMap.put("图片", R.mipmap.ic_launcher_round);
    list.add(hashMap);
    hashMap = (HashMap<String, Object>) hashMap.clone();
    hashMap.put("文字", "猪八戒");
    hashMap.put("图片", R.mipmap.ic_launcher_round);
    list.add(hashMap);
    hashMap = (HashMap<String, Object>) hashMap.clone();
    hashMap.put("文字", "司马懿");
    hashMap.put("图片", R.mipmap.ic_launcher);
    list.add(hashMap);
    hashMap = (HashMap<String, Object>) hashMap.clone();
    hashMap.put("文字", "盘古");
    hashMap.put("图片", R.mipmap.ic_launcher_round);
    list.add(hashMap);
    hashMap = (HashMap<String, Object>) hashMap.clone();
    hashMap.put("文字", "伽罗");
    hashMap.put("图片", R.mipmap.ic_launcher);
    list.add(hashMap);
    hashMap = (HashMap<String, Object>) hashMap.clone();
    hashMap.put("文字", "艾琳");
    hashMap.put("图片", R.mipmap.ic_launcher_round);
    list.add(hashMap);
    hashMap = (HashMap<String, Object>) hashMap.clone();
    hashMap.put("文字", "韩信");
    hashMap.put("图片", R.mipmap.ic_launcher);
    list.add(hashMap);
    spinner.setAdapter(new SimpleAdapter(MainActivity.this, list, R.layout.layout_spinner, new String[]{"图片", "文字"}, new int[]{R.id.imageview, R.id.textview}));

SimpleAdapter可以传递多个HashMap,使数据的多样化,解析SimpleAdapter的终点在于其构造方法。

public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
        @LayoutRes int resource, String[] from, @IdRes int[] to) {
    mData = data;
    mResource = mDropDownResource = resource;
    mFrom = from;
    mTo = to;
    mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

resource:是资源文件
data:是数据
from:指定Map的key,传递key的数组
to:指定那些key所对应组件的id

展示效果如下:

147.gif

Adapter适配总结:

综上所述,Spinner数据的适配有三种方式:

  • 自定义Adapter,这种方法是一个比较好的方法,但是构造Adapter比较繁琐;
  • ArrayAdapter,这种方式可以快速的适配文本列表,即使布局中可以添加其他组件,但是其他组件只能固定写死,而唯一可以设置不同值的就只有文本了;
  • SimpleAdapter,这是Spinner数据适配的亮点,可以任意配置各种类型的数据,以及数据和组件之间一一对应的关系。(最方便)

最后,为了完善这篇文章,Spinner固有的属性还是要提一下的。

Spinner有哪些属性呢?从源码中可以获取。

图片.png

Spinner弹出的列表本质上是一个PopupWindow或者AlertDialog。

  • dropDownSelector

点击列表上某数据时显示的颜色。(这个设置之后发现没效果)

  • dropDownWidth

设置popupWindow的宽度,如这个布局

<Spinner
    android:id="@+id/spinner"
    android:layout_marginTop="10dp"
    android:layout_width="200dp"
    android:dropDownWidth="150dp"
    android:layout_height="wrap_content"
    android:entries="@array/spinner_values" />

Spinner宽度本身是200dp,默认情况下,popupWindow的宽度和Spinner宽度一样,如果将popupWindow的宽度设置成150dp,那么效果图如下:

图片.png
  • popupBackground

设置popupWindow的背景色

图片.png
  • spinnerMode

设置Spinner模式,有两种模式:dropdown和dialog,默认是dropdown模式。

dropdown模式下,Spinner弹出的列表本质上是PopupWindow;
dialog模式下,Spinner弹出的列表本质上是AlertDialog;

  • prompt

设置标题提示,目测在dropdown不生效,在dialog模式下,AlertDialog顶端会有一个提示,如图:

图片.png

[本章完...]

相关文章

网友评论

    本文标题:Android控件<第十篇>:Spinner和AppCompat

    本文链接:https://www.haomeiwen.com/subject/gjdwiltx.html