高级地点详情组件
高级版“地点详情”组件提供的功能与地点详情基本版相同。您还可以配置组件的操作按钮,并对显示的评价和媒体应用搜索和过滤选项。
内置 Jetpack Compose 支持
Places UI Kit 为 AdvancedPlaceDetailsCompact 提供 Jetpack Compose @Composable 函数。这些可组合项使用提升的状态、内置的修饰符支持和 Compose 原生槽位 DSL。
以下代码示例演示了如何使用可组合函数来实现垂直布局。
Kotlin
import android.util.Log import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp import com.google.android.libraries.places.api.model.Place import com.google.android.libraries.places.compose.AdvancedPlaceDetailsCompact import com.google.android.libraries.places.compose.PlaceReference import com.google.android.libraries.places.compose.action.CornerAction import com.google.android.libraries.places.compose.action.MainAction import com.google.android.libraries.places.compose.Orientation @Composable fun SampleAdvancedPlaceDetailsCompactCard( placeId: String, bookmarkIcon: Painter, onSavePlaceClicked: (Place) -> Unit ) { AdvancedPlaceDetailsCompact( // defines the place to be displayed reference = PlaceReference.PlaceId(placeId), // defines the orientation orientation = Orientation.VERTICAL, // defines custom styling modifier = Modifier .fillMaxWidth() .padding(16.dp), preferTruncation = true, enableLightbox = true, mainActions = { place -> listOf( MainAction.OpenDirections(), MainAction.OpenWebsite(), MainAction.Call() ) }, // action button configuration cornerActions = { place -> listOf( CornerAction.Custom( icon = bookmarkIcon, text = "Save Place", onClick = { onSavePlaceClicked(place) } ) ) }, onPlaceLoaded = { place -> Log.d("Places", "Successfully loaded place: ${place.id}") }, onPlaceLoadFailed = { exception -> Log.e("Places", "Failed to load place", exception) } ) { // --- Slot: Injected directly above the place name --- basicSectionTop { Text( text = "Featured in 'Top 10 Coffee Shops in SF'", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.secondary, modifier = Modifier.padding(top = 4.dp, bottom = 2.dp) ) } // --- Slot: Injected directly below the action buttons --- basicSectionBottom { Text( text = "Notes: Great quiet atmosphere for meetings.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } }
高级地点详情配置
使用 AdvancedPlaceDetailsFragment 或 AdvancedPlaceDetailsCompactFragment 即可使用高级功能。
配置操作按钮
使用 AdvancedPlaceDetailsFragment 或 AdvancedPlaceDetailsCompactFragment 的 PlaceActionProvider 接口定义和自定义操作按钮。
如果您使用的是 @composable 函数,请使用 mainActions 和 cornerActions lambda 来配置操作按钮。
视觉提供方说明要求
重要提示:当自定义操作按钮会将用户信息分享给 Google 以外的第三方时,您必须向最终用户明确说明这一点。请参阅视觉提供方信息要求。
右侧的示例展示了一张自定义图片,其中明确指出用户的信息将发送给“Altostrat”。
应用搜索和过滤选项
使用 AdvancedPlaceDetailsFragment 或 AdvancedPlaceDetailsCompactFragment 的 SearchMediaOptions 和 SearchReviewsOptions 类可为媒体或评价启用搜索和过滤功能。
如果您使用的是 @composable 函数,请使用 com.google.android.libraries.places.compose.search.SearchMediaOptions 来启用媒体或评价的搜索和过滤功能。
创建自定义内容槽
借助自定义内容槽,您可以将自己的内容(例如 AI 生成的摘要、行程时间戳或自定义徽章)直接添加到预定义的布局区域中。
内容槽位的提供方信息要求和内容建议
当自定义内容槽被挂载时,披露声明会自动更新,以明确指出哪些内容由 Google 提供,哪些内容由第三方提供。
正确做法
在 Google 地点详情和自定义内容之间保持清晰的视觉层次结构,以避免对信息来源产生任何歧义。
正确做法
考虑提供本地化的高价值数据洞见,以满足用户的即时目标。量身定制的内容(例如实时预订窗口或当前空房情况)有助于用户更快做出更明智的决策。
错误做法
即使您已隐藏 Google 提供的内容,也不要复制现有的 Google 地点详细信息,例如评分、评价或营业时间。这样做可能会让用户对内容的来源感到困惑,不清楚内容是来自 Google 还是第三方。
错误做法
请勿展示未注明出处的声明或徽章(例如“强烈推荐!”),以免被误认为 Google 的官方认可。自定义内容必须明确注明其来源。
错误做法
请勿在自定义位置容器中添加广告、营销文案、促销折扣或订阅提示。
如需添加自定义内容,请使用 AdvancedPlaceDetailsCompact 可组合项。使用 basicSectionTop 或 basicSectionBottom 指定内容的位置。
高级地点详情示例
自定义操作按钮
您可以使用 PlaceActionProvider 界面来控制每个地点可执行哪些操作。
Kotlin
class PlaceDetailsWidgetTestActivity : BaseActivity() { // simplified example of starting the fragment fun setupPPDFragment() { fullWidgetSelectedContent = AdvancedPlaceDetailsFragment.ALL_CONTENT.toMutableList() AdvancedPlaceDetailsFragment.newInstance(fullWidgetSelectedContent, Orientation.VERTICAL) supportFragmentManager .beginTransaction() .replace(R.id.fragment_container, fragment!!) .commitNow() } // you can set the callback elsewhere, but this is the ideal callback spot override fun onAttachFragment(fragment: Fragment) { super.onAttachFragment(fragment) when (fragment) { is PlaceActionController -> setPlaceActionProvider(fragment) } } // have this callback return an empty list to hide all buttons // if this callback isn't set, default buttons appear private fun setPlaceActionProvider(fragment: PlaceActionController) { fragment.setPlaceActionProvider( object : PlaceActionProvider { override fun getMainPlaceActions(place: Place): List<PlaceAction> = buildList { // predefined actions can be mixed in with custom ones add(PlaceAction.OPEN_IN_MAPS) // example of button with both text label and icon add( PlaceAction.builder() .setLabelTextResId(R.string.custom_action) .setIconResId(R.drawable.gs_custom_action_icon) .setContentDescriptionResId(R.string.custom_action_description) .setOnClickListener { /* todo perform action */ } .build() ) // icon only button add( PlaceAction.builder() .setIconResId(R.drawable.custom_action_icon_2) .setContentDescriptionResId(R.string.custom_action_description_2) .setOnClickListener { /* todo perform action */} .build() ) // text label only button add( PlaceAction.builder() .setLabelTextResId(R.string.custom_action_3) .setContentDescriptionResId(R.string.custom_action_description_2) .setOnClickListener {/* todo perform action */ } .build() ) } /** You can also specify whether corner buttons should appear. For either callback, returning an empty list results in no buttons appearing */ override fun getCornerPlaceActions(place: Place): List<PlaceAction> { return emptyList() } override fun addPlaceActionsChangedListener( listener: PlaceActionProvider.OnChangedListener ) { // in this example, just one listener and widget this@HostAppActivity.listener = listener } override fun removePlaceActionsChangedListener( listener: PlaceActionProvider.OnChangedListener ) { // if there are multiple listeners, remove only the provided listener this@HostAppActivity.listener = null } }) } }
添加自定义操作和预定义操作
此示例添加了一个切换按钮,供用户将某个地点标记为喜爱或取消标记。
Kotlin
private var listener: PlaceActionProvider.OnChangedListener? = null private fun setPlaceActionProvider(fragment: PlaceActionController) { fragment.setPlaceActionProvider( object : PlaceActionProvider { private var placeFavorited = false override fun getMainPlaceActions(place: Place): List<PlaceAction> { val buttonList = mutableListOf<PlaceAction>() if (placeFavorited) { buttonList.add( PlaceAction.builder() .setLabelTextResId(R.string.place_details_button_config_favorite) .setIconResId(R.drawable.gs_web_vd_theme_24) .setOnClickListener { placeFavorited = !placeFavorited refreshButtonConfigs() } .build() ) } else { buttonList.add( PlaceAction.builder() .setLabelTextResId(R.string.place_details_button_config_unfavorite) .setIconResId(R.drawable.gs_web_vd_theme_24) .setOnClickListener { placeFavorited = !placeFavorited refreshButtonConfigs() } .build() ) } return buttonList } override fun addPlaceActionsChangedListener( listener: PlaceActionProvider.OnChangedListener ) { this@PlaceDetailsWidgetTestActivity.listener = listener } override fun removePlaceActionsChangedListener( listener: PlaceActionProvider.OnChangedListener ) { // if there are multiple listeners, remove only the provided listener this@PlaceDetailsWidgetTestActivity.listener = null } } ) } private fun refreshButtonConfigs() { listener?.onPlaceActionsChanged() }
使用预定义的标准按钮
创建可执行标准 Google 定义的操作的操作按钮。
Kotlin
override fun getMainPlaceActions(place: Place): List<PlaceAction> { val buttonList = mutableListOf<PlaceAction>() // these buttons will have different background and text color than user-created buttons. // Text and icon button buttonList.add(PlaceAction.OPEN_IN_MAPS) // Icon-only buttons not enabled for main action buttons // Use of kotlin extension to do the same. see PlaceAction.OPEN_IN_MAPS { setIconResId(0) } return buttonList }
过滤评价和媒体内容
此代码示例仅显示提及“意大利面”和“美味”的媒体和评价。
Kotlin
import com.google.android.libraries.places.widget.AdvancedPlaceDetailsFragment import com.google.android.libraries.places.widget.model.SearchMediaOptions import com.google.android.libraries.places.widget.model.SearchReviewsOptions import com.google.android.libraries.places.widget.model.Orientation import com.google.android.libraries.places.R // 1. Create options using the Java builders (called from Kotlin) val mediaOptions = SearchMediaOptions.builder() .setQuery("spaghetti") .setRankPreference(SearchMediaOptions.RankPreference.MOST_RELEVANT) .build() val reviewOptions = SearchReviewsOptions.builder() .setQuery("tasty") .setRankPreference(SearchReviewsOptions.RankPreference.NEWEST) .build() // 2. Create the fragment val fragment = AdvancedPlaceDetailsFragment.newInstance( AdvancedPlaceDetailsFragment.STANDARD_CONTENT, Orientation.VERTICAL, R.style.PlacesMaterialTheme ) // 3. Apply options to the fragment fragment.applySearchMediaOptions(mediaOptions) fragment.applySearchReviewsOptions(reviewOptions)
创建内容槽
Kotlin
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import com.google.android.libraries.places.compose.AdvancedPlaceDetailsCompact import com.google.android.libraries.places.compose.PlaceReference @Composable fun ItineraryPlaceCard(placeId: String) { AdvancedPlaceDetailsCompact( reference = PlaceReference.PlaceId(placeId) ) { // Slot: Injected directly above the place name basicSectionTop { Text( text = "Day 2 • 2:30 PM", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.primary ) } // Slot: Injected directly below the main action buttons basicSectionBottom { NotesCard( notes = "Great quiet atmosphere for morning meetings. Famous for house-roasted pour-overs." ) } } }