Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Tạo EditText có focus trong ListView trên Android

Giới thiệu

Bài viết này hướng dẫn cách tạo EditText có thể focus và nhập liệu bên trong ListView trên Android. Vấn đề chính: ListView mặc định không cho phép child view focus được (để cuộn mượt), cần cấu hình descendantFocusability và quản lý focus thủ công.

Các bước thực hiện

Bước 1: Tạo dự án mới

Mở Android Studio, chọn File → New Project và điền thông tin cần thiết.

Bước 2: Layout chính (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="4dp"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:descendantFocusability="beforeDescendants" />

</LinearLayout>

Bước 3: Layout item (res/layout/listitem.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp">

    <EditText
        android:id="@+id/itemCaption"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:hint="Nhập nội dung..."
        android:inputType="text"
        android:singleLine="true" />

</LinearLayout>

Bước 4: Adapter tùy chỉnh (MainActivity.java)

import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ListView listView;
    private MyAdapter myAdapter;
    private List items = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = findViewById(R.id.listView);
        
        // Quan trọng: Cho phép item focus
        listView.setItemsCanFocus(true);
        
        // Dữ liệu mẫu
        for (int i = 0; i < 7; i++) {
            items.add("Caption " + i);
        }

        myAdapter = new MyAdapter();
        listView.setAdapter(myAdapter);
    }

    private class MyAdapter extends BaseAdapter {

        private final LayoutInflater inflater;

        MyAdapter() {
            inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        @Override
        public int getCount() {
            return items.size();
        }

        @Override
        public Object getItem(int position) {
            return items.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @SuppressLint("InflateParams")
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            
            if (convertView == null) {
                convertView = inflater.inflate(R.layout.listitem, parent, false);
                holder = new ViewHolder();
                holder.caption = convertView.findViewById(R.id.itemCaption);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            // Set data
            holder.caption.setText(items.get(position));
            holder.caption.setTag(position); // Lưu position để dùng trong listener

            // Xử lý khi mất focus -> lưu dữ liệu
            holder.caption.setOnFocusChangeListener((v, hasFocus) -> {
                if (!hasFocus) {
                    int pos = (int) v.getTag();
                    String text = ((EditText) v).getText().toString();
                    items.set(pos, text);
                    // TODO: Lưu vào database, SharedPreferences, hoặc notifyDataSetChanged()
                }
            });

            return convertView;
        }

        static class ViewHolder {
            EditText caption;
        }
    }
}

Bước 5: Cấu hình Manifest (androidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity 
            android:name=".MainActivity"
            android:windowSoftInputMode="adjustPan|adjustResize">
            <intent-filter>
                
                
            </intent-filter>
        </activity>

    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Chạm vào EditText bất kỳ để focus, nhập text, chạm ra ngoài hoặc cuộn để mất focus - dữ liệu sẽ được lưu vào list.

Giải thích các thuộc tính quan trọng

Thuộc tính Giá trị Chức năng
android:descendantFocusability beforeDescendants Cho phép child view nhận focus trước khi ListView xử lý touch
android:descendantFocusability afterDescendants ListView xử lý touch trước, child nhận focus sau (mặc định)
android:descendantFocusability blocksDescendants Chặn hoàn toàn child nhận focus (mặc định của ListView cũ)
listView.setItemsCanFocus(true) Java code Bật cho phép item focus (tương đương beforeDescendants)
android:windowSoftInputMode adjustPan|adjustResize Điều chỉnh layout khi bàn phím hiện: pan hoặc resize

Vấn đề thường gặp & Giải pháp

  • Bàn phím che EditText: Dùng adjustResize trong Manifest, hoặc android:windowSoftInputMode="adjustPan".
  • Mất dữ liệu khi cuộn (recycle view): Luôn lưu data trong onFocusChange(hasFocus=false) hoặc TextWatcher.
  • Focus nhảy vọt: Set android:focusableInTouchMode="true" cho EditText, và android:focusable="true".
  • ListView recycle sai position: Dùng holder.caption.setTag(position) để lấy đúng position.

Khuyên dùng: RecyclerView thay vì ListView

ListView đã lỗi thời. RecyclerView xử lý focus tốt hơn, hiệu năng cao hơn, hỗ trợ LayoutManager linh hoạt.

// RecyclerView.Adapter - ViewHolder pattern bắt buộc, view recycling tự động
// Item focus xử lý tương tự, nhưng RecyclerView có ViewHolder pattern chuẩn hơn
// Nên dùng RecyclerView cho mọi danh sách mới

Lưu ý

  • Test kỹ trên nhiều thiết bị - hành vi focus khác nhau giữa OEM.
  • Cân nhắc dùng TextWatcher (afterTextChanged) để auto-save thay vì chờ mất focus.
  • Đối với form phức tạp: dùng RecyclerView + DiffUtil + ViewBinding.