由于项目的需要,需要用到选择对话框,虽然可以使用系统自带到控件就可以实现,但是从长远的角度上去看,还是不利于本项目的发展,于是自己做了一个自定义的控件,使用到了组合控件,顺便也学习了下组合控件的创建。
自定义控件有多种方式,具体就不提及了,本次俺只使用组合控件,先上个图,让大家对本次所实现的效果有个直观的认识:
如上图所示,我希望能在点击“浏览模式”的时候弹出选择对话框(注意:这个按钮有标题和当前选择项,右边还有一个图标以提示用户),而且这个对话框是可以使用自定义的对话框。针对这种实现,我想到的可能的几种方法:
- 使用ListPreference
- 难点1:使用PreferenceActivity的背景颜色不知道可以选择不? 个人觉得应该可以更改掉
- 难点2:Preference不是要保存数据么?如果不保存数据要如何去做? 经过查证,可以设置它的persistence属性,使其不保存数据
- 难点3:能够更改对话框的布局?应该可以,俺不确定,有待求证
- 使用Spinner
- 难点1:主页面(标题、当前选项、右边加一个图标)显示使用组合对话框应该可以解决,不过好像也蛮麻烦的
- 难点2:能够更改对话框的布局?应该可以,俺不确定,有待求证
- 完全自定义
- 难点1:控件的美观程度?应该可以用图片解决
- 难点2:弹出对话框后选择选项后改变相应的内容
- 难点3:自定义对话框布局
起初做的时候,可能查证的信息不够多,因此,头两种方式没有求尝试实现,当时就选择了最后的一种方式。
我们选择了自定义控件的方式,于是定义了一个类ListSelectView extends LinearLayout,有如下变量
private TextView tvHeader; //Item 的标题 private TextView tvContent; //Item的选项 private CharSequence[] mEntries; //对话框的选项 private CharSequence[] mEntryValues; //选择对话框后的值 private int mClickedDialogEntryIndex; //对话框选项的index
首先是主页的item项:
定义了如下布局:
就大致了实现了这样的布局,然后在attrs.xml中定义了两个属性(可根据情况添加)
之后在在ListSelectView构造函数中初始化控件及状态public ListSelectView(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.list_select_layout, this, true); TypedArray a = context.obtainStyledAttributes(attrs, org.lansir.R.styleable.ListSelectView, 0, 0); mEntries = a.getTextArray(org.lansir.R.styleable.ListSelectView_entries); //从属性处初始化值 mEntryValues = a.getTextArray(org.lansir.R.styleable.ListSelectView_entryValues); a.recycle(); tvHeader = (TextView) findViewById(R.id.tvListSelectLayoutTitle); //初始化控件 tvContent = (TextView) findViewById(R.id.tvListSelectLayoutContent); tvHeader.setTextColor(Color.BLACK); //设置标题颜色 tvContent.setGravity(Gravity.TOP); this.setOnClickListener(this); //设置点击事件 }
得到的效果如上,具体的美工在后期可通过贴图实现
这样,Item项就初始化完毕了
接下来是对话框的视图:
上节中,我们设置了点击事件,事件的内容如下:
@Override public final void onClick(View v) { SingleSelectionDialog dialog = new SingleSelectionDialog.Builder(this.getContext()).setTitle(tvHeader.getText()).setSingleChoiceItems(mEntries, mClickedDialogEntryIndex, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mClickedDialogEntryIndex = which; tvContent.setText(mEntryValues[mClickedDialogEntryIndex]); dialog.dismiss(); } }).create(); dialog.show(); }
在这里自定义了一个对话框,SingleSelectionDialog是一个继承自Dialog的一个类,之所以不继承自AlertDialog,是因为AlertDialog要去自定义内容及Style比Dialog类还麻烦,要通过比较复杂的方式去实现。同时,这里setSingleChoiceItems是SingleSelectionDialog类自定义的一个封装方法,这里设置了对话框List选项的值及事件,在这个事件里,设置选项的Index,以便下次再次点击对话框时设置成已设置的值,同时,这里改变主页Item选项值t的内容及关闭对话框。
SingleSelectionDialog的代码实现如下:
public class SingleSelectionDialog extends Dialog { public SingleSelectionDialog(Context context, boolean cancelable, OnCancelListener cancelListener) { super(context, cancelable, cancelListener); } public SingleSelectionDialog(Context context, int theme) { super(context, theme); } public SingleSelectionDialog(Context context) { super(context); } public static class Builder { private Context context; private CharSequence title; //对话框标题 private CharSequence[] mListItem; //对话框选项值 private int mClickedDialogEntryIndex; //对话框选项Index private DialogInterface.OnClickListener mOnClickListener; //对话框点击事件 public Builder(Context context) { this.context = context; } public Builder setTitle(int title) { this.title = (String) context.getText(title); return this; } public Builder setTitle(CharSequence title) { this.title = title; return this; } public CharSequence[] getItems() { return mListItem; } public Builder setItems(CharSequence[] mListItem) { this.mListItem = mListItem; return this; } //设置单选List选项及事件,这些属性在之后的create中用到,这里使用Android系统创建dialog的风格 public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) { this.mListItem = items; this.mOnClickListener = listener; this.mClickedDialogEntryIndex = checkedItem; return this; } public SingleSelectionDialog create() { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); final SingleSelectionDialog dialog = new SingleSelectionDialog( context, R.style.Theme_Dialog_ListSelect); View layout = inflater.inflate(R.layout.single_selection_dialog, null); dialog.addContentView(layout, new LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); if(mListItem == null){ throw new RuntimeException("Entries should not be empty"); } ListView lvListItem = (ListView) layout.findViewById(R.id.lvListItem); // android.R.layout.simple_list_item_single_choice //lvListItem.setAdapter(new ArrayAdapter(context, android.R.layout.simple_list_item_single_choice, android.R.id.text1, mListItem)); // SingleSelectionAdapter mSingleSelectionAdapter = new SingleSelectionAdapter(context, R.layout.single_list_item, R.id.ctvListItem, mListItem); // lvListItem.setAdapter(mSingleSelectionAdapter); lvListItem.setAdapter(new ArrayAdapter(context, R.layout.single_selection_list_item, R.id.ctvListItem, mListItem)); lvListItem.setOnItemClickListener(new OnItemClickListener(){ @Override public void onItemClick(AdapterView> parent, View view, int position, long id) { mOnClickListener.onClick(dialog, position); } }); lvListItem.setChoiceMode(ListView.CHOICE_MODE_SINGLE); lvListItem.setItemChecked(mClickedDialogEntryIndex, true); lvListItem.setSelection(mClickedDialogEntryIndex); TextView tvHeader = (TextView)layout.findViewById(R.id.title); tvHeader.setText(title); return dialog; } } }
setSingleSelectItems设置了单选列表的Item以及事件,这些属性在create中用到。
最关键的就是create()方法,在这里初始化dialog的主题:
style样式如下:
- 取消标题
- 取消边框
- 使用透明
之后是使用setContent初始化内容,这里,使用自定义布局的方式去创建,xml布局文件如下:
//标题 //内容 //底部
整个布局分为:头部、内容、底部。头部用于显示标题, 内容用于显示单选List, 底部用于美化对话框用。然后设置Adapter,使得adapter的layout使用我们自定义的layout,这个layout设置list选项的样式:
在做这个layout的时候,当初确实不知道如何去做,自己本想定义一个TextView 以及一个RadioButton解决,后来发现这种解决方式一直不理想,主要问题在如何设置单选按钮,实现它的单选功能?用我们自定义的Adapter必须检测是哪个item被选中,但由于ListView的缓存机制增加了这种做法的复杂程度,我们还得为之做个缓冲,保存单选的状态才能实现。而且查看了系统Dialog的源码,发现其本就使用了缓存选项状态的方式去实现,那我们干嘛要自己定义,自己实现,还那么复杂,为何不去使用它呢?于是便查看参考了android.R.layout.simple_list_item_single_choice布局文件的内容,发现它用到了CheckedTextView的控件,但是我们并不想要系统默认的单选样式。于是,自己自定义了一个CheckedTextView,改掉它的单选样式(即android:checkMark)。于是创建布局完毕,开始最后一步整合,即设置单选列表,容易出问题的地方来了:
我们使用系统自定义的Adapter,发现它能为我们解决很多问题,如上述的缓存选项状态的问题。这里表明看起来没什么讲究,其实大有讲究:setChoiceMode必须在setItemChecked前调用,否则setChoiceMode会失效,这个也是我查看了Android的ListView的源码实现后才发现的。ListView lvListItem = (ListView) layout.findViewById(R.id.lvListItem); lvListItem.setAdapter(new ArrayAdapter(context, R.layout.single_selection_list_item, R.id.ctvListItem, mListItem)); lvListItem.setOnItemClickListener(new OnItemClickListener(){ @Override public void onItemClick(AdapterView> parent, View view, int position, long id) { mOnClickListener.onClick(dialog, position); } }); lvListItem.setChoiceMode(ListView.CHOICE_MODE_SINGLE); lvListItem.setItemChecked(mClickedDialogEntryIndex, true); lvListItem.setSelection(mClickedDialogEntryIndex);
这样,选择对话框:自定义组合控件+自定义对话框 完成,使用起来超级简单,完全不需要自己去做很多的设置,我的整个Activity如:
public class SharePrefersActivity extends Activity { private ListSelectView mListSelectView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mListSelectView = (ListSelectView)findViewById(R.id.lsvTest); mListSelectView.setHeader("Hello"); mListSelectView.setContent("world"); }
便能实现如下的效果,来个截图纪念下:
这样,就大功告成了。
在做这个DEMO的时候,我想设置主页Item的select,于是便在构造函数里使用this.setBackground()去设置,发现这样设置后,在xml里对ListSelectView设置paddingLeft, layout_margin等属性时都会便无效,这里记住这个错误的方式,以提醒后来人!
版权所有,转载请出明出处!源代码下载