Android之自定义spinner—TextView+PopWindow

  • Post author:
  • Post category:其他


因为系统提供的Spinner不能够满足业务需求:

1.默认展示hint

2.点击时若无数据则提示用户

3.有数据时点击展示列表,用户不选择时不赋值,用户选择赋值并触发选择事件

4.可以动态设置数据,设置数据后自动展开列表,不做默认选择

5.重新初始化spinner,展示hint,清空数据

于是根据上述需求根据TextView+Popwindow自定义了一个仿系统spinner,支持xml配置数据和提示,二话不说先看效果图:

根据需求,先画出自定义spinner的流程图:

步骤1:新建BasePopWindow,继承PopupWindow,除自动设置可点击以及电机外部消失外,设置外部半透明的背景,并在show和dismiss的时候自动展示及隐藏(此处引用他人代码)

public class BasePopWindow extends PopupWindow {
    //ValueAnimator会从1平滑过渡到0的值的效果
    private ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(500);

    public BasePopWindow(View contentView, int width, int height, boolean focusable) {
        super(contentView, width, height, focusable);
        setBackgroundDrawable(new ColorDrawable(-0x90000000));//设置背景半透明
        update();//刷新update()在以下状态下要更新 ,This include: setClippingEnabled(boolean), setFocusable(boolean), setIgnoreCheekPress(),setInputMethodMode(int), setTouchable(boolean), and setAnimationStyle(int).
        setTouchable(true);// 设置PopupWindow可触摸
        setOutsideTouchable(true);// 设置允许在外点击消失
        contentView.setFocusableInTouchMode(true);//调用View的setFocusableInTouchMode(true)可以使View在Touch Mode模式之下仍然可获得焦点
    }

    @Override
    public void showAtLocation(View parent, int gravity, int x, int y) {
        super.showAtLocation(parent, gravity, x, y);
        showTransBackground(true);
    }

    @Override
    public void showAsDropDown(View anchor) {
        super.showAsDropDown(anchor);
        showTransBackground(true);
    }

    @Override
    public void dismiss() {
        super.dismiss();
        showTransBackground(false);
    }

    public void showTransBackground(final boolean isTrans) {
        final Window window = ((Activity) getContentView().getContext()).getWindow();
        if (window == null) {
            return;
        }
        valueAnimator.removeAllListeners();
        valueAnimator.removeAllUpdateListeners();
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction = (float) animation.getAnimatedValue();
                WindowManager.LayoutParams lp = window.getAttributes();
                if (isTrans) {
                    lp.alpha = 0.7f + 0.3f * fraction;//从不透明平滑过渡到0.7
                } else {
                    lp.alpha = 0.7f + (1 - fraction) * 0.3f;//从0.7平滑过渡到不透明
                }
                window.setAttributes(lp);
            }
        });
        valueAnimator.start();
    }
}

步骤2:自定义SpinnerPopWindow继承BasePopWindow,内部自动填充RecyclerView,实现RecyclerView.Adapter以及ViewHolder,layout文件可以自动设置,也可以暴露给外面设置,此处我默认设置了。自定义OnRecyclerItemClickListener,实现用户选择事件的外部监听:

public class SpinnerPopWindow<T> extends BasePopWindow {
    private LayoutInflater inflater;
    private RecyclerView mListView;
    private List<T> list;
    private PopUpAdapter popAdapter;

    //声明自定义的监听接口
    private OnRecyclerItemClickListener onItemClickListener;

    //提供set方法供Activity或Fragment调用
    public void setRecyclerItemClickListener(OnRecyclerItemClickListener listener){
        onItemClickListener=listener;
    }

    public SpinnerPopWindow(Context context,List<T> list,OnRecyclerItemClickListener listener) {
        super(LayoutInflater.from(context).inflate(R.layout.spinner_window_layout, null),
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                true);
        inflater = LayoutInflater.from(context);
        this.list = list;
        this.onItemClickListener = listener;
        init();
    }

    private void init(){
        setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
        setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
        setFocusable(true);
        mListView = getContentView().findViewById(R.id.popup_listview);
        if(null == list){
            list = new ArrayList<>();
        }
        mListView.setAdapter(popAdapter = new PopUpAdapter(list));
    }

    public void setNewData(List<T> list){
        this.list = list;
        popAdapter.setNewData(list);
    }

    private class PopUpAdapter extends RecyclerView.Adapter<PopUpAdapter.ViewHolder> {

        private List<T> list;

        public PopUpAdapter(List<T> list) {
            this.list = list;
        }

        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

            View convertView = inflater.inflate(R.layout.spinner_pop_item,parent,false);
            ViewHolder viewHolder = new ViewHolder(convertView);

            return viewHolder;
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            holder.textTv.setText(list.get(position).toString());
        }

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

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

        public void setNewData(List<T> list){
            this.list = list;
            notifyDataSetChanged();
        }


        private class ViewHolder extends RecyclerView.ViewHolder{
            public TextView textTv;

            public ViewHolder(@NonNull View itemView) {
                super(itemView);
                textTv = itemView.findViewById(R.id.pop_item_text);
                itemView.findViewById(R.id.pop_item_layout).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if(null != onItemClickListener){
                            onItemClickListener.onItemClick(getAdapterPosition(),textTv,list);
                        }
                    }
                });
            }
        }

    }

    public interface OnRecyclerItemClickListener {
        //RecyclerView的点击事件,将信息回调给view
        void onItemClick(int position, TextView textView,List<?> list);
    }
}

步骤3:实现自定义Spinner,有两种方式:LinearLayout+TextView+PopWindow;TextView+PopWindow,经过比较我选择后一种方式,并且可以实现默认的hint以及setText方法,不需要动态visible和gone。配置TypedArray实现默认数组填充以及toast,自定义OnViewClickListener及OnItemSelectListener实现点击文本监听以及列表选择器选择监听:

public class SpinnerTextView extends androidx.appcompat.widget.AppCompatTextView implements SpinnerPopWindow.OnRecyclerItemClickListener, View.OnClickListener {

    private List<String> dataList;
    private SpinnerPopWindow<String> popWindow;
    private String toastString="";
    private OnItemSelectListener onItemSelectListener;
    private OnViewClickListener onViewClickListener;

    public SpinnerTextView(Context context) {
        super(context);
    }

    public SpinnerTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs);
    }

    public SpinnerTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        dataList = new ArrayList<>();
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SpinnerTextView);
        if(null != ta) {
            CharSequence[] arrays = ta.getTextArray(R.styleable.SpinnerTextView_android_entries);
            if(null != arrays){
                for (CharSequence charSequence:arrays) {
                    if(null != charSequence) {
                        dataList.add(charSequence.toString());
                    }else{
                        dataList.add("");
                    }
                }
            }
            toastString = ta.getString(R.styleable.SpinnerTextView_toast);
            ta.recycle();
        }
        popWindow = new SpinnerPopWindow<>(context,dataList,this);
        setOnClickListener(this);
    }

    @Override
    public void onItemClick(int position, TextView textView, List<?> list) {
        Object object = list.get(position);
        String[] result = {""};
        if(null != object){
            result[0] = object.toString();
        }
        setText(result[0]);
        if(null != onItemSelectListener) {
            onItemSelectListener.OnItemSelect(position, result[0]);
        }
        popWindow.dismiss();
    }


    @Override
    public void onClick(View v) {
        if(null == dataList || dataList.size()==0) {
            if (!TextUtils.isEmpty(toastString)) {
                Toast.makeText(getContext(), toastString, Toast.LENGTH_SHORT).show();
            }
        }else{
            popWindow.showAsDropDown(v);
        }
        if(null != onViewClickListener){
            onViewClickListener.viewClick(v);
        }
    }

    public void reInit(){
        dataList = new ArrayList<>();
        setText("");
    }

    public OnItemSelectListener getOnItemSelectListener() {
        return onItemSelectListener;
    }

    public void setOnItemSelectListener(OnItemSelectListener onItemSelectListener) {
        this.onItemSelectListener = onItemSelectListener;
    }

    public OnViewClickListener getOnViewClickListener() {
        return onViewClickListener;
    }

    public void setOnViewClickListener(OnViewClickListener onViewClickListener) {
        this.onViewClickListener = onViewClickListener;
    }

    public void setNewData(List<String> dataList){
        if(null == dataList) {
            dataList = new ArrayList<>();
        }
        this.dataList = dataList;
        popWindow.setNewData(this.dataList);
        popWindow.showAsDropDown(this);
    }

    public interface OnViewClickListener{
        public void viewClick(View v);
    }

    public interface OnItemSelectListener{
        public void OnItemSelect(int position,String text);
    }
}

步骤四,直接在xml中配置,外部引用即可:

public class CustomSpinnerActivity extends Activity {
    private Button btnPopupWindow;
    private SpinnerTextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom_popup_window);
        btnPopupWindow = (Button) findViewById(R.id.btn_popup_window);
        btnPopupWindow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                textView.reInit();
            }
        });

        textView = findViewById(R.id.custom_text);
        textView.setOnItemSelectListener(new SpinnerTextView.OnItemSelectListener() {
            @Override
            public void OnItemSelect(int position, String text) {
                Toast.makeText(getApplicationContext(),text,Toast.LENGTH_SHORT).show();
            }
        });

        findViewById(R.id.btn_popup_list).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String[] data = getResources().getStringArray(R.array.spinner_item);
                textView.setNewData(Arrays.asList(data));
            }
        });
    }
}

以上,自定义Spinner完成,代码就不提交了,下面是各个资源文件内容:

activity_custom_popup_window:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_popup_window"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="清空"

        />
    <Button
        android:id="@+id/btn_popup_list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="设置数据"

        />


    <java.wen.com.view.SpinnerTextView
        android:id="@+id/custom_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="请选择地区类别"
        app:toast="地区选择失败,无地区数据!"
        android:entries="@array/spinner_item"
        android:layout_gravity="center"/>
</LinearLayout>
spinner_window_layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/popup_listview"
        android:scrollbars="none"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>

</LinearLayout>
pop_item_text:
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:id="@+id/pop_item_layout">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/pop_item_image"
        android:visibility="gone"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/pop_item_text"
        android:textColor="@android:color/black"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>

</LinearLayout>

至此,全部完成。。。下一步工作,自定义EditText,根据Java的Annotation实现内部判空及提示



版权声明:本文为wenqiang0718原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。