因为系统提供的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实现内部判空及提示