Android UI - Views
Phần này tổng hợp các câu hỏi về Android UI Views - hệ thống View truyền thống của Android.
1. View Fundamentals
Phần tiêu đề “1. View Fundamentals”Q: View và ViewGroup khác nhau thế nào?
Phần tiêu đề “Q: View và ViewGroup khác nhau thế nào?”Trả lời:
| Aspect | View | ViewGroup |
|---|---|---|
| Definition | Single UI element | Container for Views |
| Can contain children | ❌ | ✅ |
| Examples | TextView, Button, ImageView | LinearLayout, ConstraintLayout |
| Hierarchy | Leaf node | Branch node |
// View - single elementval textView = TextView(context).apply { text = "Hello, World!" textSize = 16f}
// ViewGroup - containerval linearLayout = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL addView(TextView(context).apply { text = "Child 1" }) addView(TextView(context).apply { text = "Child 2" })}💡 Mẹo:
View.javacó hơn 34,000 dòng code! Tránh tạo View không cần thiết để optimize performance.
📚 Tìm hiểu thêm: AndroidView trong Compose
Q: View Lifecycle gồm những giai đoạn nào?
Phần tiêu đề “Q: View Lifecycle gồm những giai đoạn nào?”Trả lời:
flowchart TD A["onFinishInflate()"] --> B["onAttachedToWindow()"] B --> C["onMeasure()"] C --> D["onLayout()"] D --> E["onDraw()"] E --> F["User Interaction"] F --> G["onDetachedFromWindow()"]
style E fill:#c8e6c9 style F fill:#fff3e0| Phase | Method | Purpose |
|---|---|---|
| Creation | onFinishInflate() |
After XML inflation |
| Attachment | onAttachedToWindow() |
View added to window |
| Measurement | onMeasure() |
Determine size |
| Layout | onLayout() |
Position children |
| Drawing | onDraw() |
Render on canvas |
| Interaction | onTouchEvent() |
Handle user input |
| Detachment | onDetachedFromWindow() |
Cleanup |
class CustomView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
override fun onAttachedToWindow() { super.onAttachedToWindow() // Start animations, register listeners }
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // Calculate desired size val width = calculateWidth(widthMeasureSpec) val height = calculateHeight(heightMeasureSpec) setMeasuredDimension(width, height) }
override fun onDraw(canvas: Canvas) { // Draw content canvas.drawRect(...) canvas.drawText(...) }
override fun onDetachedFromWindow() { super.onDetachedFromWindow() // Cleanup resources }}2. RecyclerView
Phần tiêu đề “2. RecyclerView”Q: RecyclerView hoạt động như thế nào?
Phần tiêu đề “Q: RecyclerView hoạt động như thế nào?”Trả lời:
RecyclerView tái sử dụng ViewHolders để hiển thị large datasets hiệu quả.
flowchart LR A["Adapter"] --> B["ViewHolder Pool"] B --> C["LayoutManager"] C --> D["RecyclerView"]
D -.->|"recycle"| BComponents:
| Component | Responsibility |
|---|---|
| Adapter | Create ViewHolders, bind data |
| ViewHolder | Hold view references |
| LayoutManager | Position items |
| ItemDecoration | Dividers, spacing |
| ItemAnimator | Add/remove animations |
class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
private var items = listOf<User>()
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { private val binding = ItemUserBinding.bind(view)
fun bind(user: User) { binding.nameText.text = user.name binding.avatar.load(user.avatarUrl) } }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = ItemUserBinding.inflate( LayoutInflater.from(parent.context), parent, false ) return ViewHolder(view.root) }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bind(items[position]) }
override fun getItemCount() = items.size
fun submitList(newItems: List<User>) { items = newItems notifyDataSetChanged() // Use DiffUtil for better performance }}Q: DiffUtil là gì? Tại sao nên dùng?
Phần tiêu đề “Q: DiffUtil là gì? Tại sao nên dùng?”Trả lời:
DiffUtil tính toán minimal updates cần thiết để biến đổi list cũ thành list mới.
// ❌ Without DiffUtil - redraws everythingadapter.submitList(newList)adapter.notifyDataSetChanged()
// ✅ With DiffUtil - only updates changed itemsclass UserDiffCallback : DiffUtil.ItemCallback<User>() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean { return oldItem.id == newItem.id }
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean { return oldItem == newItem }}
class UserAdapter : ListAdapter<User, UserAdapter.ViewHolder>(UserDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // ... }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bind(getItem(position)) }}
// Usageadapter.submitList(newList) // DiffUtil handles updates automaticallyBenefits:
- ✅ Smooth animations
- ✅ Better performance
- ✅ Preserves scroll position
Q: RecyclerView optimizations?
Phần tiêu đề “Q: RecyclerView optimizations?”Trả lời:
// 1. Use setHasFixedSize for fixed-size itemsrecyclerView.setHasFixedSize(true)
// 2. Set item view cache sizerecyclerView.setItemViewCacheSize(20)
// 3. Use RecycledViewPool for multiple RecyclerViewsval sharedPool = RecyclerView.RecycledViewPool()recyclerView1.setRecycledViewPool(sharedPool)recyclerView2.setRecycledViewPool(sharedPool)
// 4. Avoid nested RecyclerViews (use ConcatAdapter)val concatAdapter = ConcatAdapter(headerAdapter, contentAdapter, footerAdapter)recyclerView.adapter = concatAdapter
// 5. Use stable IDsclass MyAdapter : RecyclerView.Adapter<ViewHolder>() { init { setHasStableIds(true) }
override fun getItemId(position: Int): Long = items[position].id}
// 6. Prefetch for nested scrolling(recyclerView.layoutManager as LinearLayoutManager) .initialPrefetchItemCount = 43. Custom Views
Phần tiêu đề “3. Custom Views”Q: Làm sao tạo Custom View?
Phần tiêu đề “Q: Làm sao tạo Custom View?”Trả lời:
3 cách tạo Custom View:
1. Compound View (combine existing views)
Phần tiêu đề “1. Compound View (combine existing views)”class UserInfoView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
private val binding: ViewUserInfoBinding
init { orientation = HORIZONTAL binding = ViewUserInfoBinding.inflate( LayoutInflater.from(context), this )
// Read custom attributes context.obtainStyledAttributes(attrs, R.styleable.UserInfoView).use { binding.nameText.text = it.getString(R.styleable.UserInfoView_userName) } }
fun setUser(user: User) { binding.nameText.text = user.name binding.avatar.load(user.avatarUrl) }}2. Custom Drawing (draw on canvas)
Phần tiêu đề “2. Custom Drawing (draw on canvas)”class CircleProgressView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG) var progress: Float = 0f set(value) { field = value.coerceIn(0f, 1f) invalidate() // Request redraw }
override fun onDraw(canvas: Canvas) { val centerX = width / 2f val centerY = height / 2f val radius = minOf(centerX, centerY) - paint.strokeWidth
// Draw background circle paint.color = Color.LTGRAY canvas.drawCircle(centerX, centerY, radius, paint)
// Draw progress arc paint.color = Color.BLUE canvas.drawArc( centerX - radius, centerY - radius, centerX + radius, centerY + radius, -90f, 360f * progress, false, paint ) }}3. Extend existing View
Phần tiêu đề “3. Extend existing View”class SquareImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null) : AppCompatImageView(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) // Make it square val size = minOf(measuredWidth, measuredHeight) setMeasuredDimension(size, size) }}4. Touch Handling
Phần tiêu đề “4. Touch Handling”Q: Touch event handling trong Android?
Phần tiêu đề “Q: Touch event handling trong Android?”Trả lời:
Touch events được dispatch từ parent → children (tunnel down), xử lý từ children → parent (bubble up).
flowchart TD A["dispatchTouchEvent()"] --> B["onInterceptTouchEvent()<br/>(ViewGroup only)"] B -->|"not intercepted"| C["Child dispatchTouchEvent()"] B -->|"intercepted"| D["onTouchEvent()"] C --> E["Child onTouchEvent()"] E -->|"not handled"| D
style B fill:#fff3e0 style D fill:#c8e6c9| Method | Purpose | Return |
|---|---|---|
dispatchTouchEvent() |
Dispatch to children | true if handled |
onInterceptTouchEvent() |
Parent intercepts | true to steal |
onTouchEvent() |
Handle touch | true if consumed |
class CustomViewGroup : FrameLayout {
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { // Return true to steal touch from children return when (ev.action) { MotionEvent.ACTION_MOVE -> { val dx = abs(ev.x - startX) dx > touchSlop // Intercept if horizontal swipe } else -> false } }
override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN -> { startX = event.x return true } MotionEvent.ACTION_MOVE -> { // Handle drag return true } MotionEvent.ACTION_UP -> { // Handle release return true } } return super.onTouchEvent(event) }}5. Layouts
Phần tiêu đề “5. Layouts”Q: ConstraintLayout vs other layouts?
Phần tiêu đề “Q: ConstraintLayout vs other layouts?”Trả lời:
| Layout | Nesting | Performance | Flexibility |
|---|---|---|---|
| ConstraintLayout | Flat | ✅ Best | ✅ Highest |
| LinearLayout | Can nest | ⚠️ OK | Medium |
| RelativeLayout | 2-pass | ⚠️ Slower | Medium |
| FrameLayout | Flat | ✅ Fast | Low |
<!-- ConstraintLayout - flat hierarchy --><androidx.constraintlayout.widget.ConstraintLayout> <ImageView android:id="@+id/avatar" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/>
<TextView android:id="@+id/name" app:layout_constraintStart_toEndOf="@id/avatar" app:layout_constraintTop_toTopOf="@id/avatar"/>
<TextView android:id="@+id/email" app:layout_constraintStart_toStartOf="@id/name" app:layout_constraintTop_toBottomOf="@id/name"/></androidx.constraintlayout.widget.ConstraintLayout>ConstraintLayout features:
- Chains, barriers, guidelines
- Percentage sizing
- Circular positioning
- MotionLayout animations
Q: View measure specs?
Phần tiêu đề “Q: View measure specs?”Trả lời:
MeasureSpec là 32-bit int chứa mode (2 bits) và size (30 bits).
| Mode | Description | Example |
|---|---|---|
EXACTLY |
Exact size required | 100dp, match_parent |
AT_MOST |
Max size limit | wrap_content with limit |
UNSPECIFIED |
No constraints | ScrollView children |
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val widthMode = MeasureSpec.getMode(widthMeasureSpec) val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val desiredWidth = 200.dpToPx()
val width = when (widthMode) { MeasureSpec.EXACTLY -> widthSize MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize) else -> desiredWidth }
setMeasuredDimension(width, calculatedHeight)}6. Animations
Phần tiêu đề “6. Animations”Q: Animation types trong Android?
Phần tiêu đề “Q: Animation types trong Android?”Trả lời:
| Type | Use Case | API |
|---|---|---|
| Property Animation | Any property | ObjectAnimator, ValueAnimator |
| View Animation | Simple transforms | XML, Animation class |
| Transition | Layout changes | TransitionManager |
| MotionLayout | Complex gestures | MotionScene |
// ObjectAnimatorObjectAnimator.ofFloat(view, "translationX", 0f, 100f).apply { duration = 300 interpolator = AccelerateDecelerateInterpolator() start()}
// ViewPropertyAnimator (simpler)view.animate() .translationX(100f) .alpha(0.5f) .setDuration(300) .setInterpolator(AccelerateDecelerateInterpolator()) .start()
// TransitionTransitionManager.beginDelayedTransition(container, Fade())view.visibility = View.GONE // Animates automatically
// MotionLayoutmotionLayout.transitionToEnd()motionLayout.setTransitionDuration(500)📝 View Best Practices
Phần tiêu đề “📝 View Best Practices”| Practice | Why |
|---|---|
| Keep hierarchy flat | Faster measure/layout |
| Use ConstraintLayout | Single-pass layout |
| Recycle ViewHolders | Memory efficiency |
| Use DiffUtil | Minimal updates |
Avoid requestLayout() in loops |
Expensive |
Use invalidate() for redraw |
Not full relayout |
Clean up in onDetachedFromWindow() |
Prevent leaks |
// ❌ Bad - deep hierarchyLinearLayout > LinearLayout > LinearLayout > TextView
// ✅ Good - flat with ConstraintLayoutConstraintLayout > TextView, TextView, TextView📚 Tìm hiểu thêm: AndroidView trong Compose