Android UI Basics
Android UI Basics Interview with follow-up questions
1. Can you explain the different layout types in Android?
In 2026, Android UI is built primarily with Jetpack Compose, which uses a declarative layout model — you describe what the UI should look like given the current state, and Compose handles rendering and updates. The older XML-based View system is still supported and widely found in existing codebases, so knowing both is expected.
Jetpack Compose layout composables
Compose does not use named "layout types" in the same way as the View system; instead, it uses composable functions:
Column: arranges children vertically, top to bottom. Equivalent to a verticalLinearLayout.Row: arranges children horizontally. Equivalent to a horizontalLinearLayout.Box: stacks children on top of each other (last child on top). Equivalent toFrameLayout.LazyColumn/LazyRow: efficiently displays large scrollable lists, composing only visible items. The modern replacement forRecyclerView.LazyVerticalGrid/LazyHorizontalGrid: grid layouts for large datasets.ConstraintLayout(compose-constraint): available in Compose viaandroidx.constraintlayout.compose, for complex constraint-based layouts whenRow/Columnnesting becomes unwieldy.Scaffold: provides Material Design structure — top app bar, bottom navigation, FAB, snack bar host — coordinated in one composable.
XML / View-based layout types (legacy but still relevant)
LinearLayout: arranges children in a single row or column. Simple but can create deep view hierarchies when nested.ConstraintLayout: the most powerful XML layout. Define relationships between views using constraints. Recommended for flat, complex layouts without deep nesting. Significantly better performance than nestedLinearLayouts.FrameLayout: single child (or stacked children). Used as a fragment container and for overlapping views.RelativeLayout: position views relative to each other or the parent. Largely replaced byConstraintLayout.CoordinatorLayout: from the Material Components library. Enables coordinated scrolling behaviors (collapsing toolbars, anchored FABs). Works withAppBarLayoutandCollapsingToolbarLayout.RecyclerView: not a layout per se, but the standard scrollable list/grid widget in the View system.
Which to use when
New code should use Compose. For screens with many complex constraints and animations, ConstraintLayout in Compose is available. In legacy codebases, ConstraintLayout is the go-to XML layout for anything beyond a simple linear arrangement. Avoid deep nesting of LinearLayout — it degrades measure/layout performance.
Common gotcha: in Compose, LazyColumn items are not composed until they scroll into view, so you cannot reliably use remember to hold state across items — each item composable is a separate call site. Use LazyListState and persistent storage for scroll position and item state.
Follow-up 1
What is the difference between LinearLayout and RelativeLayout?
LinearLayout arranges its child views in a single row or column, either horizontally or vertically. Views are placed one after another in the specified direction. RelativeLayout, on the other hand, allows you to position views relative to each other or to the parent layout. Views can be aligned to the top, bottom, left, right, or center of other views, or they can be positioned relative to the parent layout. RelativeLayout provides more flexibility in positioning views, while LinearLayout is simpler and more straightforward for arranging views in a linear manner.
Follow-up 2
How does ConstraintLayout improve performance?
ConstraintLayout improves performance by reducing the number of nested views and by optimizing the layout calculation process. With ConstraintLayout, you can create complex layouts without the need for nested views, which can improve performance by reducing the view hierarchy depth. Additionally, ConstraintLayout uses a more efficient algorithm for calculating the positions and sizes of views, resulting in faster layout rendering. It also provides features like layout constraints, which allow you to create responsive layouts that adapt to different screen sizes and orientations.
Follow-up 3
Can you give an example of when you might use a FrameLayout?
FrameLayout is commonly used when you want to display a single view at a time, such as when implementing a tabbed interface or a slideshow. For example, if you have a screen with multiple tabs and you want to display the content of each tab one at a time, you can use a FrameLayout to hold the content views and switch between them by showing and hiding the appropriate view. FrameLayout is also useful when you want to overlay views on top of each other, such as displaying a progress indicator or a floating action button.
Follow-up 4
What is the purpose of a ViewGroup?
A ViewGroup is a special type of view that can contain other views. It is used to define the structure and layout of the user interface in an Android app. ViewGroup acts as a container for other views, allowing you to organize and arrange them in a specific way. It provides methods for adding, removing, and manipulating child views, as well as for specifying layout parameters for the child views. Examples of ViewGroup subclasses include LinearLayout, RelativeLayout, and ConstraintLayout, which are used to create different types of layouts in Android.
2. How do you reference a UI element in code?
How you reference a UI element depends on whether you are using Jetpack Compose or the traditional View-based system.
Jetpack Compose (modern approach)
In Compose, there is no concept of "referencing a UI element by ID" because composables are functions, not objects you hold references to. Instead, state drives the UI — you pass data and callbacks down to composables and respond to changes through state holders:
@Composable
fun GreetingScreen(viewModel: GreetingViewModel = hiltViewModel()) {
val name by viewModel.name.collectAsStateWithLifecycle()
Text(text = "Hello, $name!")
Button(onClick = { viewModel.updateName("World") }) {
Text("Update")
}
}
You interact with the UI by updating state, not by grabbing a reference to a Text composable and calling .setText().
View-based system (legacy / XML layouts)
In XML-based code you assign an android:id attribute to the view in the layout file:
Then in code you reference it using one of these approaches:
ViewBinding (recommended for View-based code)
// In an Activity
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.greetingText.text = "Hello, World!"
}
ViewBinding generates a type-safe binding class for each layout. It is null-safe (no risk of referencing a view from the wrong layout) and faster than findViewById. Enabled in build.gradle with viewBinding { enabled = true }.
findViewById (older pattern, still common)
val textView = findViewById(R.id.greetingText)
textView.text = "Hello, World!"
findViewById is error-prone: it returns null if the ID doesn't exist (or the wrong view on Android versions below API 26), and requires an unchecked cast. ViewBinding is preferred.
DataBinding (declarative binding)
DataBinding goes a step further by binding data directly in XML and observing LiveData/StateFlow. It is functional but adds build complexity; most new codebases use ViewBinding for Views or Compose for new screens instead.
Common gotcha: calling findViewById before setContentView() returns null. In fragments, do not call findViewById in onCreateView; use onViewCreated or ViewBinding, and always null out the binding reference in onDestroyView to prevent memory leaks.
Follow-up 1
What is the purpose of the findViewById method?
The findViewById method is used to find and return a reference to a UI element with a specific ID. It allows you to access and manipulate the properties and behavior of the UI element programmatically. By referencing UI elements in code, you can dynamically update their content, change their visibility, handle user interactions, and more.
Follow-up 2
What happens if findViewById doesn't find a match?
If the findViewById method doesn't find a UI element with the specified ID, it returns null. It's important to handle this possibility in your code to avoid NullPointerExceptions. You can check if the returned reference is null before using it, or use the Optional class introduced in Java 8 to handle nullability more gracefully.
Follow-up 3
Can you explain the concept of View Binding?
View Binding is a feature introduced in Android Studio 3.6 that simplifies the process of referencing UI elements in code. It generates a binding class for each XML layout file in your project, which contains direct references to all the views with an ID. You can then use these generated binding classes to access the views without the need for findViewById. View Binding provides compile-time safety, improves code readability, and reduces the risk of runtime errors caused by incorrect view IDs.
3. What is the difference between match_parent and fill_parent?
fill_parent and match_parent are functionally identical — both instruct a view to expand to fill the available space of its parent.
fill_parent was the original name introduced in Android 1.0. It was renamed to match_parent in API level 8 (Android 2.2, released in 2010) because the new name more accurately describes what the attribute does: the view's size "matches" its parent's size rather than "filling" it. fill_parent was deprecated at the same time.
In practice, both values have always compiled to the same constant (-1 in the LayoutParams class), so they behave identically. However, you should always use match_parent in any code written or maintained today — fill_parent is a historical artifact you might encounter in very old codebases or tutorials.
android:layout_width="match_parent"
android:layout_width="fill_parent"
In Jetpack Compose, the equivalent is the fillMaxWidth(), fillMaxHeight(), or fillMaxSize() modifiers:
Text(
text = "Hello",
modifier = Modifier.fillMaxWidth()
)
There is no XML attribute in Compose — layout sizing is expressed through Modifier chaining.
Common interview follow-up: what is the difference between match_parent and wrap_content? match_parent sizes the view to its parent's size; wrap_content sizes the view to just fit its own content.
Follow-up 1
What happens if we use match_parent in a ScrollView?
If we use match_parent in a ScrollView, the view will expand to fill the available space within the ScrollView. This means that the view will stretch to fill the entire height or width of the ScrollView, depending on its orientation.
Follow-up 2
What is the equivalent of these in ConstraintLayout?
In ConstraintLayout, the equivalent of match_parent is match_constraint or 0dp. This means that the view's dimension will be determined by the constraints applied to it. The equivalent of fill_parent is wrap_content, which means that the view's dimension will wrap its content.
Follow-up 3
In which versions of Android is fill_parent used?
fill_parent was used in Android versions prior to API level 8. Starting from API level 8, fill_parent was deprecated and replaced with match_parent.
4. How can you handle user interactions with UI elements?
How you handle user interactions depends on whether you are using Jetpack Compose or the View-based system.
Jetpack Compose (modern approach)
In Compose, interaction handling is declarative. You pass lambda callbacks directly into composables:
@Composable
fun LoginScreen(viewModel: LoginViewModel = hiltViewModel()) {
var username by remember { mutableStateOf("") }
Column {
TextField(
value = username,
onValueChange = { username = it },
label = { Text("Username") }
)
Button(onClick = { viewModel.login(username) }) {
Text("Log In")
}
}
}
There are no setOnClickListener calls — the event handler is declared as a parameter. Compose provides interaction-specific APIs as well:
Modifier.clickable { }— tap events with ripple feedback.Modifier.pointerInput(Unit) { detectTapGestures { } }— low-level pointer handling.Modifier.draggable/Modifier.swipeable— drag and swipe gestures.
View-based system (legacy)
In XML + Kotlin code, you attach listeners to view objects:
Click listener
binding.submitButton.setOnClickListener {
viewModel.submit()
}
Long click listener
binding.itemView.setOnLongClickListener {
showContextMenu()
true // consume the event
}
Text changes
binding.searchField.addTextChangedListener { text ->
viewModel.search(text.toString())
}
Touch events
view.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> { /* finger down */ true }
MotionEvent.ACTION_UP -> { /* finger up */ true }
else -> false
}
}
Wiring UI events to the ViewModel
In both Compose and the View system, event handlers should delegate to the ViewModel rather than performing business logic directly in the UI layer. The ViewModel processes the event and updates state; the UI observes the state and recompose/re-renders:
// ViewModel
class LoginViewModel : ViewModel() {
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow = _uiState.asStateFlow()
fun login(username: String) {
viewModelScope.launch { /* call repository */ }
}
}
Common gotcha: in Compose, lambdas passed to event handlers should be stable (not create new lambda instances on every recomposition) to avoid unnecessary recomposition. Use remember { } or pass ViewModel method references directly.
Follow-up 1
Can you explain the concept of event listeners?
Event listeners are functions that are used to handle specific events that occur on UI elements. They are attached to elements and wait for the specified event to occur. When the event is triggered, the event listener function is executed. Event listeners can be added to elements using JavaScript or through HTML attributes.
Here is an example of adding an event listener to a button element using JavaScript:
const button = document.querySelector('#myButton');
button.addEventListener('click', function() {
// Code to be executed when the button is clicked
});
In this example, the event listener is added to the button element with the id 'myButton'. When the button is clicked, the function inside the event listener will be executed.
Follow-up 2
What is the difference between onClick and onTouch?
The onClick event is triggered when a UI element is clicked using a mouse or a touch screen. It is commonly used for handling user interactions on desktop and mobile devices.
The onTouch event, on the other hand, is specifically designed for touch screen devices. It is triggered when a touch is detected on a UI element. The onTouch event can handle multiple touch points and provides additional touch-related information such as touch coordinates and touch duration.
In general, onClick is used for handling mouse clicks and touch screen taps, while onTouch is used for more advanced touch interactions on touch screen devices.
Follow-up 3
How can you handle long click events?
To handle long click events, you can use the onLongClick event listener. This event listener is triggered when a UI element is pressed and held for a certain duration.
Here is an example of adding an onLongClick event listener to a button element using JavaScript:
const button = document.querySelector('#myButton');
button.addEventListener('contextmenu', function(event) {
event.preventDefault(); // Prevent the default context menu
// Code to be executed when the button is long clicked
});
In this example, the onLongClick event listener is added to the button element with the id 'myButton'. When the button is pressed and held for a certain duration, the function inside the event listener will be executed. The event.preventDefault() method is used to prevent the default context menu from appearing when the button is long clicked.
5. What are some common attributes you can set in the XML for a UI element?
This question is typically asked about the XML View system. In Jetpack Compose, attributes are replaced by Modifier functions and composable parameters — there are no XML files. For existing codebases or hybrid apps using Views, the following XML attributes are fundamental.
Layout and sizing
android:id— unique identifier for the view, referenced in code asR.id.myView.android:layout_width/android:layout_height— required on every view. Values:match_parent(fill parent),wrap_content(shrink to content), or a specificdpvalue.android:layout_margin/android:layout_marginStart/android:layout_marginEndetc. — space outside the view's bounds.android:padding/android:paddingStartetc. — space inside the view's bounds, between the view edge and its content.
Appearance
android:background— background color (@color/primary) or drawable (@drawable/rounded_button).android:elevation— shadow depth in dp, part of Material Design's z-axis.android:alpha— opacity from 0.0 (transparent) to 1.0 (opaque).android:visibility—visible,invisible(hidden but still occupying space), orgone(hidden and not occupying space).
Text (TextView, Button, EditText)
android:text— the text content, ideally referencing a string resource (@string/label).android:textSize— font size insp(scaled pixels, respects user font size preference).android:textColor— text color.android:hint— placeholder text shown when the field is empty (EditText).android:inputType— keyboard type for EditText:text,number,textPassword,textEmailAddress, etc.android:maxLines/android:ellipsize— control text truncation.
Layout behavior
android:gravity— alignment of content within the view (e.g.,center,end).android:layout_gravity— alignment of the view within its parent (FrameLayout, LinearLayout).android:layout_weight— inLinearLayout, distributes remaining space proportionally among children.android:orientation—horizontalorverticalforLinearLayout.
Interaction
android:clickable— whether the view responds to clicks.android:focusable— whether the view can receive keyboard/d-pad focus.android:contentDescription— accessibility label for screen readers. Required forImageViewand icon-only buttons.
In Jetpack Compose, all of these map to Modifier extensions and composable parameters. For example:
Text(
text = stringResource(R.string.label),
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(MaterialTheme.colorScheme.surface)
)
Follow-up 1
How do you set the width and height of a view?
To set the width and height of a view, you can use the android:layout_width and android:layout_height attributes in the XML layout file. These attributes accept different values:
match_parentorfill_parent: The view takes up the entire available space in its parent.wrap_content: The view adjusts its size to fit its content.- Specific dimension values: You can specify a specific dimension value in pixels (e.g.,
100dp) or other units.
For example, to set the width and height of a TextView to match its parent, you can use:
Follow-up 2
How can you change the background color of a view?
To change the background color of a view, you can use the android:background attribute in the XML layout file. This attribute accepts different values:
- Color values: You can specify a color value using the
#RRGGBBor#AARRGGBBformat. - Color resources: You can reference a color resource defined in
res/values/colors.xmlusing the@color/colorNamesyntax. - Drawable resources: You can reference a drawable resource defined in
res/drawableusing the@drawable/drawableNamesyntax.
For example, to set the background color of a LinearLayout to red, you can use:
Follow-up 3
What is the purpose of the layout_weight attribute in a LinearLayout?
The layout_weight attribute is used in a LinearLayout to distribute the remaining space among its child views. It is used in combination with the android:layout_width or android:layout_height attribute set to 0dp (or 0px).
When you set the layout_weight attribute of a child view to a positive value, it specifies the proportion of the remaining space that the view should occupy. For example, if you have two child views with layout_weight values of 1 and 2, the second view will take twice as much space as the first view.
Here's an example of using layout_weight in a LinearLayout:
Live mock interview
Mock interview: Android UI Basics
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.