- 浏览: 5820118 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (890)
- WindowsPhone (0)
- android (88)
- android快速迭代 (17)
- android基础 (34)
- android进阶 (172)
- android高级 (0)
- android拾遗 (85)
- android动画&效果 (68)
- Material Design (13)
- LUA (5)
- j2me (32)
- jQuery (39)
- spring (26)
- hibernate (20)
- struts (26)
- tomcat (9)
- javascript+css+html (62)
- jsp+servlet+javabean (14)
- java (37)
- velocity+FCKeditor (13)
- linux+批处理 (9)
- mysql (19)
- MyEclipse (9)
- ajax (7)
- wap (8)
- j2ee+apache (24)
- 其他 (13)
- phonegap (35)
最新评论
-
Memories_NC:
本地lua脚本终于执行成功了,虽然不是通过redis
java中调用lua脚本语言1 -
ZHOU452840622:
大神://处理返回的接收状态 这个好像没有监听到 遇 ...
android 发送短信的两种方式 -
PXY:
拦截部分地址,怎么写的for(int i=0;i<lis ...
判断是否登录的拦截器SessionFilter -
maotou1988:
Android控件之带清空按钮(功能)的AutoComplet ...
自定义AutoCompleteTextView -
yangmaolinpl:
希望有表例子更好。。。,不过也看明白了。
浅谈onInterceptTouchEvent、onTouchEvent与onTouch
系统自带的Toast有时候不能满足我们的需求,现在提供一个可以快速替代Toast的方案。
项目地址:
源码:
用法:
详情看附件。
项目地址:
源码:
/* * Copyright 2012 Evgeny Shishkin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.devspark.appmsg.sample; import java.util.LinkedList; import java.util.Queue; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; /** * @author Evgeny Shishkin */ class MsgManager extends Handler { private static final int MESSAGE_DISPLAY = 0xc2007; private static final int MESSAGE_ADD_VIEW = 0xc20074dd; private static final int MESSAGE_REMOVE = 0xc2007de1; private static MsgManager mInstance; private Queue<AppMsg> msgQueue; private Animation inAnimation, outAnimation; private MsgManager() { msgQueue = new LinkedList<AppMsg>(); } /** * @return The currently used instance of the {@link MsgManager}. */ static synchronized MsgManager getInstance() { if (mInstance == null) { mInstance = new MsgManager(); } return mInstance; } /** * Inserts a {@link AppMsg} to be displayed. * * @param appMsg */ void add(AppMsg appMsg) { msgQueue.add(appMsg); if (inAnimation == null) { inAnimation = AnimationUtils.loadAnimation(appMsg.getActivity(), android.R.anim.fade_in); } if (outAnimation == null) { outAnimation = AnimationUtils.loadAnimation(appMsg.getActivity(), android.R.anim.fade_out); } displayMsg(); } /** * Removes all {@link AppMsg} from the queue. */ void clearMsg(AppMsg appMsg) { if(msgQueue.contains(appMsg)){ // Avoid the message from being removed twice. removeMessages(MESSAGE_REMOVE); msgQueue.remove(appMsg); removeMsg(appMsg); } } /** * Removes all {@link AppMsg} from the queue. */ void clearAllMsg() { if (msgQueue != null) { msgQueue.clear(); } removeMessages(MESSAGE_DISPLAY); removeMessages(MESSAGE_ADD_VIEW); removeMessages(MESSAGE_REMOVE); } /** * Displays the next {@link AppMsg} within the queue. */ private void displayMsg() { if (msgQueue.isEmpty()) { return; } // First peek whether the AppMsg is being displayed. final AppMsg appMsg = msgQueue.peek(); // If the activity is null we throw away the AppMsg. if (appMsg.getActivity() == null) { msgQueue.poll(); } final Message msg; if (!appMsg.isShowing()) { // Display the AppMsg msg = obtainMessage(MESSAGE_ADD_VIEW); msg.obj = appMsg; sendMessage(msg); } else { msg = obtainMessage(MESSAGE_DISPLAY); sendMessageDelayed(msg, appMsg.getDuration() + inAnimation.getDuration() + outAnimation.getDuration()); } } /** * Removes the {@link AppMsg}'s view after it's display duration. * * @param appMsg The {@link AppMsg} added to a {@link ViewGroup} and should be removed.s */ private void removeMsg(final AppMsg appMsg) { ViewGroup parent = ((ViewGroup) appMsg.getView().getParent()); if (parent != null) { outAnimation.setAnimationListener(new OutAnimationListener(appMsg)); appMsg.getView().startAnimation(outAnimation); // Remove the AppMsg from the queue. msgQueue.poll(); if (appMsg.isFloating()) { // Remove the AppMsg from the view's parent. parent.removeView(appMsg.getView()); } else { appMsg.getView().setVisibility(View.INVISIBLE); } Message msg = obtainMessage(MESSAGE_DISPLAY); sendMessage(msg); } } private void addMsgToView(AppMsg appMsg) { View view = appMsg.getView(); if (view.getParent() == null) { appMsg.getActivity().addContentView( view, appMsg.getLayoutParams()); } view.startAnimation(inAnimation); if (view.getVisibility() != View.VISIBLE) { view.setVisibility(View.VISIBLE); } final Message msg = obtainMessage(MESSAGE_REMOVE); msg.obj = appMsg; sendMessageDelayed(msg, appMsg.getDuration()); } @Override public void handleMessage(Message msg) { final AppMsg appMsg; switch (msg.what) { case MESSAGE_DISPLAY: displayMsg(); break; case MESSAGE_ADD_VIEW: appMsg = (AppMsg) msg.obj; addMsgToView(appMsg); break; case MESSAGE_REMOVE: appMsg = (AppMsg) msg.obj; removeMsg(appMsg); break; default: super.handleMessage(msg); break; } } private static class OutAnimationListener implements Animation.AnimationListener { private AppMsg appMsg; private OutAnimationListener(AppMsg appMsg) { this.appMsg = appMsg; } @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { if (!appMsg.isFloating()) { appMsg.getView().setVisibility(View.GONE); } } @Override public void onAnimationRepeat(Animation animation) { } } }
/* * Copyright 2012 Evgeny Shishkin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.devspark.appmsg.sample; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; import android.widget.TextView; /** * In-layout notifications. Based on {@link android.widget.Toast} notifications * and article by Cyril Mottier (http://android.cyrilmottier.com/?p=773). * * @author e.shishkin */ public class AppMsg { /** * Show the view or text notification for a short period of time. This time * could be user-definable. This is the default. * * @see #setDuration */ public static final int LENGTH_SHORT = 3000; /** * Show the view or text notification for a long period of time. This time * could be user-definable. * * @see #setDuration */ public static final int LENGTH_LONG = 5000; /** * Show the text notification for a long period of time with a negative style. */ public static final Style STYLE_ALERT = new Style(LENGTH_LONG, R.color.alert); /** * Show the text notification for a short period of time with a positive style. */ public static final Style STYLE_CONFIRM = new Style(LENGTH_SHORT, R.color.confirm); /** * Show the text notification for a short period of time with a neutral style. */ public static final Style STYLE_INFO = new Style(LENGTH_SHORT, R.color.info); private final Activity mContext; private int mDuration = LENGTH_SHORT; private View mView; private LayoutParams mLayoutParams; private boolean mFloating; /** * Construct an empty AppMsg object. You must call {@link #setView} before * you can call {@link #show}. * * @param context The context to use. Usually your * {@link android.app.Activity} object. */ public AppMsg(Activity context) { mContext = context; } /** * Make a {@link AppMsg} that just contains a text view. * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param text The text to show. Can be formatted text. * @param style The style with a background and a duration. */ public static AppMsg makeText(Activity context, CharSequence text, Style style) { return makeText(context, text, style, R.layout.app_msg); } /** * @author mengguoqiang 扩展支持设置字体大小 * Make a {@link AppMsg} that just contains a text view. * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param text The text to show. Can be formatted text. * @param style The style with a background and a duration. */ public static AppMsg makeText(Activity context, CharSequence text, Style style, float textSize) { return makeText(context, text, style, R.layout.app_msg, textSize); } /** * Make a {@link AppMsg} with a custom layout. The layout must have a {@link TextView} com id {@link android.R.id.message} * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param text The text to show. Can be formatted text. * @param style The style with a background and a duration. */ public static AppMsg makeText(Activity context, CharSequence text, Style style, int layoutId) { LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(layoutId, null); return makeText(context, text, style, v, true); } /** * @author mengguoqiang 扩展支持字体大小 * Make a {@link AppMsg} with a custom layout. The layout must have a {@link TextView} com id {@link android.R.id.message} * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param text The text to show. Can be formatted text. * @param style The style with a background and a duration. */ public static AppMsg makeText(Activity context, CharSequence text, Style style, int layoutId, float textSize) { LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(layoutId, null); return makeText(context, text, style, v, true, textSize); } /** * Make a non-floating {@link AppMsg} with a custom view presented inside the layout. * It can be used to create non-floating notifications if floating is false. * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param customView * View to be used. * @param text The text to show. Can be formatted text. * @param style The style with a background and a duration. */ public static AppMsg makeText(Activity context, CharSequence text, Style style, View customView) { return makeText(context, text, style, customView, false); } /** * Make a {@link AppMsg} with a custom view. It can be used to create non-floating notifications if floating is false. * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param view * View to be used. * @param text The text to show. Can be formatted text. * @param style The style with a background and a duration. * @param floating true if it'll float. */ private static AppMsg makeText(Activity context, CharSequence text, Style style, View view, boolean floating) { return makeText(context, text, style, view, floating, 0); } /** * * @author mengguoqiang 扩展支持设置字体大小 * Make a {@link AppMsg} with a custom view. It can be used to create non-floating notifications if floating is false. * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param view * View to be used. * @param text The text to show. Can be formatted text. * @param style The style with a background and a duration. * @param floating true if it'll float. */ private static AppMsg makeText(Activity context, CharSequence text, Style style, View view, boolean floating, float textSize) { AppMsg result = new AppMsg(context); view.setBackgroundResource(style.background); TextView tv = (TextView) view.findViewById(android.R.id.message); if(textSize > 0) tv.setTextSize(textSize); tv.setText(text); result.mView = view; result.mDuration = style.duration; result.mFloating = floating; return result; } /** * Make a {@link AppMsg} with a custom view. It can be used to create non-floating notifications if floating is false. * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param resId The resource id of the string resource to use. Can be * formatted text. * @param style The style with a background and a duration. * @param floating true if it'll float. */ public static AppMsg makeText(Activity context, int resId, Style style, View customView, boolean floating) { return makeText(context, context.getResources().getText(resId), style, customView, floating); } /** * Make a {@link AppMsg} that just contains a text view with the text from a * resource. * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param resId The resource id of the string resource to use. Can be * formatted text. * @param style The style with a background and a duration. * @throws Resources.NotFoundException if the resource can't be found. */ public static AppMsg makeText(Activity context, int resId, Style style) throws Resources.NotFoundException { return makeText(context, context.getResources().getText(resId), style); } /** * Make a {@link AppMsg} with a custom layout using the text from a * resource. The layout must have a {@link TextView} com id {@link android.R.id.message} * * @param context The context to use. Usually your * {@link android.app.Activity} object. * @param resId The resource id of the string resource to use. Can be * formatted text. * @param style The style with a background and a duration. * @throws Resources.NotFoundException if the resource can't be found. */ public static AppMsg makeText(Activity context, int resId, Style style, int layoutId) throws Resources.NotFoundException { return makeText(context, context.getResources().getText(resId), style, layoutId); } /** * Show the view for the specified duration. */ public void show() { MsgManager manager = MsgManager.getInstance(); manager.add(this); } /** * @return <code>true</code> if the {@link AppMsg} is being displayed, else <code>false</code>. */ public boolean isShowing() { if (mFloating) { return mView != null && mView.getParent() != null; } else { return mView.getVisibility() == View.VISIBLE; } } /** * Close the view if it's showing, or don't show it if it isn't showing yet. * You do not normally have to call this. Normally view will disappear on its own * after the appropriate duration. */ public void cancel() { MsgManager.getInstance().clearMsg(this); } /** * Cancels all queued {@link AppMsg}s. If there is a {@link AppMsg} * displayed currently, it will be the last one displayed. */ public static void cancelAll() { MsgManager.getInstance().clearAllMsg(); } /** * Return the activity. */ public Activity getActivity() { return mContext; } /** * Set the view to show. * * @see #getView */ public void setView(View view) { mView = view; } /** * Return the view. * * @see #setView */ public View getView() { return mView; } /** * Set how long to show the view for. * * @see #LENGTH_SHORT * @see #LENGTH_LONG */ public void setDuration(int duration) { mDuration = duration; } /** * Return the duration. * * @see #setDuration */ public int getDuration() { return mDuration; } /** * Update the text in a AppMsg that was previously created using one of the makeText() methods. * * @param resId The new text for the AppMsg. */ public void setText(int resId) { setText(mContext.getText(resId)); } /** * Update the text in a AppMsg that was previously created using one of the makeText() methods. * * @param s The new text for the AppMsg. */ public void setText(CharSequence s) { if (mView == null) { throw new RuntimeException("This AppMsg was not created with AppMsg.makeText()"); } TextView tv = (TextView) mView.findViewById(android.R.id.message); if (tv == null) { throw new RuntimeException("This AppMsg was not created with AppMsg.makeText()"); } tv.setText(s); } /** * Gets the crouton's layout parameters, constructing a default if necessary. * * @return the layout parameters */ public LayoutParams getLayoutParams() { if (mLayoutParams == null) { mLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } return mLayoutParams; } /** * Sets the layout parameters which will be used to display the crouton. * * @param layoutParams The layout parameters to use. * @return <code>this</code>, for chaining. */ public AppMsg setLayoutParams(LayoutParams layoutParams) { mLayoutParams = layoutParams; return this; } /** * Constructs and sets the layout parameters to have some gravity. * * @param gravity the gravity of the Crouton * @return <code>this</code>, for chaining. * @see android.view.Gravity */ public AppMsg setLayoutGravity(int gravity) { mLayoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, gravity); return this; } /** * Return the value of floating. * * @see #setFloating(boolean) */ public boolean isFloating() { return mFloating; } /** * Sets the value of floating. * * @param mFloating */ public void setFloating(boolean mFloating) { this.mFloating = mFloating; } /** * The style for a {@link AppMsg}. * * @author e.shishkin */ public static class Style { private final int duration; private final int background; /** * Construct an {@link AppMsg.Style} object. * * @param duration How long to display the message. Either * {@link #LENGTH_SHORT} or {@link #LENGTH_LONG} * @param resId resource for AppMsg background */ public Style(int duration, int resId) { this.duration = duration; this.background = resId; } /** * Return the duration in milliseconds. */ public int getDuration() { return duration; } /** * Return the resource id of background. */ public int getBackground() { return background; } @Override public boolean equals(Object o) { if (!(o instanceof AppMsg.Style)) { return false; } Style style = (Style) o; return style.duration == duration && style.background == background; } } }
用法:
/* * Copyright 2012 Evgeny Shishkin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.devspark.appmsg.sample; import android.app.Activity; import android.os.Bundle; import android.view.Gravity; import android.view.View; import android.widget.Button; import android.widget.CheckBox; /** * Sample of AppMsg library. * * @author Evgeny Shishkin * SherlockActivity */ public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * Button onClick listener. * * @param v */ public void showAppMsg(View v) { final CharSequence msg = ((Button) v).getText(); final AppMsg.Style style; switch (v.getId()) { case R.id.alert: style = AppMsg.STYLE_ALERT; break; case R.id.confirm: style = AppMsg.STYLE_CONFIRM; break; case R.id.info: style = AppMsg.STYLE_INFO; break; case R.id.custom: style = new AppMsg.Style(AppMsg.LENGTH_SHORT, R.color.custom); break; default: return; } // create {@link AppMsg} with specify type AppMsg appMsg = AppMsg.makeText(this, msg, style); if (((CheckBox) (findViewById(R.id.bottom))).isChecked()) { appMsg.setLayoutGravity(Gravity.BOTTOM); } appMsg.show(); } }
详情看附件。
发表评论
-
NestedScrollView滚动到顶部固定子View悬停挂靠粘在顶端
2018-10-31 20:45 6993网上有一个StickyScrollView,称之为粘性Scro ... -
自定义Behavior实现AppBarLayout越界弹性效果
2017-03-31 09:33 10369一、继承AppBarLayout.Beha ... -
Android - 一种相似图片搜索算法的实现
2017-03-31 09:33 2622算法 缩小尺寸。 将图片缩小到8x8的尺寸,总共64个 ... -
使用SpringAnimation实现带下拉弹簧动画的 ScrollView
2017-03-30 11:30 2848在刚推出的 Support Library 25.3.0 里面 ... -
Android为应用添加角标(Badge)
2017-03-30 11:21 61771.需求简介 角标是什么意思呢? 看下图即可明了: 可 ... -
Android端与笔记本利用局域网进行FTP通信
2017-03-23 10:17 978先看图 打开前: 打开后: Activity类 ... -
PorterDuffColorFilter 在项目中的基本使用
2017-03-03 10:58 1354有时候标题栏会浮在内容之上,而内容会有颜色的变化,这时候就要求 ... -
ColorAnimationView 实现了滑动Viewpager 时背景色动态变化的过渡效果
2017-02-24 09:41 2220用法在注释中: import android.anima ... -
迷你轻量级全方向完美滑动处理侧滑控件SlideLayout
2017-01-16 16:53 2594纯手工超级迷你轻量级全方向完美滑动处理侧滑控件(比官方 sup ... -
Effect
2017-01-05 09:57 0https://github.com/JetradarMobi ... -
动态主题库Colorful,容易地改变App的配色方案
2016-12-27 14:49 2565Colorful是一个动态主题库,允许您很容易地改变App的配 ... -
对视图的对角线切割DiagonalView
2016-12-27 14:23 1118提供对视图的对角线切割,具有很好的用户定制 基本用法 ... -
仿淘宝京东拖拽商品详情页上下滚动黏滞效果
2016-12-26 16:53 3494比较常用的效果,有现成的,如此甚好!:) import ... -
让任意view具有滑动效果的SlideUp
2016-12-26 09:26 1707基本的类,只有一个: import android.a ... -
AdvancedWebView
2016-12-21 09:44 16https://github.com/delight-im/A ... -
可设置圆角背景边框的按钮, 通过调节色彩明度自动计算按下(pressed)状态颜色
2016-11-02 22:13 1920可设置圆角背景边框的的按钮, 通过调节色彩明度自动计算按下(p ... -
网络请求库相关
2016-10-09 09:35 62https://github.com/amitshekhari ... -
ASimpleCache一个简单的缓存框架
2015-10-26 22:53 2178ASimpleCache 是一个为android制定的 轻量级 ... -
使用ViewDragHelper实现的DragLayout开门效果
2015-10-23 10:55 3415先看一下图,有个直观的了解,向下拖动handle就“开门了”: ... -
保证图片长宽比的同时拉伸图片ImageView
2015-10-16 15:40 3733按比例放大图片,不拉伸失真 import android. ...
相关推荐
An Android Toast replacement, similar to the one seen in the GMail app. Multiple messages can be posted in succession, and each message will be shown for 5 seconds. Usage There's two ways to use the ...
自定义Toast的关键在于创建一个布局文件来替代系统的默认布局。首先,我们需要在项目的res/layout目录下创建一个新的XML文件,例如命名为`custom_toast.xml`。在这个布局文件中,可以自由设计Toast的外观,包括文字...
在Android开发中,`Toast`是一个非常常用的组件,它用于显示短暂的信息提示,不占用屏幕空间,不影响用户操作。这个“Toast范例源代码”压缩包可能包含了一个或多个示例,展示了如何在Android应用中使用`Toast`进行...
在`Android`中,我们可以通过`Toast.makeText()`方法来创建一个`Toast`。例如: ```java Toast.makeText(context, "这是基础的Toast信息", Toast.LENGTH_SHORT).show(); ``` 其中`context`是应用程序上下文,`...
创建一个Toast非常简单,只需要调用`Toast.makeText(Context context, String message, int duration)`方法,其中`context`是应用程序上下文,`message`是要显示的文本,`duration`表示显示的时间长度,可以是`Toast...
例如,我们可以设计一个包含自定义颜色的TextView,来替代原生的TextView。在布局文件中,我们可以设置TextView的字体颜色、背景色以及文字大小等属性,以达到预期的效果。 ```xml android:layout_width="wrap_...
它可以显示一个圆形的进度条,文本信息,甚至还可以添加自定义子视图。MBProgressHUD通过简单的API提供了一种快速创建和管理提示框的方法。 二、UIAlertController使用 要创建一个UIAlertController,首先需要导入`...
EventBus的设计理念源于Java中的发布/订阅模式,它提供了一个轻量级的替代方案,避免了使用BroadcastReceiver或者Intent进行组件间通信的复杂性。相比于传统的通信方式,EventBus的主要优点在于其灵活性和高效性。 ...
在Lab15_WebView-main项目中,可能包含了一个简单的示例应用,演示了上述部分或全部功能。开发者可以通过这个例子学习如何集成和使用`WebView`,并根据实际需求扩展其功能。例如,可以实现更复杂的JavaScript交互,...
15.1 闹钟管理器基本知识:设置一个简单的闹钟 15.1.1 获取闹钟管理器 15.1.2 设置闹钟时间 15.1.3 设置闹钟接收程序 15.1.4 创建适合闹钟的PendingIntent 15.1.5 设置闹钟 15.1.6 测试项目 ...
15.1 闹钟管理器基本知识:设置一个简单的闹钟 15.1.1 获取闹钟管理器 15.1.2 设置闹钟时间 15.1.3 设置闹钟接收程序 15.1.4 创建适合闹钟的PendingIntent 15.1.5 设置闹钟 15.1.6 测试项目 ...