Search code examples
androidandroid-layoutandroid-relativelayout

How to align 8 little circles around of a centered big circle, like attached image shows?


I have to do this layout:enter image description here

I was trying to align the views, using RelativeLayout and layout_toRightOf, layout_below, etc, but the best that I achieved was this:

enter image description here

Here are the xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!" />

<RelativeLayout
    android:id="@+id/big"
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:background="@drawable/circular"
    android:layout_margin="10dp"
    android:layout_centerInParent="true"/>

<RelativeLayout
    android:id="@+id/right"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@drawable/circular"
    android:layout_toRightOf="@+id/big"
    android:layout_centerVertical="true"/>

<RelativeLayout
    android:id="@+id/left"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@drawable/circular"
    android:layout_toLeftOf="@+id/big"
    android:layout_centerVertical="true"/>

<RelativeLayout
    android:id="@+id/top"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@drawable/circular"
    android:layout_above="@+id/big"
    android:layout_centerHorizontal="true"/>

<RelativeLayout
    android:id="@+id/bottom"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@drawable/circular"
    android:layout_below="@+id/big"
    android:layout_centerHorizontal="true"/>

<RelativeLayout
    android:id="@+id/northeast"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@drawable/circular"
    android:layout_toRightOf="@+id/big"
    android:layout_alignTop="@+id/top"/>

<RelativeLayout
    android:id="@+id/northwest"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@drawable/circular"
    android:layout_toLeftOf="@+id/big"
    android:layout_alignTop="@+id/top"/>

<RelativeLayout
    android:id="@+id/southeast"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@drawable/circular"
    android:layout_toRightOf="@+id/big"
    android:layout_below="@+id/big"/>

    <RelativeLayout
    android:id="@+id/southwest"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@drawable/circular"
    android:layout_toLeftOf="@+id/big"
    android:layout_below="@+id/big"/>

</RelativeLayout>

I'm trying to avoid using margin on the little circles, because the diagonal circles have to be aligned exactly to the center, in comparison with the top/bottom/right/left circles.

How can I do that?


Solution

  • I show you another approach.

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.view.View;
    
    
    public class CircleMenu extends View {
    
    private Paint mainPaint;
    private Paint secondPaint;
    private Paint textPaint;
    private int radius_main =130;
    private int menuRadialButtonsCount =7;
    private int menuInnerPadding = 40;
    private int radialCircleRadius = 60;
    private int textPadding = 25;
    private double startAngle = - Math.PI/2f;;
    public CircleMenu(Context context) {
        super(context);
    }
    
    public CircleMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public CircleMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    
        mainPaint = new Paint();
        mainPaint.setColor(Color.BLUE);
        secondPaint = new Paint();
        secondPaint.setColor(Color.DKGRAY);
        textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       int centerX = canvas.getWidth()/2 ;
        int centerY= canvas.getHeight()/2;
        canvas.drawCircle(centerX,centerY,radius_main,mainPaint);
        for(int i=0;i<menuRadialButtonsCount;i++){
            double angle =0;
            if(i==0){
             angle = startAngle;
            }else{
                angle = startAngle+(i * ((2 * Math.PI) / menuRadialButtonsCount));
            }
            int x = (int) (centerX + Math.cos(angle)*(radius_main+menuInnerPadding+radialCircleRadius));
            int y = (int) (centerY + Math.sin(angle)*(radius_main+menuInnerPadding+radialCircleRadius));
    
    
            canvas.drawCircle(x,y,radialCircleRadius,secondPaint);
    
            float tW = textPaint.measureText("Text "+i);
            canvas.drawText("Text "+i,x-tW/2,y+radialCircleRadius+textPadding,textPaint);
        }
    
    
     }
    }
    

    You can extend this class, add methods to set dimmensions from resources, controlling numer of circles, their size, paddings, onTouch, shadows, colors ....

    enter image description here

     <your.package.CircleMenu
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    

    Updated version:

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    
    import java.util.ArrayList;
    
    
    public class CircleMenu extends View {
    
    public static interface IMenuListener{
    
        public void onMenuClick(MenuCircle item);
    }
    
    public static class MenuCircle{
        private int x,y,radius;
        public int id;
        public String text;
    
    }
    
    private Paint mainPaint;
    private Paint secondPaint;
    private Paint textPaint;
    private int radius_main =130;
    
    private int menuInnerPadding = 40;
    private int radialCircleRadius = 60;
    private int textPadding = 25;
    private double startAngle = - Math.PI/2f;
    private ArrayList<MenuCircle> elements;
    private IMenuListener listener;
    
    public void setListener(IMenuListener listener){
        this.listener = listener;
    }
    public void clear(){
        elements.clear();
        listener=null;
    }
    public CircleMenu(Context context) {
        super(context);
        init();
    }
    
    public CircleMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    
    public CircleMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init(){
        elements = new ArrayList<>();
    }
    public void addMenuItem(String text,int id){
        MenuCircle item = new MenuCircle();
        item.id = id;
        item.text=text;
        elements.add(item);
    
    }
    
    
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    
        mainPaint = new Paint();
        mainPaint.setColor(Color.BLUE);
        secondPaint = new Paint();
        secondPaint.setColor(Color.DKGRAY);
        textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       int centerX = canvas.getWidth()/2 ;
        int centerY= canvas.getHeight()/2;
        canvas.drawCircle(centerX,centerY,radius_main,mainPaint);
        for(int i=0;i<elements.size();i++){
            double angle =0;
            if(i==0){
                angle = startAngle;
            }else{
                angle = startAngle+(i * ((2 * Math.PI) / elements.size()));
            }
            elements.get(i).x = (int) (centerX + Math.cos(angle)*(radius_main+menuInnerPadding+radialCircleRadius));
            elements.get(i).y = (int) (centerY + Math.sin(angle)*(radius_main+menuInnerPadding+radialCircleRadius));
    
    
            canvas.drawCircle( elements.get(i).x,elements.get(i).y,radialCircleRadius,secondPaint);
    
            float tW = textPaint.measureText(elements.get(i).text);
            canvas.drawText(elements.get(i).text,elements.get(i).x-tW/2,elements.get(i).y+radialCircleRadius+textPadding,textPaint);
        }
    
    
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
        if(event.getAction()==MotionEvent.ACTION_DOWN){
            for(MenuCircle mc : elements){
                double distance =  Math.hypot(event.getX()-mc.x,event.getY()-mc.y);
                if(distance<= radialCircleRadius){
                    //touched
                    if(listener!=null)
                        listener.onMenuClick(mc);
                   return true;
                }
            }
    
        }
    
        return super.onTouchEvent(event);
    }
    
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    
    }
    }
    

    In Fragment:

      CircleMenu cm = (CircleMenu) view.findViewById(R.id.c_menu);
        cm.addMenuItem("one",1);
        cm.addMenuItem("two",2);
        cm.addMenuItem("three",3);
        cm.addMenuItem("ten",10);
        cm.addMenuItem("oh oh",156);
        cm.addMenuItem("exit",134);
        cm.setListener(new CircleMenu.IMenuListener() {
            @Override
            public void onMenuClick(CircleMenu.MenuCircle item) {
                Toast.makeText(getActivity(),item.text+" "+item.id,Toast.LENGTH_LONG).show();
            }
        });