`

选择对话框:自定义组合控件+自定义对话框 实现

 
阅读更多

本文出自:http://blog.csdn.net/billpig/article/details/6664053

由于项目的需要,需要用到选择对话框,虽然可以使用系统自带到控件就可以实现,但是从长远的角度上去看,还是不利于本项目的发展,于是自己做了一个自定义的控件,使用到了组合控件,顺便也学习了下组合控件的创建。

自定义控件有多种方式,具体就不提及了,本次俺只使用组合控件,先上个图,让大家对本次所实现的效果有个直观的认识:


如上图所示,我希望能在点击“浏览模式”的时候弹出选择对话框(注意:这个按钮有标题和当前选择项,右边还有一个图标以提示用户),而且这个对话框是可以使用自定义的对话框。针对这种实现,我想到的可能的几种方法:

  1. 使用ListPreference
    • 难点1:使用PreferenceActivity的背景颜色不知道可以选择不? 个人觉得应该可以更改掉
    • 难点2:Preference不是要保存数据么?如果不保存数据要如何去做? 经过查证,可以设置它的persistence属性,使其不保存数据
    • 难点3:能够更改对话框的布局?应该可以,俺不确定,有待求证
  2. 使用Spinner
    • 难点1:主页面(标题、当前选项、右边加一个图标)显示使用组合对话框应该可以解决,不过好像也蛮麻烦的
    • 难点2:能够更改对话框的布局?应该可以,俺不确定,有待求证
  3. 完全自定义
    • 难点1:控件的美观程度?应该可以用图片解决
    • 难点2:弹出对话框后选择选项后改变相应的内容
    • 难点3:自定义对话框布局

起初做的时候,可能查证的信息不够多,因此,头两种方式没有求尝试实现,当时就选择了最后的一种方式。


我们选择了自定义控件的方式,于是定义了一个类ListSelectView extends LinearLayout,有如下变量

  1. privateTextViewtvHeader;//Item的标题
  2. privateTextViewtvContent;//Item的选项
  3. privateCharSequence[]mEntries;//对话框的选项
  4. privateCharSequence[]mEntryValues;//选择对话框后的值
  5. privateintmClickedDialogEntryIndex;//对话框选项的index


1、item项目实现

首先是主页的item项:



定义了如下布局:

  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:orientation="horizontal"
  5. android:layout_width="fill_parent"
  6. android:layout_height="fill_parent"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="10dp"android:paddingBottom="4dp">
  7. <LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"android:layout_weight="1">
  8. <TextViewandroid:id="@+id/tvListSelectLayoutTitle"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="0dp"/>
  9. <TextViewandroid:id="@+id/tvListSelectLayoutContent"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textSize="10dp"android:paddingTop="0dp"/>
  10. </LinearLayout>
  11. <ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:scaleType="fitCenter"android:background="@drawable/ic_btn_round_more_normal"/>
  12. </LinearLayout>


就大致了实现了这样的布局,然后在attrs.xml中定义了两个属性(可根据情况添加)
  1. <declare-styleablename="ListSelectView">
  2. <attrname="entries"format="reference"/>
  3. <attrname="entryValues"format="reference"/>
  4. </declare-styleable>

之后在在ListSelectView构造函数中初始化控件及状态
  1. publicListSelectView(Contextcontext,AttributeSetattrs){
  2. super(context,attrs);
  3. LayoutInflater.from(context).inflate(R.layout.list_select_layout,this,true);
  4. TypedArraya=context.obtainStyledAttributes(attrs,org.lansir.R.styleable.ListSelectView,0,0);
  5. mEntries=a.getTextArray(org.lansir.R.styleable.ListSelectView_entries);//从属性处初始化值
  6. mEntryValues=a.getTextArray(org.lansir.R.styleable.ListSelectView_entryValues);
  7. a.recycle();
  8. tvHeader=(TextView)findViewById(R.id.tvListSelectLayoutTitle);//初始化控件
  9. tvContent=(TextView)findViewById(R.id.tvListSelectLayoutContent);
  10. tvHeader.setTextColor(Color.BLACK);//设置标题颜色
  11. tvContent.setGravity(Gravity.TOP);
  12. this.setOnClickListener(this);//设置点击事件
  13. }



得到的效果如上,具体的美工在后期可通过贴图实现

这样,Item项就初始化完毕了

2、对话框实现

接下来是对话框的视图:

上节中,我们设置了点击事件,事件的内容如下:

  1. @Override
  2. publicfinalvoidonClick(Viewv){
  3. SingleSelectionDialogdialog=newSingleSelectionDialog.Builder(this.getContext()).setTitle(tvHeader.getText()).setSingleChoiceItems(mEntries,
  4. mClickedDialogEntryIndex,newDialogInterface.OnClickListener(){
  5. @Override
  6. publicvoidonClick(DialogInterfacedialog,intwhich){
  7. mClickedDialogEntryIndex=which;
  8. tvContent.setText(mEntryValues[mClickedDialogEntryIndex]);
  9. dialog.dismiss();
  10. }
  11. }).create();
  12. dialog.show();
  13. }

在这里自定义了一个对话框,SingleSelectionDialog是一个继承自Dialog的一个类,之所以不继承自AlertDialog,是因为AlertDialog要去自定义内容及Style比Dialog类还麻烦,要通过比较复杂的方式去实现。同时,这里setSingleChoiceItems是SingleSelectionDialog类自定义的一个封装方法,这里设置了对话框List选项的值及事件,在这个事件里,设置选项的Index,以便下次再次点击对话框时设置成已设置的值,同时,这里改变主页Item选项值t的内容及关闭对话框。


SingleSelectionDialog的代码实现如下:

  1. publicclassSingleSelectionDialogextendsDialog{
  2. publicSingleSelectionDialog(Contextcontext,booleancancelable,
  3. OnCancelListenercancelListener){
  4. super(context,cancelable,cancelListener);
  5. }
  6. publicSingleSelectionDialog(Contextcontext,inttheme){
  7. super(context,theme);
  8. }
  9. publicSingleSelectionDialog(Contextcontext){
  10. super(context);
  11. }
  12. publicstaticclassBuilder{
  13. privateContextcontext;
  14. privateCharSequencetitle;//对话框标题
  15. privateCharSequence[]mListItem;//对话框选项值
  16. privateintmClickedDialogEntryIndex;//对话框选项Index
  17. privateDialogInterface.OnClickListenermOnClickListener;//对话框点击事件
  18. publicBuilder(Contextcontext){
  19. this.context=context;
  20. }
  21. publicBuildersetTitle(inttitle){
  22. this.title=(String)context.getText(title);
  23. returnthis;
  24. }
  25. publicBuildersetTitle(CharSequencetitle){
  26. this.title=title;
  27. returnthis;
  28. }
  29. publicCharSequence[]getItems(){
  30. returnmListItem;
  31. }
  32. publicBuildersetItems(CharSequence[]mListItem){
  33. this.mListItem=mListItem;
  34. returnthis;
  35. }
  36. //设置单选List选项及事件,这些属性在之后的create中用到,这里使用Android系统创建dialog的风格
  37. publicBuildersetSingleChoiceItems(CharSequence[]items,intcheckedItem,finalOnClickListenerlistener){
  38. this.mListItem=items;
  39. this.mOnClickListener=listener;
  40. this.mClickedDialogEntryIndex=checkedItem;
  41. returnthis;
  42. }
  43. publicSingleSelectionDialogcreate(){
  44. LayoutInflaterinflater=(LayoutInflater)context
  45. .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  46. finalSingleSelectionDialogdialog=newSingleSelectionDialog(
  47. context,R.style.Theme_Dialog_ListSelect);
  48. Viewlayout=inflater.inflate(R.layout.single_selection_dialog,
  49. null);
  50. dialog.addContentView(layout,newLayoutParams(
  51. LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));
  52. if(mListItem==null){
  53. thrownewRuntimeException("Entriesshouldnotbeempty");
  54. }
  55. ListViewlvListItem=(ListView)layout.findViewById(R.id.lvListItem);
  56. //android.R.layout.simple_list_item_single_choice
  57. //lvListItem.setAdapter(newArrayAdapter(context,android.R.layout.simple_list_item_single_choice,android.R.id.text1,mListItem));
  58. //SingleSelectionAdaptermSingleSelectionAdapter=newSingleSelectionAdapter(context,R.layout.single_list_item,R.id.ctvListItem,mListItem);
  59. //lvListItem.setAdapter(mSingleSelectionAdapter);
  60. lvListItem.setAdapter(newArrayAdapter(context,R.layout.single_selection_list_item,R.id.ctvListItem,mListItem));
  61. lvListItem.setOnItemClickListener(newOnItemClickListener(){
  62. @Override
  63. publicvoidonItemClick(AdapterView<?>parent,Viewview,
  64. intposition,longid){
  65. mOnClickListener.onClick(dialog,position);
  66. }
  67. });
  68. lvListItem.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
  69. lvListItem.setItemChecked(mClickedDialogEntryIndex,true);
  70. lvListItem.setSelection(mClickedDialogEntryIndex);
  71. TextViewtvHeader=(TextView)layout.findViewById(R.id.title);
  72. tvHeader.setText(title);
  73. returndialog;
  74. }
  75. }
  76. }


setSingleSelectItems设置了单选列表的Item以及事件,这些属性在create中用到。

最关键的就是create()方法,在这里初始化dialog的主题:

  • 取消标题
  • 取消边框
  • 使用透明
style样式如下:
  1. <stylename="Theme.Dialog.ListSelect"parent="android:style/Theme.Dialog">
  2. <itemname="android:windowIsFloating">true</item>
  3. <itemname="android:windowIsTranslucent">false</item><!--半透明-->
  4. <itemname="android:backgroundDimEnabled">false</item><!--模糊-->
  5. <itemname="android:windowContentOverlay">@null</item>
  6. <itemname="android:windowNoTitle">true</item>
  7. <itemname="android:windowFrame">@null</item><!--边框-->
  8. </style>


之后是使用setContent初始化内容,这里,使用自定义布局的方式去创建,xml布局文件如下:

  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"android:layout_width="fill_parent"
  4. android:minWidth="280dip"android:layout_height="wrap_content">
  5. //标题
  6. <LinearLayoutandroid:orientation="vertical"
  7. android:background="@drawable/header"android:layout_width="fill_parent"
  8. android:layout_height="wrap_content">
  9. <TextViewstyle="@style/DialogText.Title"android:id="@+id/title"
  10. android:paddingRight="8dip"android:paddingLeft="8dip"
  11. android:background="@drawable/title"android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"android:textColor="@color/black"/>
  13. </LinearLayout>
  14. //内容
  15. <LinearLayoutandroid:id="@+id/content"
  16. android:orientation="vertical"android:background="@drawable/center"
  17. android:layout_width="fill_parent"android:layout_height="wrap_content">
  18. <ListViewandroid:layout_width="match_parent"
  19. android:layout_height="match_parent"android:layout_marginTop="5px"
  20. android:cacheColorHint="@null"android:divider="@android:drawable/divider_horizontal_bright"
  21. android:scrollbars="vertical"android:id="@+id/lvListItem"/>
  22. </LinearLayout>//底部
  23. <LinearLayoutandroid:orientation="horizontal"
  24. android:background="@drawable/footer"android:layout_width="fill_parent"
  25. android:layout_height="wrap_content"/>
  26. </LinearLayout>


整个布局分为:头部、内容、底部。头部用于显示标题, 内容用于显示单选List, 底部用于美化对话框用。

然后设置Adapter,使得adapter的layout使用我们自定义的layout,这个layout设置list选项的样式:

  1. <CheckedTextViewxmlns:android="http://schemas.android.com/apk/res/android"
  2. android:id="@+id/ctvListItem"
  3. android:layout_width="match_parent"
  4. android:layout_height="?android:attr/listPreferredItemHeight"
  5. android:textAppearance="?android:attr/textAppearanceLarge"
  6. android:gravity="center_vertical"
  7. android:checkMark="@drawable/radio_button_selector"
  8. android:paddingLeft="6dip"
  9. android:paddingRight="6dip"
  10. android:textColor="@color/black"
  11. />

在做这个layout的时候,当初确实不知道如何去做,自己本想定义一个TextView 以及一个RadioButton解决,后来发现这种解决方式一直不理想,主要问题在如何设置单选按钮,实现它的单选功能?用我们自定义的Adapter必须检测是哪个item被选中,但由于ListView的缓存机制增加了这种做法的复杂程度,我们还得为之做个缓冲,保存单选的状态才能实现。而且查看了系统Dialog的源码,发现其本就使用了缓存选项状态的方式去实现,那我们干嘛要自己定义,自己实现,还那么复杂,为何不去使用它呢?于是便查看参考了android.R.layout.simple_list_item_single_choice布局文件的内容,发现它用到了CheckedTextView的控件,但是我们并不想要系统默认的单选样式。于是,自己自定义了一个CheckedTextView,改掉它的单选样式(即android:checkMark)。

于是创建布局完毕,开始最后一步整合,即设置单选列表,容易出问题的地方来了:

  1. ListViewlvListItem=(ListView)layout.findViewById(R.id.lvListItem);
  2. lvListItem.setAdapter(newArrayAdapter(context,R.layout.single_selection_list_item,R.id.ctvListItem,mListItem));
  3. lvListItem.setOnItemClickListener(newOnItemClickListener(){
  4. @Override
  5. publicvoidonItemClick(AdapterView<?>parent,Viewview,
  6. intposition,longid){
  7. mOnClickListener.onClick(dialog,position);
  8. }
  9. });
  10. lvListItem.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
  11. lvListItem.setItemChecked(mClickedDialogEntryIndex,true);
  12. lvListItem.setSelection(mClickedDialogEntryIndex);
我们使用系统自定义的Adapter,发现它能为我们解决很多问题,如上述的缓存选项状态的问题。这里表明看起来没什么讲究,其实大有讲究:setChoiceMode必须在setItemChecked前调用,否则setChoiceMode会失效,这个也是我查看了Android的ListView的源码实现后才发现的。

这样,选择对话框:自定义组合控件+自定义对话框 完成,使用起来超级简单,完全不需要自己去做很多的设置,我的整个Activity如:

  1. publicclassSharePrefersActivityextendsActivity{
  2. privateListSelectViewmListSelectView;
  3. @Override
  4. publicvoidonCreate(BundlesavedInstanceState){
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.main);
  7. mListSelectView=(ListSelectView)findViewById(R.id.lsvTest);
  8. mListSelectView.setHeader("Hello");
  9. mListSelectView.setContent("world");
  10. }

便能实现如下的效果,来个截图纪念下:



这样,就大功告成了。

3、注意选项

在做这个DEMO的时候,我想设置主页Item的select,于是便在构造函数里使用this.setBackground()去设置,发现这样设置后,在xml里对ListSelectView设置paddingLeft, layout_margin等属性时都会便无效,这里记住这个错误的方式,以提醒后来人!


源代码下载


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics