在上篇文章Chrome Custom Tab(一)— 自定义款式与基本使用中,介绍了翻开网页、调整视图高度、调整地址栏款式、替换显示隐藏动画等功能。

本文介绍怎么增加功能按钮、菜单项,以及怎么装备预加载。

增加功能按钮

Custom Tab支持在地址栏中增加功能按钮,代码如下:

// 辅佐类
object CustomTabHelper {
    private var customTabAvailablePackageName: String = ""
    private var customTabsClient: CustomTabsClient? = null
    private var customTabsServiceConnection: CustomTabsServiceConnection? = null
    fun openCustomTabWithCustomActionButton(context: Context, url: String, actionButtonIcon: Bitmap, actionButtonLabel: String, pendingIntent: PendingIntent) {
        val customTabsIntentBuilder = CustomTabsIntent.Builder(customTabsClient?.newSession(null))
        // 设置按钮的图标、案牍以及点击图标时触发的PendingIntent
        customTabsIntentBuilder.setActionButton(actionButtonIcon, actionButtonLabel, pendingIntent)
        customTabsIntentBuilder.build().launchUrl(context, url.toUri())
    }
    fun checkCustomTabAvailable(context: Context): Boolean {
        val packageManager = context.packageManager
        val browsableIntent = Intent().apply {
            action = Intent.ACTION_VIEW
            addCategory(Intent.CATEGORY_BROWSABLE)
            data = Uri.fromParts("http", "", null)
        }
        // 获取所有浏览器
        val browsableResolverInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            packageManager.queryIntentActivities(browsableIntent, PackageManager.ResolveInfoFlags.of(0))
        } else {
            packageManager.queryIntentActivities(browsableIntent, 0)
        }
        val supportingCustomTabResolveInfo = ArrayList<ResolveInfo>()
        browsableResolverInfo.forEach {
            val serviceIntent = Intent().apply {
                action = androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION
                setPackage(it.activityInfo.packageName)
            }
            val customTabServiceResolverInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                packageManager.resolveService(serviceIntent, PackageManager.ResolveInfoFlags.of(0))
            } else {
                packageManager.resolveService(serviceIntent, 0)
            }
            // 判断是否能够处理Custom Tabs service
            if (customTabServiceResolverInfo != null) {
                supportingCustomTabResolveInfo.add(it)
            }
        }
        if (supportingCustomTabResolveInfo.isNotEmpty()) {
            customTabAvailablePackageName = supportingCustomTabResolveInfo[0].activityInfo.packageName
        }
        return supportingCustomTabResolveInfo.isNotEmpty()
    }
    fun bindCustomTabsService(activity: Activity) {
        if (checkCustomTabAvailable(activity)) {
            if (customTabsClient == null) {
                customTabsServiceConnection = object : CustomTabsServiceConnection() {
                    override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
                        customTabsClient = client
                    }
                    override fun onServiceDisconnected(name: ComponentName?) {
                        customTabsClient = null
                    }
                }
                customTabsServiceConnection?.let {
                    CustomTabsClient.bindCustomTabsService(activity, customTabAvailablePackageName, it)
                }
            }
        }
    }
    fun unbindCustomTabsService(activity: Activity) {
        customTabsServiceConnection?.let { activity.unbindService(it) }
        customTabsClient = null
        customTabsServiceConnection = null
    }
}
// 示例Activity
class CustomTabExampleActivity : BaseGestureDetectorActivity() {
    private val url = "https://go.minigame.vip/"
    private val ACTION_ID = "actionId"
    private val ACTION_ID_SCAN = 1
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: LayoutCustomTabActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_custom_tab_activity)
        binding.btnCustomActionButton.setOnClickListener {
            checkCustomTabAvailable()
            ContextCompat.getDrawable(this, R.drawable.icon_scan_32_black)?.let {
                CustomTabHelper.openCustomTabWithCustomActionButton(this, url, toBitmap(it), "Scan", createPendingIntent(ACTION_ID_SCAN))
            }
        }
    }
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        intent?.run {
            when (getIntExtra(ACTION_ID, -1)) {
                ACTION_ID_SCAN -> showToast("click scan action button")
                else -> showToast("Unknown action id")
            }
        }
    }
    private fun checkCustomTabAvailable() {
        if (!CustomTabHelper.checkCustomTabAvailable(this)) {
            startActivity(Intent(this, WebViewActivity::class.java).apply { putExtra(PARAMS_LINK_URL, url) })
            return
        }
    }
    override fun onStart() {
        super.onStart()
        CustomTabHelper.bindCustomTabsService(this)
    }
    override fun onDestroy() {
        super.onDestroy()
        CustomTabHelper.unbindCustomTabsService(this)
    }
    private fun toBitmap(drawable: Drawable): Bitmap {
        val width = DensityUtil.dp2Px(24)
        val height = DensityUtil.dp2Px(24)
        val oldBounds = Rect(drawable.bounds)
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        drawable.setBounds(0, 0, width, height)
        drawable.draw(Canvas(bitmap))
        drawable.bounds = oldBounds
        return bitmap
    }
    private fun createPendingIntent(actionId: Int): PendingIntent {
        val actionIntent = Intent(applicationContext, CustomTabExampleActivity::class.java).apply {
            putExtra(ACTION_ID, actionId)
        }
        return PendingIntent.getActivity(applicationContext, this.hashCode() + actionId, actionIntent, PendingIntent.FLAG_IMMUTABLE)
    }
}

作用如图:

Android Chrome  Custom Tab(二)— 操作按钮自定义以及预加载

增加菜单项

Custom Tab支持在地址栏的溢出菜单中增加菜单项(至多5个),代码如下:

// 辅佐类
object CustomTabHelper {
    fun openCustomTabWithCustomMenu(context: Context, url: String, menuItemParams: Map<String, PendingIntent>) {
        val customTabsIntentBuilder = CustomTabsIntent.Builder(customTabsClient?.newSession(null))
        menuItemParams.entries.forEach {
            // 设置菜单项的案牍,以及点击菜单项时触发的PendingIntent
            customTabsIntentBuilder.addMenuItem(it.key, it.value)
        }
        customTabsIntentBuilder.build().launchUrl(context, url.toUri())
    }
}
// 示例Activity
class CustomTabExampleActivity : BaseGestureDetectorActivity() {
    private val url = "https://go.minigame.vip/"
    private val ACTION_ID = "actionId"
    private val ACTION_ID_COPY_LINK = 2
    private val ACTION_ID_SEARCH = 3
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: LayoutCustomTabActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_custom_tab_activity)
        binding.btnCustomMenu.setOnClickListener {
            checkCustomTabAvailable()
            CustomTabHelper.openCustomTabWithCustomMenu(this, url, mapOf(
                Pair("Copy Link", createPendingIntent(ACTION_ID_COPY_LINK)),
                Pair("Search In App", createPendingIntent(ACTION_ID_SEARCH))
            ))
        }
    }
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        intent?.run {
            when (getIntExtra(ACTION_ID, -1)) {
                ACTION_ID_COPY_LINK -> showToast("click copy link menu item")
                ACTION_ID_SEARCH -> showToast("click search menu item")
                else -> showToast("Unknown action id")
            }
        }
    }
    private fun checkCustomTabAvailable() {
        if (!CustomTabHelper.checkCustomTabAvailable(this)) {
            startActivity(Intent(this, WebViewActivity::class.java).apply { putExtra(PARAMS_LINK_URL, url) })
            return
        }
    }
    override fun onStart() {
        super.onStart()
        CustomTabHelper.bindCustomTabsService(this)
    }
    override fun onDestroy() {
        super.onDestroy()
        CustomTabHelper.unbindCustomTabsService(this)
    }
    private fun toBitmap(drawable: Drawable): Bitmap {
        val width = DensityUtil.dp2Px(24)
        val height = DensityUtil.dp2Px(24)
        val oldBounds = Rect(drawable.bounds)
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        drawable.setBounds(0, 0, width, height)
        drawable.draw(Canvas(bitmap))
        drawable.bounds = oldBounds
        return bitmap
    }
    private fun createPendingIntent(actionId: Int): PendingIntent {
        val actionIntent = Intent(applicationContext, CustomTabExampleActivity::class.java).apply {
            putExtra(ACTION_ID, actionId)
        }
        return PendingIntent.getActivity(applicationContext, this.hashCode() + actionId, actionIntent, PendingIntent.FLAG_IMMUTABLE)
    }
}

作用如图:

Android Chrome  Custom Tab(二)— 操作按钮自定义以及预加载

预加载

Custom Tab支持预热浏览器,装备可能会拜访的网站,能够加快网页的翻开速度,使用户取得更好的体会,代码如下:

// 辅佐类
object CustomTabHelper {
    private val mayLaunchUrl = arrayListOf("https://go.minigame.vip/", "https:///", "https://www.baidu.com/")
    private var customTabAvailablePackageName: String = ""
    private var customTabsClient: CustomTabsClient? = null
    private var customTabsSession: CustomTabsSession? = null
    private var customTabsServiceConnection: CustomTabsServiceConnection? = null
    fun openSimpleCustomTab(context: Context, url: String) {
        CustomTabsIntent.Builder(customTabsSession).build().launchUrl(context, url.toUri())
    }
    fun checkCustomTabAvailable(context: Context): Boolean {
        val packageManager = context.packageManager
        val browsableIntent = Intent().apply {
            action = Intent.ACTION_VIEW
            addCategory(Intent.CATEGORY_BROWSABLE)
            data = Uri.fromParts("http", "", null)
        }
        // 获取所有浏览器
        val browsableResolverInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            packageManager.queryIntentActivities(browsableIntent, PackageManager.ResolveInfoFlags.of(0))
        } else {
            packageManager.queryIntentActivities(browsableIntent, 0)
        }
        val supportingCustomTabResolveInfo = ArrayList<ResolveInfo>()
        browsableResolverInfo.forEach {
            val serviceIntent = Intent().apply {
                action = androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION
                setPackage(it.activityInfo.packageName)
            }
            val customTabServiceResolverInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                packageManager.resolveService(serviceIntent, PackageManager.ResolveInfoFlags.of(0))
            } else {
                packageManager.resolveService(serviceIntent, 0)
            }
            // 判断是否能够处理Custom Tabs service
            if (customTabServiceResolverInfo != null) {
                supportingCustomTabResolveInfo.add(it)
            }
        }
        if (supportingCustomTabResolveInfo.isNotEmpty()) {
            customTabAvailablePackageName = supportingCustomTabResolveInfo[0].activityInfo.packageName
        }
        return supportingCustomTabResolveInfo.isNotEmpty()
    }
    fun bindCustomTabsService(activity: Activity) {
        if (checkCustomTabAvailable(activity)) {
            if (customTabsClient == null) {
                customTabsServiceConnection = object : CustomTabsServiceConnection() {
                    override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
                        // 预热浏览器,返回是否成功
                        client.warmup(0)
                        customTabsSession = client.newSession(null)
                        customTabsSession?.let {
                            var mainUri: Uri? = null
                            val otherLinkBundles = ArrayList<Bundle>()
                            for ((index, url) in mayLaunchUrl.withIndex()) {
                                if (index == 0) {
                                    mainUri = url.toUri()
                                } else {
                                    otherLinkBundles.add(Bundle().apply { putParcelable(CustomTabsService.KEY_URL, url.toUri()) })
                                }
                            }
                            // 装备可能会拜访的网站
                            // 设置一个主链接,能够通过Bundle List传入其他可能会翻开的链接
                            it.mayLaunchUrl(mainUri, null, otherLinkBundles)
                        }
                        customTabsClient = client
                    }
                    override fun onServiceDisconnected(name: ComponentName?) {
                        customTabsClient = null
                        customTabsSession = null
                    }
                }
                customTabsServiceConnection?.let {
                    CustomTabsClient.bindCustomTabsService(activity, customTabAvailablePackageName, it)
                }
            }
        }
    }
    fun unbindCustomTabsService(activity: Activity) {
        customTabsServiceConnection?.let { activity.unbindService(it) }
        customTabsClient = null
        customTabsSession = null
        customTabsServiceConnection = null
    }
}
// 示例Activity
class CustomTabExampleActivity : BaseGestureDetectorActivity() {
    private val url = "https://go.minigame.vip/"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: LayoutCustomTabActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_custom_tab_activity)
        binding.btnOpenSimpleTab.setOnClickListener {
            checkCustomTabAvailable()
            CustomTabHelper.openSimpleCustomTab(this, url)
        }
    }
    private fun checkCustomTabAvailable() {
        if (!CustomTabHelper.checkCustomTabAvailable(this)) {
            startActivity(Intent(this, WebViewActivity::class.java).apply { putExtra(PARAMS_LINK_URL, url) })
            return
        }
    }
    override fun onStart() {
        super.onStart()
        CustomTabHelper.bindCustomTabsService(this)
    }
    override fun onDestroy() {
        super.onDestroy()
        CustomTabHelper.unbindCustomTabsService(this)
    }
}

作用如图:

无预加载 预加载
Android Chrome  Custom Tab(二)— 操作按钮自定义以及预加载
Android Chrome  Custom Tab(二)— 操作按钮自定义以及预加载

视频转换为GIF后有点卡顿,可是开启预加载后,网页中的资源加载速度仍是有所提高的。

示例

在示例Demo中增加了相关的演示代码。

ExampleDemo github

ExampleDemo gitee