android杂谈

android自定义滚动选择器(三)

2019-01-24  本文已影响213人  寒潇2018

本篇文章将会阐述ScrollPickerAdapter及默认的item视图DefaultItemViewProvider的具体实现,ScrollPickerAdapter的设计在文章android自定义滚动选择器(一)
已经详细阐述过,这里照例直接从代码的角度进行阐述。

如果来不及阅读文章,或者想直接获取源码,见git:android自定义滚动选择器

ScrollPickerAdapter解析

根据前面分析,ScrollPickerAdapter首先要继承RecyclerView.Adapter并实现IPickerViewOperation接口,这里我们就从这两个方面进行分析。

继承RecyclerView.Adapter必须要复写其中的抽象方法,这个是无法避免的,只不过我们要明确在每个方法中应该做哪些事情,如下所示:

    @NonNull
    @Override
    public ScrollPickerAdapterHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (mViewProvider == null) {
            mViewProvider = new DefaultItemViewProvider();
        }
        return new ScrollPickerAdapterHolder(LayoutInflater.from(mContext).inflate(mViewProvider.resLayout(), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ScrollPickerAdapterHolder holder, int position) {
        mViewProvider.onBindView(holder.itemView, mDataList.get(position));
    }

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

我们分开来阐述下每个方法完成的功能:

  1. onCreateViewHolder方法,这个方法的目的显然需要返回一个viewHolder,这里我们直接返回了ScrollPickerAdapterHolder,ScrollPickerAdapterHolder的具体定义如下:
    static class ScrollPickerAdapterHolder extends RecyclerView.ViewHolder {
        private View itemView;

        private ScrollPickerAdapterHolder(@NonNull View view) {
            super(view);
            itemView = view;
        }
    }

ScrollPickerAdapterHolder接收一个View视图,这个就是我们的item视图,所以我们需要在构造ScrollPickerAdapterHolder的时候传入item视图,那么这个item视图该如何提供?

按照常规方法,可以直接在onCreateViewHolder方法中,通过LayoutInflater inflate具体的视图,但是这么做显然无法满足我们的需求,即无法满足用户可以自定义的需求,那么如果要满足用户自定义的需求该怎么办?

答案是我们将视图的构造入口暴露给用户即可,因此,这里我们提供一个视图提供接口,如下所示:

public interface IViewProvider<T> {
//提供layout布局id
    @LayoutRes
    int resLayout();
//对应于adapter中的onBindView方法
    void onBindView(@NonNull View view, @Nullable T itemData);
//选择滚动器滚动的时候,通知外界视图更新的接口
    void updateView(@NonNull View itemView, boolean isSelected);
}

通过提供IViewProvider接口,我们就能够满足用户自定义的需求。但是,我们同样需要提供一个默认item视图实现,当用户不需要自定义的时候,可以使用默认的item视图,所以在onCreateViewHolder中,我们做了一下判断:

//当用户没有提供view provider的时候,使用默认item视图提供者
        if (mViewProvider == null) {
            mViewProvider = new DefaultItemViewProvider();
        }

对于DefaultItemViewProvider的实现,会在下面进行分析。

  1. onBindViewHolder方法,其实现代码如下所示:
    @Override
    public void onBindViewHolder(@NonNull ScrollPickerAdapterHolder holder, int position) {
        mViewProvider.onBindView(holder.itemView, mDataList.get(position));
    }

onBindViewHolder本身的功能就是完成holder视图和数据的绑定,这里因为我们允许用户自定义item视图,所以就直接委托给view provider进行实现。

至此,关于adapter自身的一些方法就阐述完了,下面来看一下IPickerViewOperation相应的方法,在ScrollPickerAdapter中的实现,如下所示:

    @Override
    public int getSelectedItemOffset() {
        return mSelectedItemOffset;
    }

    @Override
    public int getVisibleItemNumber() {
        return mVisibleItemNum;
    }

    @Override
    public int getLineColor() {
        return mLineColor;
    }
    @Override
    public void updateView(View itemView, boolean isSelected) {
        mViewProvider.updateView(itemView, isSelected);
        adaptiveItemViewSize(itemView);
        itemView.setOnClickListener(isSelected ? new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickListener != null) {
                    mOnItemClickListener.onSelectedItemClicked(v);
                }
            }
        } : null);
        if (isSelected && mOnScrollListener != null) {
            mOnScrollListener.onScrolled(itemView);
        }
    }

除了updateView,其他的方法都是数据提供功能,这些都是由外界塞进来的,所以这里主要关注下updateView。

再次阐述下updateView方法的作用,updateView就是在ScrollPickerView滚动的时候,及时通知外界。updateView包括两个入参:一个是当前的item视图,一个是标识当前item视图是否被选中。外界拿到这两个参数后可以根据自己的需求来定制化,比如将选中的item视图文字变大、变色等。

在ScrollPickerAdapter中的updateView中,我们主要完成以下几个工作:

  1. 调用用户提供的view provider的updateView,即通知对方视图要更新。
  2. 对于滚动选择器中的item视图,提供了一个默认的点击事件,这个事件只有在item视图被选中的时候才有。其实用户完全可以在自己的view provider中,通过updateView来完成业务逻辑处理,这里只是通过adapter对外暴露一个监听响应入口,能满足基本需要。
  3. 与此同时,我们还提供了一个滚动监听,滚动监听的方法入口是onScrolled,该方法有一个参数currentItemView,表示当前被选中的item视图。
  4. 在updateView方法中,调用了 adaptiveItemViewSize(itemView)方法,这个方法的目的是保证item视图的最小高度和宽度,如下所示:
    private void adaptiveItemViewSize(View itemView) {
        int h = itemView.getHeight();
        if (h > maxItemH) {
            maxItemH = h;
        }

        int w = itemView.getWidth();
        if (w > maxItemW) {
            maxItemW = w;
        }

        itemView.setMinimumHeight(maxItemH);
        itemView.setMinimumWidth(maxItemW);
    }

好了,adapter相关的基本阐述完了,那么还有一个问题,如何保证外部定制数据能直接有效的在视图构建前生效?这个问题在第一篇文章中分析过,那就是采用builder设计模式,如下所示:

    public static class ScrollPickerAdapterBuilder<T> {
        private ScrollPickerAdapter mAdapter;

        public ScrollPickerAdapterBuilder(Context context) {
            mAdapter = new ScrollPickerAdapter<T>(context);
        }

        public ScrollPickerAdapterBuilder<T> selectedItemOffset(int offset) {
            mAdapter.mSelectedItemOffset = offset;
            return this;
        }

        public ScrollPickerAdapterBuilder<T> setDataList(List<T> list) {
            mAdapter.mDataList.clear();
            mAdapter.mDataList.addAll(list);
            return this;
        }

        public ScrollPickerAdapterBuilder<T> setOnClickListener(OnClickListener listener) {
            mAdapter.mOnItemClickListener = listener;
            return this;
        }

        public ScrollPickerAdapterBuilder<T> visibleItemNumber(int num) {
            mAdapter.mVisibleItemNum = num;
            return this;
        }

        public ScrollPickerAdapterBuilder<T> setItemViewProvider(IViewProvider viewProvider) {
            mAdapter.mViewProvider = viewProvider;
            return this;
        }

        public ScrollPickerAdapterBuilder<T> setDivideLineColor(String colorString) {
            mAdapter.mLineColor = Color.parseColor(colorString);
            return this;
        }

        public ScrollPickerAdapterBuilder<T> setOnScrolledListener(OnScrollListener listener) {
            mAdapter.mOnScrollListener = listener;
            return this;
        }

        public ScrollPickerAdapter build() {
            adaptiveData(mAdapter.mDataList);
            mAdapter.notifyDataSetChanged();
            return mAdapter;
        }

        private void adaptiveData(List list) {
            int visibleItemNum = mAdapter.mVisibleItemNum;
            int selectedItemOffset = mAdapter.mSelectedItemOffset;
            for (int i = 0; i < mAdapter.mSelectedItemOffset; i++) {
                list.add(0, null);
            }

            for (int i = 0; i < visibleItemNum - selectedItemOffset - 1; i++) {
                list.add(null);
            }
        }
    }

上面,我们通过ScrollPickerAdapterBuilder暴露给外界定制入口,主要关注一个方法,就是build方法。在build方法中主要调用了adaptiveData方法,这个方法功能很重要,下面分析下它的实现。

首先来看下adaptiveData方法。该方法的功能是用于数据填充,比如两条分割线偏移量为n个item视图,那么我们就需要在其前面补充n个item视图,这样才能保证能有机会选中所有的item视图,如下所示:

        private void adaptiveData(List list) {
            int visibleItemNum = mAdapter.mVisibleItemNum;
            int selectedItemOffset = mAdapter.mSelectedItemOffset;
            for (int i = 0; i < mAdapter.mSelectedItemOffset; i++) {
                list.add(0, null);//在滚动器前面增加数据,item数据值为空
            }

            for (int i = 0; i < visibleItemNum - selectedItemOffset - 1; i++) {
                list.add(null);//在滚动器后面增加数据,item数据值为空
            }
        }

上面代码需要注意的是,因为我们补充数据的时候,补充的是null,所以在接收数据的时候一定要进行非空判断,在阐述默认item视图的时候会有所阐述。

item的默认视图提供者 DefaultItemViewProvider

这个就是本案例提供的默认的item视图提供者,阐述DefaultItemViewProvider的目的更多的是为自定义view provider提供思路。其完整代码如下所示:

public class DefaultItemViewProvider implements IViewProvider<String> {
    @Override
    public int resLayout() {
        return R.layout.scroll_picker_default_item_layout;
    }

    @Override
    public void onBindView(@NonNull View view, @Nullable String text) {
        TextView tv = view.findViewById(R.id.tv_content);
        tv.setText(text);
        view.setTag(text);
        tv.setTextSize(18);
    }

    @Override
    public void updateView(@NonNull View itemView, boolean isSelected) {
        TextView tv = itemView.findViewById(R.id.tv_content);
        tv.setTextSize(isSelected ? 18 : 14);
        tv.setTextColor(Color.parseColor(isSelected ? "#ED5275" : "#000000"));
    }
}

首先,view provider必须要实现IViewProvider接口,DefaultItemViewProvider也不例外,唯一注意的是IViewProvider本身是泛型的,所以我们需要提供item视图对应的数据类型,这里我们直接使用String类型即可。

而对于DefaultItemViewProvider的逻辑,我们只需要实现IViewProvider接口中的抽象方法即可。所以这里对其中的方法实现进行下阐述。

  1. resLayout方法,这个方法很简单,就是提供我们自己的itme视图布局文件,默认的视图如下所示:
<?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:layout_gravity="center"
    android:gravity="center">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:singleLine="true"
        android:maxLines="1"
        android:padding="10dp"
        android:textColor="#666666" />
</LinearLayout>

很简单,就一个TextView,不再阐述。

  1. onBindView,这个在上面已经阐述过,就是对应于adapter中的onBindView,在这里主要是进行视图初始化,并设置了textview的一些属性,比如字体大小等。

这里需要注意两点:

第一点,textview设置的字体大小应该是你期望的最大的字体大小,比如,如果想要被选中的item字体大小是18sp,而未选中的item字体大小是16sp,那么这里应该设置最大的18sp;

第二点,我们设置了view的tag(即 view.setTag(text);
),传入的是与item视图对应的数据,这么做是有原因的,因为我们前面通过adapter对外暴露的监听接口,无论是onClick接口还是onScroll接口,其回调数据都是item视图,并没有item对应的具体data数据,所以这里通过将item对应的数据设置为视图tag的方法,来进行数据传递,这样就可以通过getTag获取到对应的item数据了。

  1. updateView,这个方法前面也已经阐述过了,在这里我们对选中的item视图文字进行了处理,即设置选中的item视图字体大小为18sp,颜色是红色,而未选中的item视图字体大小为14sp,颜色是黑色。

使用姿势

本小节阐述下,该控件的使用姿势。
首先,在需要使用滚动选择器的地方,引入我们滚动选择器视图,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context=".demo.sample1.SampleActivity">

    <com.life2smile.scrollpicker.library.view.ScrollPickerView
        android:id="@+id/scroll_picker_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent">

    </com.life2smile.scrollpicker.library.view.ScrollPickerView>

</LinearLayout>

接着,和使用RecyclerView一样,完成正常的视图初始化即可,如下所示:

        ScrollPickerAdapter.ScrollPickerAdapterBuilder<String> builder =
                new ScrollPickerAdapter.ScrollPickerAdapterBuilder<String>(this)
                        .setDataList(list)
                        .selectedItemOffset(1)
                        .visibleItemNumber(3)
                        .setDivideLineColor("#E5E5E5")
                        .setItemViewProvider(null)
                        .setOnClickListener(new ScrollPickerAdapter.OnClickListener() {
                            @Override
                            public void onSelectedItemClicked(View v) {
                                String text = (String) v.getTag();
                                if (text != null) {
                                    Toast.makeText(SampleActivity.this, text, Toast.LENGTH_SHORT).show();
                                }
                            }
                        });
        ScrollPickerAdapter mScrollPickerAdapter = builder.build();
        mScrollPickerView.setAdapter(mScrollPickerAdapter);

上述代码就是具体的调用姿态,具体不再展开。

至此本篇文章的主题已阐述完毕。

上一篇下一篇

猜你喜欢

热点阅读