ToolBar使用详解
来源:互联网 发布:汽车工作知乎 编辑:程序博客网 时间:2024/06/07 14:45
请尊重他人劳动成果,请勿随意剽窃,转载请注明,谢谢!转载请注明出处:http://blog.csdn.net/evan_man/article/details/51684947
注意:使用ToolBar的话,一定要将当前Activity或者Application的Theme设置为NoActionBar样式,如<style name=”AppTheme” parent=”Theme.AppCompat.Light.NoActionBar”>,否则ActionBar和ToolBar两者都会显示,而且setSupportActionBar方法将会抛出异常,其实使用了ToolBar完全可以把ActionBar抛到一边去,只是使用ToolBar就能完成所有ActionBar的功能。
ToolBar是在Android5.0中提出来的,ActionBar在Android3.0提出来的。ActionBar是系统为我们创建一个View并显示在应用的顶端,直接继承自Object,因此我们如果要对这块内容的视图进行定制就需要严格按照ActionBar的方式进行工作。ToolBar是一个继承自ViewGroup的控件,因此我们可以用ViewGroup的方式去创建任何我们想要的视图内容,也可在布局文件中写入我们想要的内容,因此ToolBar比ActionBar更加灵活。同时ToolBar相对于ViewGroup的好处在于,提供了更多便捷的方法来控制显示方式,这些方式主要是针对标题栏的特性而定制的,比如我们可以通过setTitle方法为其设置标题。下图是网上找的Toolbar提供的便捷方法所对应的修改的内容:
上图对应方法有:toolbar.setTitle(“AndroidViewDemo”); 、toolbar.inflateMenu(R.menu.ac_toolbar_menu); 、toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {…}等。
Toolbar与ActionBar一样也支持对弹出菜单样式的修改,只不过在将Toolbar单独作为控件使用的情况下不能像ActionBar那样便捷地通过Theme来修改,Toolbar提供了一个setPopupTheme方法和对应的app.popupTheme属性来设置弹出菜单的样式。
标题栏本身就是一个特殊的View为何不然View自己来处理呢?所以还是建议使用ToolBar的。如果你习惯了ActionBar那么你也可以在创建完Toolbar之后通过setSupportActionBar方法将Toolbar设置为当前Activity的ActionBar,之后的所有操作跟ActionBar都是一样的了。
简单使用:
一、编写菜单栏布局(标题栏右边最多显示三个Action,其余则隐藏)
menu_main.xml
- <menu xmlns:android=“http://schemas.android.com/apk/res/android”
- xmlns:app=“http://schemas.android.com/apk/res-auto”>
- <item
- android:id=“@+id/menu_tool”
- android:icon=“@drawable/toolbar1”
- app:showAsAction=“ifRoom”
- android:orderInCategory=“50”
- android:title=“Tool”>
- </item>
- <item
- android:id=“@+id/menu_finder”
- android:icon=“@drawable/toolbar2”
- app:showAsAction=“ifRoom”
- android:orderInCategory=“10”
- android:title=“Find”>
- </item>
- </menu>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_tool" android:icon="@drawable/toolbar1" app:showAsAction="ifRoom" android:orderInCategory="50" android:title="Tool"> </item> <item android:id="@+id/menu_finder" android:icon="@drawable/toolbar2" app:showAsAction="ifRoom" android:orderInCategory="10" android:title="Find"> </item></menu>
注意:
android:title=”XX”属性用于设置当该item在overflow menu显示时显示的文字内容。app:showAsAction=”ifRoom” 用于设置在有足够空间的情况下在toolbar中显示Action的图标。
二、main_activity.xml布局文件中加入<Toolbar/>标签
- <android.support.v7.widget.Toolbar xmlns:android=“http://schemas.android.com/apk/res/android”
- xmlns:app=“http://schemas.android.com/apk/res-auto”
- android:id=“@+id/toolbar”
- android:layout_width=“match_parent”
- android:layout_height=“wrap_content”
- android:minHeight=“?attr/actionBarSize”
- android:background=“?attr/colorPrimary”
- app:contentInsetStart=“0dp”
- app:contentInsetLeft=“0dp”
- app:theme=“@style/MyToolBarTheme”
- app:titleTextAppearance=“@style/Toolbar.TitleText”
- >
- <!–此处可以加你想要的View控件,如同LinearLayout类似的使用–>
- </android.support.v7.widget.Toolbar>
- <!–android:contentInsertStart contentInsetLeft是没有效果的不能保证控件靠近左边显示–>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?attr/actionBarSize" android:background="?attr/colorPrimary" app:contentInsetStart="0dp" app:contentInsetLeft="0dp" app:theme="@style/MyToolBarTheme" app:titleTextAppearance="@style/Toolbar.TitleText" > <!--此处可以加你想要的View控件,如同LinearLayout类似的使用--></android.support.v7.widget.Toolbar><!--android:contentInsertStart contentInsetLeft是没有效果的不能保证控件靠近左边显示-->
ToolBar在此可以就看成一个普通的ViewGroup,因此它显示的位置可以随意设置,但是如果要模拟ActionBar的功能,则需要将其放置在视图的最顶部。
三、Activity中获取ToolBar控件并做相应设置
1、初始化
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- toolbar = (Toolbar)findViewById(R.id.toolbar);
- toolbar.setNavigationIcon(R.mipmap.back);
- //设置标题栏左边的图标样式,对应id为android.id.home ,用于返回上一级目录
- setSupportActionBar(toolbar);
- //将ToolBar对象设置为当前Activity的ActionBar
- }
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationIcon(R.mipmap.back); //设置标题栏左边的图标样式,对应id为android.id.home ,用于返回上一级目录 setSupportActionBar(toolbar); //将ToolBar对象设置为当前Activity的ActionBar}
2、加载显示Menu布局,重写Activity的onCreateOptionMenu方法
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.menu_main, menu);
- return true;
- }
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true;}
3、监听标题栏中的点击事件
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()){
- case R.id.menu_finder:
- Toast.makeText(MainActivity.this,“touch finderItem”,Toast.LENGTH_SHORT).show();
- AndroidUtils.changeActivity(this,SearchActivity.class); //跳转到搜索页Activity
- break;
- case R.id.menu_tool:
- Toast.makeText(MainActivity.this,“touch toolItem”,Toast.LENGTH_SHORT).show();
- AndroidUtils.showPopupMenu(this,toolbar,R.menu.popmenu,new OnPopupMenuItemClickListener(this)); //弹出共享菜单
- break;
- case android.R.id.home:
- Toast.makeText(MainActivity.this,“touch up navigation”,Toast.LENGTH_SHORT).show();
- AndroidUtils.backToParent(this); //返回上一级目录
- break;
- default: return super.onOptionsItemSelected(item);
- }
- return true;
- }
- /**
- * 弹出共享菜单
- */
- public static void showPopupMenu(Context context,
- View v,int menuResId, PopupMenu.OnMenuItemClickListener onMenuItemClickListener,
- PopupMenu.OnDismissListener onDismissListener,int gravity){
- PopupMenu popup = new PopupMenu(context, v);
- MenuInflater inflater = popup.getMenuInflater();
- inflater.inflate(menuResId, popup.getMenu());
- //利用反射显示图标
- try {
- Field field = popup.getClass().getDeclaredField(”mPopup”);
- field.setAccessible(true);
- MenuPopupHelper mHelper = (MenuPopupHelper) field.get(popup);
- mHelper.setForceShowIcon(true);
- } catch (IllegalAccessException | NoSuchFieldException e) {
- e.printStackTrace(); }
- popup.setOnMenuItemClickListener(onMenuItemClickListener);
- if(onDismissListener!=null) popup.setOnDismissListener(onDismissListener);
- popup.setGravity(gravity);
- popup.show();
- }
- /**
- * 跳转到父Activity
- */
- public static void backToParent(Activity activity){
- Intent upIntent = NavUtils.getParentActivityIntent(activity);
- if(upIntent == null) {
- activity.finish();
- return;
- }
- if (NavUtils.shouldUpRecreateTask(activity, upIntent)) {
- // This activity is NOT part of this app’s task, so create a new task
- // when navigating up, with a synthesized back stack.
- TaskStackBuilder.create(activity)
- // Add all of this activity’s parents to the back stack
- .addNextIntentWithParentStack(upIntent)
- // Navigate up to the closest parent
- .startActivities();
- } else {
- // This activity is part of this app’s task, so simply
- // navigate up to the logical parent activity.
- NavUtils.navigateUpTo(activity, upIntent);
- }
- }
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.menu_finder: Toast.makeText(MainActivity.this,"touch finderItem",Toast.LENGTH_SHORT).show(); AndroidUtils.changeActivity(this,SearchActivity.class); //跳转到搜索页Activity break; case R.id.menu_tool: Toast.makeText(MainActivity.this,"touch toolItem",Toast.LENGTH_SHORT).show(); AndroidUtils.showPopupMenu(this,toolbar,R.menu.popmenu,new OnPopupMenuItemClickListener(this)); //弹出共享菜单 break; case android.R.id.home: Toast.makeText(MainActivity.this,"touch up navigation",Toast.LENGTH_SHORT).show(); AndroidUtils.backToParent(this); //返回上一级目录 break; default: return super.onOptionsItemSelected(item); } return true;} /** * 弹出共享菜单 */ public static void showPopupMenu(Context context, View v,int menuResId, PopupMenu.OnMenuItemClickListener onMenuItemClickListener, PopupMenu.OnDismissListener onDismissListener,int gravity){ PopupMenu popup = new PopupMenu(context, v); MenuInflater inflater = popup.getMenuInflater(); inflater.inflate(menuResId, popup.getMenu()); //利用反射显示图标 try { Field field = popup.getClass().getDeclaredField("mPopup"); field.setAccessible(true); MenuPopupHelper mHelper = (MenuPopupHelper) field.get(popup); mHelper.setForceShowIcon(true); } catch (IllegalAccessException | NoSuchFieldException e) { e.printStackTrace(); } popup.setOnMenuItemClickListener(onMenuItemClickListener); if(onDismissListener!=null) popup.setOnDismissListener(onDismissListener); popup.setGravity(gravity); popup.show(); } /** * 跳转到父Activity */ public static void backToParent(Activity activity){ Intent upIntent = NavUtils.getParentActivityIntent(activity); if(upIntent == null) { activity.finish(); return; } if (NavUtils.shouldUpRecreateTask(activity, upIntent)) { // This activity is NOT part of this app's task, so create a new task // when navigating up, with a synthesized back stack. TaskStackBuilder.create(activity) // Add all of this activity's parents to the back stack .addNextIntentWithParentStack(upIntent) // Navigate up to the closest parent .startActivities(); } else { // This activity is part of this app's task, so simply // navigate up to the logical parent activity. NavUtils.navigateUpTo(activity, upIntent); } }
注意: NavUtils 类的静态方法navigateUpFromSameTask()的效果是:finishes当前Activity并start或者resume其父Activity,如果父Activity在任务的返回栈中,那么正常回退。注意该方法仅仅适用于当当前应用是当前任务的所有者时方可使用。如果当前应用不是当前任务的所有者时,需要建立一个新的返回栈,如果你Activity能够接受其它应用通过Intent开启你的Activity,那么就需要注意这样的处理,具体内容就是第三步的case android.R.id.home下面的内容。如果你期望回退到父Activity时,其还能保持原来的状态,那么父Activity可能需要设置启动方式为android:launchMode=”singleTop”。
(补充)四、Fragment中使用ToolBar
- @Override public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // …
- setHasOptionsMenu(true);
- }
- @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- // Inflate the menu; this adds items to the action bar.
- inflater.inflate(R.menu.my_menu, menu);
- // …
- }
- @Override public boolean onOptionsItemSelected(MenuItem item) {
- // handle item selection
- switch (item.getItemId()) {
- case R.id.my_item:
- // Handle this selection
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... setHasOptionsMenu(true);}@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Inflate the menu; this adds items to the action bar. inflater.inflate(R.menu.my_menu, menu); // ...}@Override public boolean onOptionsItemSelected(MenuItem item) { // handle item selection switch (item.getItemId()) { case R.id.my_item: // Handle this selection return true; default: return super.onOptionsItemSelected(item); }}
可以发现Fragment除了第一步之外其它都是和Activity类似的。
(补充)五、ToolBar的Style设定
ToolBar的构造器中有如下一行语句:
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), null, R.styleable.Toolbar, R.attr.toolbarStyle, 0);方法底层会调用TypedArray array = context.obtainStyledAttributes(null, R.styleable.Toolbar, R.attr.toolbarStyle, 0);即从主题中定义的toolbarStyle样式文件中和主题直接定义的属性中获取到如下属性:
- <declare-styleable name=“Toolbar”>
- <attr format=“reference” name=“titleTextAppearance”/>
- <attr format=“reference” name=“subtitleTextAppearance”/>
- <attr name=“title”/>
- <attr name=“subtitle”/>
- <attr name=“android:gravity”/>
- <attr format=“dimension” name=“titleMargins”/>
- <attr format=“dimension” name=“titleMarginStart”/>
- <attr format=“dimension” name=“titleMarginEnd”/>
- <attr format=“dimension” name=“titleMarginTop”/>
- <attr format=“dimension” name=“titleMarginBottom”/>
- <attr name=“contentInsetStart”/>
- <attr name=“contentInsetEnd”/>
- <attr name=“contentInsetLeft”/>
- <attr name=“contentInsetRight”/>
- <attr format=“dimension” name=“maxButtonHeight”/>
- <attr format=“reference” name=“collapseIcon”/>
- <attr format=“string” name=“collapseContentDescription”/>
- <attr name=“Theme”/> <!–这里可以修改ToolBar除了PopupMenu的所有信息(如背景色:注意java代码中的setBackgroundColor这个方法不要用存在bug!!!效果不好,使用这里的background属性进行设置) 设置的值可以参考主题ThemeOverlay.AppCompat.Dark.ActionBar–>
- <attr name=“popupTheme”/> <!–这里只能修改由ToolBar创建的弹出菜单,而对于自己通过创建PopupMenu对象显示的只能通过对context设置对应的主题进行设置,设置的值可以参考ThemeOverlay.AppCompat.Light–>
- <attr format=“reference” name=“navigationIcon”/>
- <attr format=“string” name=“navigationContentDescription”/>
- <attr name=“android:minHeight”/>
- <attr name=“logo”/>
- <attr format=“string” name=“logoDescription”/>
- <attr format=“color” name=“titleTextColor”/>
- <attr format=“color” name=“subtitleTextColor”/>
- </declare-styleable>
<declare-styleable name="Toolbar"> <attr format="reference" name="titleTextAppearance"/> <attr format="reference" name="subtitleTextAppearance"/> <attr name="title"/> <attr name="subtitle"/> <attr name="android:gravity"/> <attr format="dimension" name="titleMargins"/> <attr format="dimension" name="titleMarginStart"/> <attr format="dimension" name="titleMarginEnd"/> <attr format="dimension" name="titleMarginTop"/> <attr format="dimension" name="titleMarginBottom"/> <attr name="contentInsetStart"/> <attr name="contentInsetEnd"/> <attr name="contentInsetLeft"/> <attr name="contentInsetRight"/> <attr format="dimension" name="maxButtonHeight"/> <attr format="reference" name="collapseIcon"/> <attr format="string" name="collapseContentDescription"/> <attr name="Theme"/> <!--这里可以修改ToolBar除了PopupMenu的所有信息(如背景色:注意java代码中的setBackgroundColor这个方法不要用存在bug!!!效果不好,使用这里的background属性进行设置) 设置的值可以参考主题ThemeOverlay.AppCompat.Dark.ActionBar--> <attr name="popupTheme"/> <!--这里只能修改由ToolBar创建的弹出菜单,而对于自己通过创建PopupMenu对象显示的只能通过对context设置对应的主题进行设置,设置的值可以参考ThemeOverlay.AppCompat.Light--> <attr format="reference" name="navigationIcon"/> <attr format="string" name="navigationContentDescription"/> <attr name="android:minHeight"/> <attr name="logo"/> <attr format="string" name="logoDescription"/> <attr format="color" name="titleTextColor"/> <attr format="color" name="subtitleTextColor"/></declare-styleable>
深入分析:
public class Toolbar extends ViewGroup
Toolbar()@Toolbar.class
- public Toolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- // Need to use getContext() here so that we use the themed context
- final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, R.styleable.Toolbar, defStyleAttr, 0);
- mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
- ……//get操作
- final CharSequence title = a.getText(R.styleable.Toolbar_title);
- if (!TextUtils.isEmpty(title)) {
- setTitle(title);
- }
- final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
- if (!TextUtils.isEmpty(subtitle)) {
- setSubtitle(subtitle);
- }
- …..//set操作
- }
public Toolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // Need to use getContext() here so that we use the themed context final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, R.styleable.Toolbar, defStyleAttr, 0); mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0); ......//get操作 final CharSequence title = a.getText(R.styleable.Toolbar_title); if (!TextUtils.isEmpty(title)) { setTitle(title); } final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle); if (!TextUtils.isEmpty(subtitle)) { setSubtitle(subtitle); } .....//set操作}
构造器除了调用ViewGroup的构造器之外,还会获取当前View所属Context的主题相关的属性。
onMeasure()@Toolbar.class
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = 0;
- int height = 0;
- int childState = 0;
- ………//note1
- final int measuredWidth = ViewCompat.resolveSizeAndState(
- Math.max(width, getSuggestedMinimumWidth()),
- widthMeasureSpec, childState & ViewCompat.MEASURED_STATE_MASK);
- final int measuredHeight = ViewCompat.resolveSizeAndState(
- Math.max(height, getSuggestedMinimumHeight()),
- heightMeasureSpec, childState << ViewCompat.MEASURED_HEIGHT_STATE_SHIFT);
- setMeasuredDimension(measuredWidth, shouldCollapse() ? 0 : measuredHeight);
- }
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 0; int height = 0; int childState = 0; .........//note1 final int measuredWidth = ViewCompat.resolveSizeAndState( Math.max(width, getSuggestedMinimumWidth()), widthMeasureSpec, childState & ViewCompat.MEASURED_STATE_MASK); final int measuredHeight = ViewCompat.resolveSizeAndState( Math.max(height, getSuggestedMinimumHeight()), heightMeasureSpec, childState << ViewCompat.MEASURED_HEIGHT_STATE_SHIFT); setMeasuredDimension(measuredWidth, shouldCollapse() ? 0 : measuredHeight);}
1、根据Toolbar中子View设置当前Toolbar显示高度宽度。
onLayout()@Toolbar.class
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- final boolean isRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
- //Horizontal layout direction of this view is from Right to Left.
- final int width = getWidth();
- final int height = getHeight();
- final int paddingLeft = getPaddingLeft();
- final int paddingRight = getPaddingRight();
- final int paddingTop = getPaddingTop();
- final int paddingBottom = getPaddingBottom();
- int left = paddingLeft;
- int right = width - paddingRight;
- ….
- if (shouldLayout(mNavButtonView)) {….}
- if (shouldLayout(mCollapseButtonView)) {….}
- if (shouldLayout(mMenuView)) {….}
- ….
- addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
- //ArrayList<View> mTempViews = new ArrayList<View>();
- final int leftViewsCount = mTempViews.size();
- for (int i = 0; i < leftViewsCount; i++) {
- left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins,
- alignmentHeight);
- }
- addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);{…… }
- addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL);{…… }
- mTempViews.clear();
- }
protected void onLayout(boolean changed, int l, int t, int r, int b) { final boolean isRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL; //Horizontal layout direction of this view is from Right to Left. final int width = getWidth(); final int height = getHeight(); final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); int left = paddingLeft; int right = width - paddingRight; .... if (shouldLayout(mNavButtonView)) {....} if (shouldLayout(mCollapseButtonView)) {....} if (shouldLayout(mMenuView)) {....} .... addCustomViewsWithGravity(mTempViews, Gravity.LEFT); //ArrayList<View> mTempViews = new ArrayList<View>(); final int leftViewsCount = mTempViews.size(); for (int i = 0; i < leftViewsCount; i++) { left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins, alignmentHeight); } addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);{...... } addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL);{...... } mTempViews.clear();}方法先对Toolbar中的导航栏、菜单栏、logo和标题等ActionBar中的属性进行布局。最后才给自己的childView进行布局。
Reference:http://blog.csdn.net/aigestudio/article/details/47090167
阅读全文
0 0
- Toolbar使用详解
- Toolbar的使用详解
- ToolBar的使用详解
- Toolbar使用详解
- ToolBar的使用详解
- ToolBar的使用详解
- ToolBar使用详解
- Toolbar使用详解
- Toolbar的使用详解
- Android ToolBar使用详解
- android之Toolbar使用详解
- Toolbar 使用详解与示例
- ToolBar简介以及使用详解
- android之Toolbar使用详解
- ToolBar使用详解+ToolBar按钮颜色修正方法
- UINavigationController详解与使用(三)ToolBar
- UINavigationController详解与使用(三)ToolBar
- ToolBar的使用详解及配合TabLayout
- python实例(复制列表)
- Java泛型总结
- emWin & STemWin & uCGUI 的中文支持
- Topological Sorting(拓扑排序必考题)
- 高精度三维空间测量、定位与追踪(上)
- ToolBar使用详解
- 隐式转换与显示转换的区别概念理解
- Response
- mui与vue结合 功能网址
- HTML初识
- unity用方向键来控制角色上楼梯
- 关于C++ const 的全面总结
- MyBatis Generator 详解
- 自定义控件之 SubmitBotton (提交按钮)