1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.intentresolver.widget 18 19 import android.content.Context 20 import android.graphics.Rect 21 import android.graphics.drawable.Drawable 22 import android.util.AttributeSet 23 import android.view.LayoutInflater 24 import android.view.View 25 import android.view.ViewGroup 26 import android.widget.TextView 27 import androidx.core.view.ViewCompat 28 import androidx.recyclerview.widget.LinearLayoutManager 29 import androidx.recyclerview.widget.RecyclerView 30 import com.android.intentresolver.R 31 32 class ScrollableActionRow : RecyclerView, ActionRow { 33 constructor(context: Context) : this(context, null) 34 constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) 35 constructor( 36 context: Context, 37 attrs: AttributeSet?, 38 defStyleAttr: Int 39 ) : super(context, attrs, defStyleAttr) { 40 layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) 41 adapter = Adapter(context) 42 43 addItemDecoration( 44 MarginDecoration( 45 context.resources.getDimensionPixelSize(R.dimen.chooser_action_horizontal_margin), 46 context.resources.getDimensionPixelSize(R.dimen.chooser_edge_margin_normal) 47 ) 48 ) 49 } 50 51 private val actionsAdapter 52 get() = adapter as Adapter 53 54 override fun setActions(actions: List<ActionRow.Action>) { 55 actionsAdapter.setActions(actions) 56 } 57 58 override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 59 super.onLayout(changed, l, t, r, b) 60 setOverScrollMode( 61 if (areAllChildrenVisible) View.OVER_SCROLL_NEVER else View.OVER_SCROLL_ALWAYS 62 ) 63 } 64 65 private inner class Adapter(private val context: Context) : RecyclerView.Adapter<ViewHolder>() { 66 private val iconSize: Int = 67 context.resources.getDimensionPixelSize(R.dimen.chooser_action_view_icon_size) 68 private val itemLayout = R.layout.chooser_action_view 69 private var actions: List<ActionRow.Action> = emptyList() 70 71 override fun onCreateViewHolder(parent: ViewGroup, type: Int): ViewHolder = 72 ViewHolder( 73 LayoutInflater.from(context).inflate(itemLayout, null) as TextView, 74 iconSize, 75 ) 76 77 override fun onBindViewHolder(holder: ViewHolder, position: Int) { 78 holder.bind(actions[position]) 79 } 80 81 override fun getItemCount() = actions.size 82 83 override fun onViewRecycled(holder: ViewHolder) { 84 holder.unbind() 85 } 86 87 override fun onFailedToRecycleView(holder: ViewHolder): Boolean { 88 holder.unbind() 89 return super.onFailedToRecycleView(holder) 90 } 91 92 fun setActions(actions: List<ActionRow.Action>) { 93 this.actions = ArrayList(actions) 94 notifyDataSetChanged() 95 } 96 } 97 98 private inner class ViewHolder( 99 private val view: TextView, 100 private val iconSize: Int, 101 ) : RecyclerView.ViewHolder(view) { 102 103 fun bind(action: ActionRow.Action) { 104 action.icon?.let { icon -> 105 icon.setBounds(0, 0, iconSize, iconSize) 106 // some drawables (edit) does not gets tinted when set to the top of the text 107 // with TextView#setCompoundDrawableRelative 108 tintIcon(icon, view) 109 view.setCompoundDrawablesRelative(icon, null, null, null) 110 } 111 view.text = action.label ?: "" 112 view.setOnClickListener { action.onClicked.run() } 113 view.id = action.id 114 } 115 116 fun unbind() { 117 view.setOnClickListener(null) 118 } 119 120 private fun tintIcon(drawable: Drawable, view: TextView) { 121 val tintList = view.compoundDrawableTintList ?: return 122 drawable.setTintList(tintList) 123 view.compoundDrawableTintMode?.let { drawable.setTintMode(it) } 124 view.compoundDrawableTintBlendMode?.let { drawable.setTintBlendMode(it) } 125 } 126 } 127 128 private class MarginDecoration(private val innerMargin: Int, private val outerMargin: Int) : 129 ItemDecoration() { 130 override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: State) { 131 val index = parent.getChildAdapterPosition(view) 132 val startMargin = if (index == 0) outerMargin else innerMargin 133 val endMargin = if (index == state.itemCount - 1) outerMargin else innerMargin 134 135 if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) { 136 outRect.right = startMargin 137 outRect.left = endMargin 138 } else { 139 outRect.left = startMargin 140 outRect.right = endMargin 141 } 142 } 143 } 144 } 145