Irbisa · Cheatsheet
Views, Layouts & RecyclerView
Layouts, ConstraintLayout, ViewBinding, custom views, measure/layout/draw, RecyclerView, DiffUtil
What is the difference between
View.VISIBLE,View.INVISIBLEandView.GONE?What is the difference between
px,dpandsp, and which one goes where?What is the difference between a style and a theme, and what does
?attr/colorPrimarymean?<!-- res/values/themes.xml: a theme, applied to the whole app --><style name="Theme.App" parent="Theme.Material3.DayNight.NoActionBar"> <item name="colorPrimary">@color/brand</item></style> <!-- A style: values for one view, colours taken from the theme -->…What is the difference between ViewBinding, DataBinding and
findViewById?// build.gradle.kts: android { buildFeatures { viewBinding = true } } // res/layout/activity_main.xml -> ActivityMainBindingclass MainActivity : AppCompatActivity() { // Fine in an Activity: the Activity and its views die together private lateinit var binding: ActivityMainBinding…How does
RecyclerViewwork, and when areonCreateViewHolderandonBindViewHoldercalled?class OrderAdapter( private val items: List<Order>, private val onClick: (Order) -> Unit,) : RecyclerView.Adapter<OrderAdapter.Holder>() { class Holder(val binding: ItemOrderBinding) : RecyclerView.ViewHolder(binding.root)…What do
ItemDecoration,ItemTouchHelperandSnapHelperadd to aRecyclerView?class SpacingDecoration(private val space: Int) : RecyclerView.ItemDecoration() { override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { val pos = parent.getChildAdapterPosition(view) if (pos == RecyclerView.NO_POSITION) return outRect.bottom = space if (pos == 0) outRect.top = space // equal gaps, no doubling between rows…What problems does
ConstraintLayoutsolve, and what do0dp, chains, guidelines and barriers do?<!-- right: 0dp fills what start..end leaves; match_parent would ignore the end constraint --><TextView android:id="@+id/title" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"…How does layout inflation work, and what do
<include>,<merge>andViewStubchange?<!-- res/layout/view_header.xml: <merge>, so both views attach to the including parent --><merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:parentTag="android.widget.LinearLayout"> <ImageView android:id="@+id/avatar"…This Fragment keeps its binding in a
lateinit varand LeakCanary reports a leak. What is wrong, and how do you fix it?class ProfileFragment : Fragment(R.layout.fragment_profile) { private lateinit var binding: FragmentProfileBinding override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding = FragmentProfileBinding.bind(view) lifecycleScope.launch { viewModel.state.collect { binding.title.text = it.title } } }}What is the lifecycle of a
View, from creation toonDetachedFromWindow?Why is
view.width0 inonCreate, and how do you get the real size?// binding: ViewBinding for this Activity, as in the ViewBinding questionoverride fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) Log.d(TAG, "width=${binding.chart.width}") // 0: not measured yet…How do
onMeasure,onLayoutandonDrawwork in a custom View, and when do you callinvalidate()vsrequestLayout()?class MeterView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { private val paint = Paint(Paint.ANTI_ALIAS_FLAG) // created once, not in onDraw private val bar = RectF()…How do you add custom XML attributes to a View and make it keep its state across rotation?
// res/values/attrs.xml// <declare-styleable name="RatingView">// <attr name="rating" format="integer" />// <attr name="starColor" format="color" />// </declare-styleable>…When would you use
SurfaceVieworTextureViewinstead of a normal View?Which animation APIs exist for Views, and why does animating
translationXstay smooth when animating a height stutters?// wrong: requestLayout and a re-measure of the branch on every frameValueAnimator.ofInt(0, targetHeight).apply { addUpdateListener { panel.updateLayoutParams { height = it.animatedValue as Int } }}.start() // right: RenderNode properties, no measure or layout…What do
DiffUtilandListAdaptergive you overnotifyDataSetChanged(), and how do you update one view in one row?class ChatAdapter : ListAdapter<Chat, ChatAdapter.Holder>(Diff) { override fun onBindViewHolder(holder: Holder, position: Int) { val item = getItem(position) holder.binding.title.text = item.title holder.binding.unread.text = item.unread.toString()…Find the bug: tapping the like button does not fill the heart until the row scrolls off-screen and back.
data class Post(val id: Long, val text: String, var liked: Boolean) object PostDiff : DiffUtil.ItemCallback<Post>() { override fun areItemsTheSame(old: Post, new: Post) = old.id == new.id override fun areContentsTheSame(old: Post, new: Post) = old == new} class FeedViewModel : ViewModel() { private val _posts = MutableStateFlow<List<Post>>(emptyList()) val posts: StateFlow<List<Post>> = _posts // the Fragment calls adapter.submitList(it) fun toggleLike(id: Long) { val copy = _posts.value.toMutableList() copy.first { it.id == id }.apply { liked = !liked } _posts.value = copy }}After scrolling, rows in this list show the previous item's badge and the wrong photo. What is wrong in
onBindViewHolder?override fun onBindViewHolder(holder: Holder, position: Int) { val item = items[position] holder.binding.title.text = item.title if (item.isUrgent) { holder.binding.badge.visibility = View.VISIBLE } holder.binding.root.setOnClickListener { onClick(items[position]) } imageLoader.load(item.photoUrl) { bitmap -> holder.binding.photo.setImageBitmap(bitmap) }}Find the bug: after scrolling a to-do list, tasks you never touched turn checked or unchecked by themselves.
class TaskAdapter( private val onToggle: (Task, Boolean) -> Unit,) : ListAdapter<Task, TaskAdapter.Holder>(TaskDiff) { class Holder(val binding: ItemTaskBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = Holder(ItemTaskBinding.inflate(LayoutInflater.from(parent.context), parent, false)) override fun onBindViewHolder(holder: Holder, position: Int) { val task = getItem(position) holder.binding.title.text = task.title holder.binding.done.isChecked = task.done holder.binding.done.setOnCheckedChangeListener { _, checked -> onToggle(task, checked) } }}How do you show several view types in one
RecyclerView, such as headers, ads and cards?// HeaderVH, ArticleVH, AdVH: ViewHolder subclasses, omittedsealed interface Row { val id: Long data class Header(override val id: Long, val title: String) : Row data class Article(override val id: Long, val headline: String) : Row data class Ad(override val id: Long, val slot: String) : Row…A
RecyclerViewstutters while scrolling. What do you check, and in what order?How do
CoordinatorLayoutand a collapsing toolbar work together with aRecyclerView?<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent"…How is a touch event dispatched through the View hierarchy, and how does a parent take a gesture away from a child?
import kotlin.math.abs class CarouselHost(ctx: Context, attrs: AttributeSet?) : FrameLayout(ctx, attrs) { private val slop = ViewConfiguration.get(ctx).scaledTouchSlop private var downX = 0f private var downY = 0f…Why does this code crash when the button is tapped, but usually not when the same kind of line runs in
onCreate?override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) thread { binding.status.text = "Loading" } // usually no crash binding.refresh.setOnClickListener { thread { binding.status.text = repository.load() } // crashes }}After raising
targetSdkto 35 or higher, the toolbar sits under the status bar and the keyboard covers the input. What changed, and how do you fix it?// binding: ViewBinding for this Activity, as in the ViewBinding questionoverride fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() // same behaviour below API 35 super.onCreate(savedInstanceState) setContentView(binding.root)…