You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Java/Kotlin创建含放射状按钮布局的Android RecyclerView/ListView

实现Android放射状按钮布局(含中央控制台)

一、整体布局结构

用ConstraintLayout作为父容器,将中央控制台(如搜索按钮)固定在布局中心,再叠加RecyclerView,通过内边距让RecyclerView的子View避开中央区域,实现放射状按钮环绕中心控制台的效果。

示例布局代码(XML):

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 中央控制台区域 -->
    <LinearLayout
        android:id="@+id/central_control"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:background="#EEEEEE"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <Button
            android:id="@+id/search_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="搜索" />
    </LinearLayout>

    <!-- 放射状按钮的RecyclerView -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/radiant_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:padding="60dp" /> <!-- 内边距匹配中央控制台的半宽,避免按钮覆盖 -->

</androidx.constraintlayout.widget.ConstraintLayout>

二、自定义放射状LayoutManager

RecyclerView默认的LayoutManager不支持放射状排列,需要自定义实现核心布局逻辑,以下是Kotlin版本:

class RadiantLayoutManager : RecyclerView.LayoutManager() {
    private val centerX get() = width / 2f
    private val centerY get() = height / 2f
    // 中心到按钮的距离(可根据需求调整)
    private val radius = 200f

    override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return RecyclerView.LayoutParams(
            RecyclerView.LayoutParams.WRAP_CONTENT,
            RecyclerView.LayoutParams.WRAP_CONTENT
        )
    }

    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
        if (itemCount == 0) {
            detachAndScrapAttachedViews(recycler)
            return
        }
        detachAndScrapAttachedViews(recycler)

        // 计算每个按钮的角度间隔
        val angleStep = 360f / itemCount

        for (i in 0 until itemCount) {
            val view = recycler.getViewForPosition(i)
            addView(view)
            measureChildWithMargins(view, 0, 0)

            // 转换角度为弧度,计算按钮中心坐标
            val angle = Math.toRadians((i * angleStep).toDouble()).toFloat()
            val viewCenterX = centerX + radius * cos(angle)
            val viewCenterY = centerY + radius * sin(angle)

            // 计算按钮的左上右下坐标
            val left = (viewCenterX - view.measuredWidth / 2f).toInt()
            val top = (viewCenterY - view.measuredHeight / 2f).toInt()
            val right = left + view.measuredWidth
            val bottom = top + view.measuredHeight

            layoutDecorated(view, left, top, right, bottom)
        }
    }

    // 放射状布局无需滚动,禁止RecyclerView滚动
    override fun canScrollVertically(): Boolean = false
    override fun canScrollHorizontally(): Boolean = false
}

三、RecyclerView适配器

常规适配器实现按钮数据绑定,Kotlin示例:

class RadiantButtonAdapter(private val buttonTexts: List<String>) :
    RecyclerView.Adapter<RadiantButtonAdapter.ButtonViewHolder>() {

    inner class ButtonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val btn: Button = itemView.findViewById(R.id.sr_btn)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ButtonViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_radiant_button, parent, false)
        return ButtonViewHolder(view)
    }

    override fun onBindViewHolder(holder: ButtonViewHolder, position: Int) {
        holder.btn.text = buttonTexts[position]
        holder.btn.setOnClickListener {
            // 自定义按钮点击逻辑
        }
    }

    override fun getItemCount() = buttonTexts.size
}

对应按钮item布局item_radiant_button.xml:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sr_btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="SR"
    android:backgroundTint="#2196F3"
    android:textColor="#FFFFFF" />

四、在Activity中初始化

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val recyclerView = findViewById<RecyclerView>(R.id.radiant_recycler)
        recyclerView.layoutManager = RadiantLayoutManager()
        // 示例按钮数据,可根据业务需求动态生成
        val buttonTexts = listOf("SR1", "SR2", "SR3", "SR4", "SR5", "SR6")
        recyclerView.adapter = RadiantButtonAdapter(buttonTexts)

        // 中央搜索按钮点击事件
        findViewById<Button>(R.id.search_btn).setOnClickListener {
            // 自定义搜索逻辑
        }
    }
}

五、Java版本核心代码(可选)

自定义LayoutManager的Java实现:

public class RadiantLayoutManager extends RecyclerView.LayoutManager {
    private float getCenterX() {
        return getWidth() / 2f;
    }

    private float getCenterY() {
        return getHeight() / 2f;
    }

    private final float radius = 200f;

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(
                RecyclerView.LayoutParams.WRAP_CONTENT,
                RecyclerView.LayoutParams.WRAP_CONTENT
        );
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() == 0) {
            detachAndScrapAttachedViews(recycler);
            return;
        }
        detachAndScrapAttachedViews(recycler);

        float angleStep = 360f / getItemCount();

        for (int i = 0; i < getItemCount(); i++) {
            View view = recycler.getViewForPosition(i);
            addView(view);
            measureChildWithMargins(view, 0, 0);

            float angle = (float) Math.toRadians(i * angleStep);
            float viewCenterX = getCenterX() + radius * (float) Math.cos(angle);
            float viewCenterY = getCenterY() + radius * (float) Math.sin(angle);

            int left = (int) (viewCenterX - view.getMeasuredWidth() / 2f);
            int top = (int) (viewCenterY - view.getMeasuredHeight() / 2f);
            int right = left + view.getMeasuredWidth();
            int bottom = top + view.getMeasuredHeight();

            layoutDecorated(view, left, top, right, bottom);
        }
    }

    @Override
    public boolean canScrollVertically() {
        return false;
    }

    @Override
    public boolean canScrollHorizontally() {
        return false;
    }
}

适配器与Activity的Java实现逻辑与Kotlin一致,仅需语法转换即可。


内容的提问来源于stack exchange,提问作者Jafar Sadiq SH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 17:27:50