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

Cách highlight (tô sáng) item được chọn trên RecyclerView Android

Giới thiệu

Ví dụ này minh họa cách.highlight (tô sáng) item được chọn trên RecyclerView một cách đúng đắn, sử dụng SparseBooleanArray để theo dõi trạng thái chọn và StateListDrawable (selector) cho background.

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. Thêm dependency RecyclerView:

dependencies {
    implementation "androidx.recyclerview:recyclerview:1.3.2"
}

Bước 2: Giao diện chính (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:padding="4dp"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="8dp" />

</RelativeLayout>

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

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/background_selector"
    android:padding="16dp">

    <TextView
        android:id="@+id/awardTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="Award Title"
        android:textSize="18sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/awardYear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/awardTitle"
        android:layout_alignStart="@id/awardTitle"
        android:layout_marginTop="20dp"
        android:text="Award Year"
        android:textColor="#000" />

    <TextView
        android:id="@+id/playerName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:text="Player Name" />

</RelativeLayout>

Bước 4: Selector background (res/drawable/background_selector.xml)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="https://schemas.android.com/apk/res/android">
    <!-- Item được chọn (selected) -->
    <item android:drawable="@color/colorAccent" android:state_selected="true" />
    <!-- Item bình thường -->
    <item android:drawable="@android:color/white" android:state_selected="false" />
</selector>

Bước 5: Divider (res/drawable/line_divider.xml)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="https://schemas.android.com/apk/res/android" android:shape="rectangle">
    <size android:width="2dp" android:height="2dp" />
    <solid android:color="@color/colorPrimaryDark" />
</shape>

Bước 6: Model Item (ItemObject.java)

package com.app.sample;

public class ItemObject {
    private String awardTitle;
    private String awardYear;
    private String player;

    public ItemObject(String awardTitle, String awardYear, String player) {
        this.awardTitle = awardTitle;
        this.awardYear = awardYear;
        this.player = player;
    }

    public String getAwardTitle() { return awardTitle; }
    public String getAwardYear() { return awardYear; }
    public String getPlayer() { return player; }
}

Bước 7: RecyclerViewAdapter (RecyclerViewAdapter.java)

package com.app.sample;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewHolders> {
    private List<ItemObject> itemList;

    public RecyclerViewAdapter(Context context, List<ItemObject> itemList) {
        this.itemList = itemList;
    }

    @NonNull
    @Override
    public RecyclerViewHolders onCreateViewHolder(ViewGroup parent, int viewType) {
        View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_layout, parent, false);
        return new RecyclerViewHolders(layoutView);
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolders holder, int position) {
        ItemObject item = itemList.get(position);
        holder.awardTitle.setText("Award Title: " + item.getAwardTitle());
        holder.awardYear.setText("Award Year: " + item.getAwardYear());
        holder.player.setText("Player Name: " + item.getPlayer());
    }

    @Override
    public int getItemCount() {
        return itemList.size();
    }
}

Bước 8: ViewHolder xử lý click (RecyclerViewHolders.java)

package com.app.sample;

import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;

class RecyclerViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener {
    TextView awardTitle, awardYear, player;
    private SparseBooleanArray selectedItems = new SparseBooleanArray();

    RecyclerViewHolders(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
        awardTitle = itemView.findViewById(R.id.awardTitle);
        awardYear = itemView.findViewById(R.id.awardYear);
        player = itemView.findViewById(R.id.playerName);
    }

    @Override
    public void onClick(View view) {
        int pos = getAdapterPosition();
        if (selectedItems.get(pos, false)) {
            selectedItems.delete(pos);
            view.setSelected(false);
        } else {
            selectedItems.put(pos, true);
            view.setSelected(true);
        }
    }
}

Bước 9: ItemDecoration (SimpleItemDecoration.java)

package com.app.sample;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;

class SimpleItemDecoration extends RecyclerView.ItemDecoration {
    private Drawable drawable;

    SimpleItemDecoration(Context context) {
        drawable = ContextCompat.getDrawable(context, R.drawable.line_divider);
    }

    @Override
    public void onDrawOver(@NonNull Canvas canvas, RecyclerView parent, @NonNull RecyclerView.State state) {
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int childCount = parent.getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            int top = child.getBottom() + params.bottomMargin;
            int bottom = top + drawable.getIntrinsicHeight();
            drawable.setBounds(left, top, right, bottom);
            drawable.draw(canvas);
        }
    }
}

Bước 10: MainActivity (src/MainActivity.java)

package com.app.sample;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.addItemDecoration(new SimpleItemDecoration(this));
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        List<ItemObject> posts = returnListItems();
        RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, posts);
        recyclerView.setAdapter(adapter);
    }

    private List<ItemObject> returnListItems() {
        List<ItemObject> items = new ArrayList<>();
        items.add(new ItemObject("Ballon d'or", "2007", "Ricardo KaKa"));
        items.add(new ItemObject("Ballon d'or", "2008", "Cristiano Ronaldo"));
        items.add(new ItemObject("Ballon d'or", "2009 - 2012, 2015", "Lionel Messi"));
        items.add(new ItemObject("Ballon d'or", "2013, 2014, 2016, 2017", "Cristiano Ronaldo"));
        items.add(new ItemObject("Ballon d'or", "2018", "Luca Modric"));
        items.add(new ItemObject("Ballon d'or", "2019", "Chưa quyết định"));
        return items;
    }
}

Bước 11: 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">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Nhấn vào các item để chọn/bỏ chọn, background sẽ đổi màu theo trạng thái.

Cách highlight (tô sáng) item được chọn trên RecyclerView Android

Cải tiến: Chế độ chọn đơn (Single Choice) vs chọn nhiều (Multi Choice)

Code hiện tại cho phép chọn nhiều item (Multi Choice). Để chỉ cho chọn 1 item (Single Choice), sửa ViewHolder:

// Trong Adapter hoặc ViewHolder
private int selectedPosition = RecyclerView.NO_POSITION;

@Override
public void onClick(View view) {
    int pos = getAdapterPosition();
    int oldSelected = selectedPosition;
    selectedPosition = (selectedPosition == pos) ? RecyclerView.NO_POSITION : pos;
    notifyItemChanged(oldSelected);
    notifyItemChanged(selectedPosition);
}

// Trong onBindViewHolder
holder.itemView.setSelected(position == selectedPosition);

Best Practices

  • Dùng SparseBooleanArray thay vì HashMap<Integer, Boolean> để tiết kiệm bộ nhớ.
  • Luôn cập nhật trạng thái view.setSelected() trong onBindViewHolder để khôi phục đúng khi recycle.
  • Kết hợp với ItemTouchHelper nếu cần drag-drop hoặc swipe-to-dismiss.