RecyclerView scale center item

1) Đặt vấn đề

Giả sử khách hàng chúng ta có 1 list các ảnh và yêu cầu list này sẽ được hiển thị ra màn hình và item trung tâm được scale hơn so với các item bên cạnh kiểu như sau:

2) Giải pháp

Có nhiều cách để có thể làm được như trên, trong bài viết này mình sẽ hướng dẫn các bạn sử dụng RecyclerView để giải quyết vấn đề này

Như chúng ta thấy thì item ở giữa sẽ to nhất (chính tâm) và ra xa tâm thì item sẽ nhỏ đi.

Như vậy thì độ lớn của các item sẽ tỷ lệ nghịch với khoảng cách giữa item đó và tâm của recyclerView

class ZoomCenterLinearLayoutManager extends LinearLayoutManager {
    public ZoomCenterLinearLayoutManager(Context context) {
        super(context);
    }

    public ZoomCenterLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public ZoomCenterLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
        lp.width = getWidth() / 3; //Độ rộng của mỗi item sẽ bằng 1/3 độ rộng của RecyclerView
        return true;
    }

    @Override
    public void onLayoutCompleted(RecyclerView.State state) {
        super.onLayoutCompleted(state);
        scaleMiddle();
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        scaleMiddle();
        return super.scrollHorizontallyBy(dx, recycler, state);
    }

    public void scaleMiddle() {
        float midpoint = getWidth() / 2.f;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            float childMidpoint = (getDecoratedRight(child) + getDecoratedLeft(child)) / 2.f; // tính hoành độ của item
            float d = Math.abs(midpoint - childMidpoint); // khoảng cách giữa item và điểm trung tâm của recyclerView
            float scale = 1f - 0.35f * d / midpoint; //tính độ scale của item (0/35: Số này càng lớn thì các item 2 bên sẽ càng nhỏ
            child.setScaleX(scale);
            child.setScaleY(scale);
        }
    }
}

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/text_name"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintDimensionRatio="1:1"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

item_empty.xml (Do chúng ta cần 1 khoảng trống ở đầu và cuối để hiển thị scale item đầu tiên và cuối cùng)

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/text_name"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintDimensionRatio="1:1"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
package com.example.recyclerviewzoomcenter;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.recyclerviewzoomcenter.databinding.ItemEmptyBinding;
import com.example.recyclerviewzoomcenter.databinding.ItemRecyclerBinding;

import java.util.List;

import static com.example.recyclerviewzoomcenter.ItemAdapter.ViewType.EMPTY;
import static com.example.recyclerviewzoomcenter.ItemAdapter.ViewType.NORMAL;

public class ItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<Data> dataList;

    public ItemAdapter(List<Data> dataList) {
        this.dataList = dataList;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 || position == dataList.size() - 1) {
            return EMPTY;
        }
        return NORMAL;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == EMPTY) {
            ItemEmptyBinding binding = ItemEmptyBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
            return new EmptyViewHolder(binding);
        }
        ItemRecyclerBinding binding = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        return new ItemViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof ItemViewHolder) {
            ((ItemViewHolder) holder).bindData(dataList.get(holder.getAdapterPosition()));
        }
    }

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

    static class ItemViewHolder extends RecyclerView.ViewHolder {
        private ItemRecyclerBinding binding;

        public ItemViewHolder(@NonNull ItemRecyclerBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        private void bindData(Data data) {
            binding.imageView.setImageResource(data.getImageId());
            binding.textName.setText(data.getName());
        }
    }

    static class EmptyViewHolder extends RecyclerView.ViewHolder {

        public EmptyViewHolder(@NonNull ItemEmptyBinding binding) {
            super(binding.getRoot());
        }
    }

    @IntDef({EMPTY, NORMAL})
    public @interface ViewType {
        int EMPTY = 0;
        int NORMAL = 1;
    }
}

package com.example.recyclerviewzoomcenter;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSnapHelper;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import static com.example.recyclerviewzoomcenter.ItemAdapter.ViewType.EMPTY;
import static com.example.recyclerviewzoomcenter.ItemAdapter.ViewType.NORMAL;

public class MainActivity extends AppCompatActivity {
    private String[] names = {"A", "B", "C"};
    private List<Data> dataList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        for (int i = 0; i < 10; i++) {
            Random random = new Random();
            Data data = new Data(names[random.nextInt(3)], R.drawable.image_1, NORMAL);
            dataList.add(data);
        }
        dataList.add(0, new Data("", 0, EMPTY));
        dataList.add(new Data("", 0, EMPTY));
        ItemAdapter adapter = new ItemAdapter(dataList);
        recyclerView.setLayoutManager(new ZoomCenterLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        recyclerView.setAdapter(adapter);
        LinearSnapHelper snapHelper = new LinearSnapHelper(); // Sử dụng SnapHelper để RecyclerView luôn focus vào 1 item (snap position)
        snapHelper.attachToRecyclerView(recyclerView);
    }
}

Như vậy chúng ta đã giải quyết xong vấn đề trên rồi đó ^^


All Rights Reserved