Irbisa · Cheatsheet

Views, Layouts & RecyclerView

Layouts, ConstraintLayout, ViewBinding, custom views, measure/layout/draw, RecyclerView, DiffUtil

Middle Developer · 25 questions

  1. 01

    What is the difference between View.VISIBLE, View.INVISIBLE and View.GONE?

    INVISIBLE hides the view but keeps its space in the layout; GONE hides it and removes it from the layout, so its neighbours move into the freed space.

  2. 02

    What is the difference between px, dp and sp, and which one goes where?

    px is a physical pixel, dp is a density-independent pixel that has the same physical size on every screen, and sp is a dp that also scales with the user's font-size setting.

  3. 03

    What is the difference between a style and a theme, and what does ?attr/colorPrimary mean?

    A style is a set of attribute values for one view; a theme is a set applied to an app, Activity or view subtree, and it also defines theme attributes such as colorPrimary that any view can look up with ?attr/.

    <!-- 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 -->…
  4. 04

    What is the difference between ViewBinding, DataBinding and findViewById?

    findViewById looks a view up at runtime and can return null or the wrong type; ViewBinding generates a class per layout with a typed field for every android:id (nullable only if the view is missing from some variants); DataBinding adds expressions and two-way binding in XML.

    // 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…
  5. 05

    How does RecyclerView work, and when are onCreateViewHolder and onBindViewHolder called?

    RecyclerView creates only enough rows to fill the screen plus a few spare ones and reuses them while scrolling: onCreateViewHolder inflates a new row when no recycled row of that view type is available, and onBindViewHolder fills an existing row with the data for a position.

    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)…
  6. 06

    What do ItemDecoration, ItemTouchHelper and SnapHelper add to a RecyclerView?

    They keep list decoration and gestures out of the row layout: ItemDecoration adds spacing and dividers, ItemTouchHelper adds swipe-to-dismiss and drag-to-reorder, and SnapHelper makes a fling stop with an item aligned.

    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…
  7. 07

    What problems does ConstraintLayout solve, and what do 0dp, chains, guidelines and barriers do?

    It builds a complex screen as one flat level instead of nested LinearLayouts and RelativeLayouts.

    <!-- 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"…
  8. 08

    How does layout inflation work, and what do <include>, <merge> and ViewStub change?

    LayoutInflater walks the compiled XML on the main thread and creates a View object for every tag; <include> reuses another layout file, <merge> removes that file's extra root, and ViewStub postpones inflating a part of the screen until it is needed.

    <!-- 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"…
  9. 09

    This Fragment keeps its binding in a lateinit var and 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 }    }  }}

    A Fragment outlives its view: after onDestroyView it stays on the back stack and its binding field keeps the dead view tree alive.

  10. 10

    What is the lifecycle of a View, from creation to onDetachedFromWindow?

    A View is constructed (from XML or code), attached to a window, goes through measure, layout and draw on every frame that needs it, and is finally detached.

  11. 11

    Why is view.width 0 in onCreate, and how do you get the real size?

    In onCreate the view tree is only inflated; measure and layout run later, in the first frame after the window is attached, so until then every size is 0.

    // 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…
  12. 12

    How do onMeasure, onLayout and onDraw work in a custom View, and when do you call invalidate() vs requestLayout()?

    The parent measures each child with a MeasureSpec, positions it with layout(), and then each view paints itself in onDraw.

    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()…
  13. 13

    How do you add custom XML attributes to a View and make it keep its state across rotation?

    Declare the attributes in <declare-styleable> and read them in the constructor with obtainStyledAttributes; for state, override onSaveInstanceState and onRestoreInstanceState with a BaseSavedState subclass.

    // res/values/attrs.xml// <declare-styleable name="RatingView">//   <attr name="rating" format="integer" />//   <attr name="starColor" format="color" />// </declare-styleable>…
  14. 14

    When would you use SurfaceView or TextureView instead of a normal View?

    When frames are produced off the main thread at a high rate: video, camera preview, a game or a map renderer.

  15. 15

    Which animation APIs exist for Views, and why does animating translationX stay smooth when animating a height stutters?

    Views are animated with property animators (ValueAnimator, ObjectAnimator, view.animate()), Transitions, MotionLayout and spring/fling animations.

    // 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…
  16. 16

    What do DiffUtil and ListAdapter give you over notifyDataSetChanged(), and how do you update one view in one row?

    notifyDataSetChanged() says "everything changed": every visible row is rebound, nothing animates and scroll anchoring is lost.

    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()…
  17. 17

    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  }}

    toggleLike copies the list but mutates the shared Post in place, so the list the adapter already holds changes too.

  18. 18

    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)  }}

    The holder is a recycled row that still shows whatever the previous item set, so bind must assign every view on every path.

  19. 19

    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) }  }}

    Bind assigns isChecked while the recycled row still holds the previous item's listener, and setChecked fires it synchronously.

  20. 20

    How do you show several view types in one RecyclerView, such as headers, ads and cards?

    Return a different getItemViewType for each kind of row and create the matching ViewHolder in onCreateViewHolder.

    // 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…
  21. 21

    A RecyclerView stutters while scrolling. What do you check, and in what order?

    Measure first: a system trace in Perfetto or the Android Studio profiler shows which RecyclerView slices (RV OnBindView, RV CreateView, RV OnLayout) overflow the frame.

  22. 22

    How do CoordinatorLayout and a collapsing toolbar work together with a RecyclerView?

    Children of CoordinatorLayout react to each other through Behaviors, and the list reports its scroll through nested scrolling, so AppBarLayout can consume it and collapse first.

    <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"…
  23. 23

    How is a touch event dispatched through the View hierarchy, and how does a parent take a gesture away from a child?

    A MotionEvent travels down through dispatchTouchEvent: each ViewGroup may claim it in onInterceptTouchEvent, otherwise it goes to the child under the finger, and unhandled events bubble back up through onTouchEvent.

    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…
  24. 24

    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  }}

    Views are not thread-safe, and the check lives in ViewRootImpl.checkThread(), which runs only once the view is attached to a window.

  25. 25

    After raising targetSdk to 35 or higher, the toolbar sits under the status bar and the keyboard covers the input. What changed, and how do you fix it?

    Apps targeting SDK 35 run edge-to-edge on Android 15 whether they want to or not: the window extends under the status and navigation bars, setStatusBarColor is ignored, and every view at an edge must apply WindowInsets itself.

    // 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)…