Android 浮动搜索框 searchable 使用指南

文章来源:http://developer.android.com/guide/topics/search/search-dialog.html
一、前言:
Android为程序的搜索功能提供了统一的搜索接口,search dialog和search widget。
search dialog只能为于activity窗口的上方,search widget可以位于任何位置。
search dialog和search widget都会向我们的searchable activity发送消息(主要是搜索关键字)。
通过这种方式,可以为任何activity加入search dialog和search widget,系统可以启动合适的activity来处理搜索并显示结果。
search dialog和search widget的 其他属性 如下:
A声音搜索
B根据最近的搜索结果,给出搜索建议
C根据我们程序的实际搜索结果,给出搜索建议
注1search widget在 Android 3.0或更高版本才可用
注2searchable activity才是真正执行搜索的
二、基本知识
在开始实现搜索功能之前,请决定使用search dialog,还是search widget.
他们的搜索功能特性都有一样,但是他们还有微小区别。
A, search dialog是一个被系统控制的UI组件。但他被用户激活的时候,它总是出现在activity的上方,如图一所示
B, Android系统负责处理search dialog上所有的事件,当用户提交了查询,系统会把这个查询请求传输到我们的searchable activity,
让searchable activity在处理真正的查询。当用户在输入的时候,search dialog还能提供搜索建议。
C, search widget是SearchView的一个实例,你可以把它放在你的布局的任何地方
D, 默认的,search widget和一个标准的EditText widget一样,不能做任何事情。
但是你可以配置它,让android系统处理所有的按键事件,把查询请求传输给合适的activity,可以配置它让它像search dialog一样提供search suggestions。
E, search widget在 Android 3.0或更高版本才可用. search dialog没有此项限制
提示如果你想自己在search widget处理所有的用户输入,请使用各种回调函数和监听接口,具体参照 SearchView 
图一
Searchable(上) - hubingforever - 民主与科学
 
当用户在search dialog或search widget中执行一个搜索的时候,系统会创建一个Intent,并把查询关键字保存在里面,
然后启动我们在AndroidManifest.xml中声明好的searchable activity,并把Intent传送给它。
实现一个可以搜索的程序,主要需要以下几个部份
(1)search dialog or widget的配置文件
配置一个XML文件用于配置search dialog 或widget的设置。对于search dialog,该配置文件的名字一般约定为 searchable.xml
(2)searchable activity
searchable activity用于接收搜索关键字,并进行数据搜索和显示搜索结果。
(3)搜索条。search dialog 或search widget
     * The search dialog
       默认的,search dialog是隐藏。当我们按下了SEARCH键或在程序中调用 onSearchRequested() ,它将出现在屏幕的上方.
     * a SearchView widget
使用search widget的时候,你可以把该搜索条放在我们activity的任何地方。
Instead of putting it in your activity layout, however, it's usually more convenient for users as an action view in the Action Bar.
三、创建配置文件searchable.xml
配置文件说明了search dialog 或widget的一些属性。包括UI,以及suggestions 和voice search behave的一些属性。
该文件一般约定为 searchable.xml 并位于 res/xml/ 目录下。
searchable.xml 必须以 <searchable>   element 作 为根节点 ,且 至少定义一个属性
比如, 示例1:
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_label"
android:hint="@string/search_hint" >
</searchable>
android:label 是唯一 必须定义 的属性。它指向一个字符串,它应该是 应用程序的名字
实际上该 label 也只有在search suggestions for Quick Search Box可用时才可见。
这时该label在系统设置的Searchable项的列表中可见
虽然a ndroid:hint 属性不是必须,但是还是推介总是定义它。它是search box用户输入前 输入框中的提示语
<searchable> 还有其他的一些属性。如果不需要search suggestions 和voice search的话,大多数的属性是不需要的。
四、创建Searchable Activity
searchable activity根据搜索关键字进行搜索,并显示搜索结果。
当我们在search dialog or widget执行搜索的时候,系统就启动你的searchable activity ,并把搜索关键字用一个aciton为ACTION_SEARCH的Intent传给你的searchable activity. 你的searchable activity在Intent中通过extra的QUERY来提取搜索关键字,执行搜索并显示搜索结果.
我们需要在AndroidManifest.xml文件中声明Searchable Activity,以便在search dialog or widget执行搜索的时候,系统启动该searchable activity并把搜索关键字传给它。
4.1、声明searchable activity
如何在 AndroidManifest.xml 文件中 声明Searchable Activity
    1在activity的<intent-filter> 中添加一个可以接受action为ACTION_SEARCH的intent。
    2在<meta-data>中指明search dialog or widget的配置文件(即searchable.xml)
   比如, 示例2
    <application ... >
    <activity android:name=".SearchableActivity" >
        <intent-filter>
            <action android:name="android.intent.action.SEARCH" />
        </intent-filter>
        <meta-data android:name="android.app.searchable"
                   android:resource="@xml/searchable"/>
    </activity>
    ...
</application>
<meta-data> 必须包括 android:name 这个属性,而且其值必须为 "android.app.searchable"
还必须包括 android:resource 这个属性,它指定了我们的s earch dialog 配置文件 。(示例2中指定是 the res/xml/searchable.xml ).
注意 <intent-filter> 没有必要加入<category android:name="android.intent.category.DEFAULT" /> ,
因为系统是通 component name 把ACTION_SEARCH intent explicitly显示的传递给searchable activity的
4.2、执行搜索
当你在manifest文件中,声明好了searchable activity,在你的searchable activity,就可以参照下面的3步执行搜索了。
(1)提取搜索关键字
(2)在你的数据中进行搜索
(3)显示搜索结果
一般来说,你的搜索结果需要要在ListView进行显示,所有你的searchable activity需要继承ListActivity。
它包括了一个拥有单一ListView的layout,它为我们使用ListView提供了方便。
4.2.1、提取搜索关键字
当用户在search dialog or widget界面开始搜索的时候,系统将查找一个合适的searchable activity,并给它传送一个ACTION_SEARCH的intent,该intent把搜索关键字保存在extra的QUERY中。当activity的时候,我们必须检查保存在extra的QUERY中的搜索关键字,并提取它。
比如, 示例3:
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.search);

    // Get the intent, verify the action and get the query
    Intent intent = getIntent();
    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
      String query = intent.getStringExtra(SearchManager.QUERY);
      doMySearch(query);
    }
}
在Action为ACTION_SEARCH的intent中,extra的QUERY总是被包含。在示例3提取处理了搜索关键字,然后让doMySearch()函数在执行真正的搜索的。
4.2.2、数据搜索
数据的存储和搜索都是和你的程序相关的。你可以以各种方式进行数据的存储和搜索,它才是你的程序更需要关注的。 
但是还是有 几点需要注意的:
(1)当你的数据存储在手机的数据库中时,执行full-text search (using FTS3, rather than a LIKE query)
能提供在文本数据中得到更健壮且速度更快的search。 在 sqlite.org 中可以得到FTS3的更多信息, 在 SQLiteDatabase 可以看到Android上的SQLite更多信息 . 可以在 Searchable Dictionary看到一个用FTS3实现搜索的示例。
(2)如果的数据来自互联网,那么你的搜索将受制于用户的数据连接情况。这时我们就需要显示一个进度条,直到从网路返回搜索结果。
. 在 android.net 可一看到更多的network APIs ,在 Creating a Progress Dialog 可以到看到如何显示一个进度条。
如果想让的数据和搜索变得透明,那么我建议你用一个Adapter返回搜索结果。这种方式,你更容易在ListView中显示搜索结果。如果你的数据在自于数据库,那么通过CursorAdapter向ListView提供搜索结果,如果你的数据来自于其他方式,可以扩展一个BaseAdapter来使用。
4.2.3、结果显示
正如上面讨论的一样,显示搜索结果建议使用的UI是ListView,所以你的searchable activity最好继承于ListActivity,然后是使用setListAdapter()来设置和搜索结果绑定了的Adapter。这样你的搜索结果就可以显示在ListView中了。
你可以参考 Searchable Dictionary 示例,它展示了怎么进行数据库查询,怎么使用Adapter向ListView提供搜索结果。
五、使用Search Dialog
search dialog提供了一个上浮在屏幕上方的搜索条,应用程序的图标显示在搜索条的左边。当用户在输入的时候,它可以提供建议的搜索关键字。
当用户自行搜索的时候,系统会把它的搜索关键字searchable activity来执行真正的搜索。
但是如果你的设备使用的是Android 3.0,(或更高版本),你可以考虑使用search widget。
search dialog默认是隐藏的,直到用户激活它。如果用法的手机上有SEARCH按钮,那么按下该键,默认是激活search dialog。
为了使用search dialog,你必须想系统说明哪个searchable activity将受到该search dialog的搜索请求,以便执行搜索。比如,在示例2中,是名叫SearchableActivity的searchable activity,当然同时使用search dialog的也是它。如果你想使用其他的actvitity来显示search dialog的, 比如名字为OtherActivity, 让它来显示search dialog 并把搜索请求传递给SearchableActivity, 你必须在manifest中声明一个searchable activity
(比如SearchableActivity)来接收OtherActivity中的search dialog的搜索请求.
为一个activity的search dialog声明searchable activity,你需要在AndroidManifest.xml中代表该activity的<activity> 元素中加入<meta-data>
这个<meta-data>必须包含“android:value”属性,该属性指明了searchable activity的类名,
还必须包括属性“android:name”,且其值必须为 "android.app.default_searchable".
下面的例子就声明了两个 searchable activity( SearchableActivity和 OtherActivity),
 OtherActivity也是在它的 search dialog中 使用SearchableActivity执行searches操作。
示例4
<application ... >
    <!-- this is the searchable activity; it performs searches -->
   
<activity android:name=".SearchableActivity" >
       
<intent-filter>
           
<action android:name="android.intent.action.SEARCH" />
       
</intent-filter>
       
<meta-data android:name="android.app.searchable"
                   
android:resource="@xml/searchable"/>
   
</activity>

   
<!-- this activity enables the search dialog to initiate searches
         in the SearchableActivity -->

   
<activity android:name=".OtherActivity" ... >
       
<!-- enable the search dialog to send searches to SearchableActivity -->
       
<meta-data android:name="android.app.default_searchable"
                   
android:value=".SearchableActivity" />

   
</activity>
    ...
</application>
因为在OtherActivity 中已经包含了一个 <meta-data>元素,它声明了使用哪个searchable activity来执行搜索,所以它的search dialog也变得可用。按下手机的SEARCH按钮(如果有的话)或调用onSearchRequested()都将激活search dialog.
一旦用户在search dialog中执行search操作, 系统将启动SearchableActivity 并向其传送ACTION_SEARCH intent.
提示searchable activity 自己默认就提供了the search dialog ,且它的searchable activity就是自己本身,所以不需要再声明.

如果你想为你的应用程序的每个activity 都提供该search dialog, 那么请 <meta-data> 元素加入 <application> 作为其儿子, 而不是加入到每个<activity>. 通过这种方式, 每个activity 都继承了该值, 提供search dialog, 并把searches传送到同一个searchable activity. (如果你有多个searchable activities, 你可以在单个activitiy中加入不同的<meta-data>来声明searchable activity,这样就重写了默认的searchable activity )

六、如何启动search dialog

正如上面所提到的,如果当前activity声明了使用searchable activity,那么按下手机的SEARCH按钮(如果有的话)或调用onSearchRequested()都将激活search dialog.

然而,SEARCH按钮并不是所有的设备上都有,所以你需要在你的UI中提供一个搜索按钮,

以便通过调用onSearchRequested()激活search dialog 。

例如,你可以在Options Menu一个菜单项或在你的activity的布局的按钮中调用onSearchRequested()来启动search dialog.

search_icons.zip 文件中有针对medium and high density屏幕的搜索图标,你可以在你的搜索菜单项或按钮中使用它(low-density screens scale-down the hdpi image by one half).

你也可以使用"type-to-search"功能, 这样当用户在键盘进行输入的时候,将激活search dialog,并且是直接输入到search dialog.

你可以在activity的onCreate() 中调用setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL)来开启该功能

七、search dialog对Activity生命周期的影响

search dialog 只是一个浮动在屏幕上的Dialog。它并不引起Activity栈的任何改变。 所以当search dialog 被启动的时候, 并不会有生命周期函数被调动(比如onPause())。你的activity只是失去输入焦点,因为输入焦点被转移到了search dialog.
如果你想在启动search dialog的时候被notified,那么请重写ActivityonSearchRequested()方法. 
当系统调用该方法的时候,说明你的activity已经失去输入焦点,输入焦点已经转移到了search dialog, 所以你就可以针对这个事件在这里做些和你的work相关的事情(比如暂停游戏).在onSearchRequested的最后你再调用父类的onSearchRequested就可以了。
比如 示例4
@Override public booleanonSearchRequested() {  
pauseSomeStuff();
return super.onSearchRequested()
}
如果用户通过按BACK键取消搜索的话,search dialog 将关闭 ,你的activity将再次获得输入焦点。你可以通过setOnDismissListener()/setOnCancelListener()注册监听器OnDismissListener/OnCancelListener来监听search dialog的关闭. 当search dialog 关闭的时候,OnDismissListener就会被调用。OnCancelListener只是在用户显式的退出search dialog时, 才被调用,当用户执行搜索的时候并不会被调用(这种情况用户只是很自然的消失,并不取消).
如果当前 activity并不是我们所指定的 searchable activity, 那么当用户执行搜索的时候,普通的activity生命周期事件将被触发
(它将调用onPause(),被暂停). 然而,如果当前就是current activity指定的searchable activity的话,下面的两件事情将发生:
A,默认的话, searchable activity 将调用onCreate() 来响应 该ACTION_SEARCH intent ,然后这个activity的一个新实例将被放到activity stack。这时你的searchable activity就有两个实例在activity stack 中(如果按下BACK键,将回到前一个searchable activity实例, 
而不是离开searchable activity).
B ,如果你把searchable activity的 android:launchMode 属性设置为了 "singleTop", 那么searchable activity 将调用onNewIntent(Intent)来响应ACTION_SEARCH , 同时ACTION_SEARCH intent也是在这里被传入 .下面的示例5, 就是一个当searchable activity的launch mode 是 "singleTop"时,该如何处理的一个很好例子。
示例5:
@Override
public void
onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.search);
    handleIntent(getIntent());
}

@Override
protected void
onNewIntent(Intent intent) {
    setIntent(intent);
    handleIntent(intent);
}

private void
handleIntent(Intent intent) {
    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
      String query = intent.getStringExtra(SearchManager.QUERY);
      doMySearch(query);
    }
}

这里所有对search intent的处理都是放在handleIntent() 函数中的, 这样 在onCreate()onNewIntent() 中直接调用它就行了.

当系统调用onNewIntent(Intent)的时候,表示activity并不是新建的, 所以getIntent()返回的还是onCreate()中接受到的intent. 因此你必须在onNewIntent(Intent)调用setIntent(Intent) (这样保存的intent才被更新,之后你可以同过getIntent()来取得它).

使用"singleTop" 是常用的处理方法, 因为一旦用户执行了一次搜索,它往往还想执行一次搜索,而且创建大量的searchable activity不太好。因此建议把所有的searchable activity 都在manifest中把它设置为"singleTop" 模式 。

比如,示例6

<activity android:name=".SearchableActivity"
         
android:launchMode="singleTop" >
   
<intent-filter>
       
<action android:name="android.intent.action.SEARCH" />
   
</intent-filter>
   
<meta-data android:name="android.app.searchable"
                     
android:resource="@xml/searchable"/>

  </activity>

八、searchable activity传送数据

有时,你可能想在searchable activity收到的搜索关键字的基础上再添加些内容。然而有时添加的内容依取决于启动search dialog的Activity。Anroid可以让你在系统向searchable activity发送的intent时候,向该intent添加你的数据。ACTION_SEARCH intent通过携带一个名叫APP_DATABundle来携带你的数据。为了传送你的数据,请在要执行搜索请求的Acitivity中重写onSearchRequested() ,创建一个Bundle,并把你要携带的数据放在其中,然后以Bundle为参数之一来调用startSearch()激活search dialog.

比如,示例7:

@Override
public boolean onSearchRequested() {
     
Bundle appData = new Bundle();
     appData
.putBoolean(SearchableActivity.JARGON, true);

     
startSearch(null, false, appData, false);
     
return true;
 
}
返回"true"表示你已经成功处理了该回调事件,调用startSearch()是为了激活search dialog. 一旦用户提交了搜索请求, 它将和你添加的数据一样被传送到searchable activity。 你可以通过APP_DATA Bundle来提取它。
比如:示例8:
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null) {
   
boolean jargon = appData.getBoolean(SearchableActivity.JARGON);
}
注意:不要在
onSearchRequested()之外调用
startSearch()方法。 当需要在你的activity中激活search dialog请总是onSearchRequested(). 否则, 如果onSearchRequested()没被调用,一些个性化得操作就不能得到执行(比如上面例子的添加额外数据)

七、search dialog对Activity生命周期的影响

search dialog 只是一个浮动在屏幕上的Dialog。它并不引起Activity栈的任何改变。 所以当search dialog 被启动的时候, 并不会有生命周期函数被调动(比如onPause())。你的activity只是失去输入焦点,因为输入焦点被转移到了search dialog.
如果你想在启动search dialog的时候被notified,那么请重写ActivityonSearchRequested()方法. 
当系统调用该方法的时候,说明你的activity已经失去输入焦点,输入焦点已经转移到了search dialog, 所以你就可以针对这个事件在这里做些和你的work相关的事情(比如暂停游戏).在onSearchRequested的最后你再调用父类的onSearchRequested就可以了。
比如 示例4
@Override public booleanonSearchRequested() {  
pauseSomeStuff();
return super.onSearchRequested()
}
如果用户通过按BACK键取消搜索的话,search dialog 将关闭 ,你的activity将再次获得输入焦点。你可以通过setOnDismissListener()/setOnCancelListener()注册监听器OnDismissListener/OnCancelListener来监听search dialog的关闭. 当search dialog 关闭的时候,OnDismissListener就会被调用。OnCancelListener只是在用户显式的退出search dialog时, 才被调用,当用户执行搜索的时候并不会被调用(这种情况用户只是很自然的消失,并不取消).
如果当前 activity并不是我们所指定的 searchable activity, 那么当用户执行搜索的时候,普通的activity生命周期事件将被触发
(它将调用onPause(),被暂停). 然而,如果当前就是current activity指定的searchable activity的话,下面的两件事情将发生:
A,默认的话, searchable activity 将调用onCreate() 来响应 该ACTION_SEARCH intent ,然后这个activity的一个新实例将被放到activity stack。这时你的searchable activity就有两个实例在activity stack 中(如果按下BACK键,将回到前一个searchable activity实例, 
而不是离开searchable activity).
B ,如果你把searchable activity的 android:launchMode 属性设置为了 "singleTop", 那么searchable activity 将调用onNewIntent(Intent)来响应ACTION_SEARCH , 同时ACTION_SEARCH intent也是在这里被传入 .下面的示例5, 就是一个当searchable activity的launch mode 是 "singleTop"时,该如何处理的一个很好例子。
示例5:
@Override
public void
onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.search);
    handleIntent(getIntent());
}

@Override
protected void
onNewIntent(Intent intent) {
    setIntent(intent);
    handleIntent(intent);
}

private void
handleIntent(Intent intent) {
    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
      String query = intent.getStringExtra(SearchManager.QUERY);
      doMySearch(query);
    }
}

这里所有对search intent的处理都是放在handleIntent() 函数中的, 这样 在onCreate()onNewIntent() 中直接调用它就行了.

当系统调用onNewIntent(Intent)的时候,表示activity并不是新建的, 所以getIntent()返回的还是onCreate()中接受到的intent. 因此你必须在onNewIntent(Intent)调用setIntent(Intent) (这样保存的intent才被更新,之后你可以同过getIntent()来取得它).

使用"singleTop" 是常用的处理方法, 因为一旦用户执行了一次搜索,它往往还想执行一次搜索,而且创建大量的searchable activity不太好。因此建议把所有的searchable activity 都在manifest中把它设置为"singleTop" 模式 。

比如,示例6

<activity android:name=".SearchableActivity"
         
android:launchMode="singleTop" >
   
<intent-filter>
       
<action android:name="android.intent.action.SEARCH" />
   
</intent-filter>
   
<meta-data android:name="android.app.searchable"
                     
android:resource="@xml/searchable"/>

  </activity>

八、searchable activity传送数据

有时,你可能想在searchable activity收到的搜索关键字的基础上再添加些内容。然而有时添加的内容依取决于启动search dialog的Activity。Anroid可以让你在系统向searchable activity发送的intent时候,向该intent添加你的数据。ACTION_SEARCH intent通过携带一个名叫APP_DATABundle来携带你的数据。为了传送你的数据,请在要执行搜索请求的Acitivity中重写onSearchRequested() ,创建一个Bundle,并把你要携带的数据放在其中,然后以Bundle为参数之一来调用startSearch()激活search dialog.

比如,示例7:

@Override
public boolean onSearchRequested() {
     
Bundle appData = new Bundle();
     appData
.putBoolean(SearchableActivity.JARGON, true);

     
startSearch(null, false, appData, false);
     
return true;
 
}
返回"true"表示你已经成功处理了该回调事件,调用startSearch()是为了激活search dialog. 一旦用户提交了搜索请求, 它将和你添加的数据一样被传送到searchable activity。 你可以通过APP_DATA Bundle来提取它。
比如:示例8:
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null) {
   
boolean jargon = appData.getBoolean(SearchableActivity.JARGON);
}
注意:不要在
onSearchRequested()之外调用
startSearch()方法。 当需要在你的activity中激活search dialog请总是onSearchRequested(). 否则, 如果onSearchRequested()没被调用,一些个性化得操作就不能得到执行(比如上面例子的添加额外数据)
十三,语言搜索(Voice Search)
你可以通过在你的searchable配置中添加android:voiceSearchMode属性来实现search dialog或widget语言功能的添加。
这样就添加了一个用于启动voice prompt的语音搜索的按钮。一但用户完成了speaking, 这个transcribed search query将传送到你的searchable activity.
示例11如下:
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/search_label"
    android:hint="@string/search_hint"
     android:voiceSearchMode="showVoiceSearchButton|launchRecognizer"  >
</searchable>
第一值"showVoiceSearchButton"用于开启voice search, 第二个值“launchRecognizer,”说明了voice search button应该启动一个recognizer来把transcribed text传递给你的searchable activity.
你还可以使用其他的一些属性来定义voice search的行为,比如语言和返回的最大结果数。
关于 voice search的 更多属性请参照 Searchable Configuration
注意请仔细考虑是否要在你的程序中使用voice search.所有通过voice search按钮的搜索都是直接传送给了你的searchable activity,用户根本没有机会预览transcribed query。需要充分测试语言识别(voice recognition),以便它能识别用户在你的程序中提交的各种请求。

十四、Search Suggestions
search dialog和the search widget都可以在Android system系统的帮助下,在用户搜索的时候,提供建议搜索词。
系统将管理suggestions列表并处理用户选中suggestion事件.
一般你可以提供以下两种建议搜索词:
最近的搜索词
这些搜索词只是你以前的一些搜索关键词。更多的内容请参照 Adding Recent Query Suggestions.
个性化搜索词:
这些search suggestions都来自于你自己的数据, 它帮助用户快速选择正确的拼写或他们要搜索的项. 
图三 就是来自于dictionary程序一个个性化suggestions的界面图—用户可以选择一个suggestion来很快的得到它的定义。更多内容请参照 Adding Custom Suggestions
图三:
Searchable之六(Voice Search和Search Suggestions) - hubingforever - 民主与科学

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值