4

I am working on an android project where I have a custom view. When the custom view is clicked, I want a to put a view (a circle) at each corner of the view.

At the moment I'm just trying to get it work in the top left corner but it ends up in the middle.

Below is my click function for adding the view.

View view = LayoutInflater.from(getContext()).inflate(R.layout.view, this, false);

        TextView textItem = view.findViewById(R.id.lblItemText);

        textItem.setText("View: " + counter);

        view.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v)
            {

                Anchor anchor1 = new Anchor(getContext());

                anchor1.setLeft(v.getLeft());
                anchor1.setTop(CustomView.this.getTop());
                CustomView.this.addView(anchor1);
            }
        });

The custom view is hosted inside a relative layout. The custom view extends RelativeLayout and the anchor view which is supposed to go into the top left corner of the custom view extends button.

The anchor constructor contains the following:

public Anchor(Context context)
    {
        super(context);
        this.setBackgroundResource(R.drawable.anchor);
        this.setPadding(0,0,0,0);
        this.setWidth(1);
        this.setHeight(1);
}

For some reason the anchor is appearing in the middle instead of being on the corner as shown below

What the end result is

Below is kind of expecting.

Expected view

UPDATE

After a couple of days made some progress and I do have it working, except its using hardcoded values to get it in the right position, which doesn't seem right. I'm guessing this will only work on the specific device I'm testing on, another device with another resolution will be positioned wrong.

Below is the code I have that hopefully shows what is I am trying to achieve along with a screenshot as to what I have now.

private void createAnchorPoints()
    {

        //Main View
        ViewGroup mainView = activity.findViewById(android.R.id.content);

        int[] viewToBeResizedLoc = new int[2];
        viewToBeResized.getLocationOnScreen(viewToBeResizedLoc);


        //Add top left anchor
        Anchor topLeftAnchor = new Anchor(context, Anchor.ResizeMode.TOP_LEFT);
        FrameLayout.LayoutParams topLeftParms = new FrameLayout.LayoutParams(150,150);
        topLeftParms.leftMargin = viewToBeResizedLoc[0] - 50;
        topLeftParms.topMargin = viewToBeResizedLoc[1] - viewToBeResized.getHeight() - 30;
        topLeftAnchor.setLayoutParams(topLeftParms);
        mainView.addView(topLeftAnchor);

        //Add top right anchor
        Anchor topRightAnchor = new Anchor(context, Anchor.ResizeMode.TOP_RIGHT);
        FrameLayout.LayoutParams topRightParms = new FrameLayout.LayoutParams(150, 150);
        topRightParms.leftMargin = topLeftParms.leftMargin + viewToBeResized.getWidth() - 40;
        topRightParms.topMargin = topLeftParms.topMargin;
        topRightAnchor.setLayoutParams(topRightParms);
        mainView.addView(topRightAnchor);

        //Add bottom left anchor
        Anchor bottomLeftAnchor = new Anchor(context, Anchor.ResizeMode.BOTTOM_LEFT);
        FrameLayout.LayoutParams bottomLeftParms = new FrameLayout.LayoutParams(150, 150);
        bottomLeftParms.leftMargin = topLeftParms.leftMargin;
        bottomLeftParms.topMargin = topLeftParms.topMargin + viewToBeResized.getHeight() - 40;
        bottomLeftAnchor.setLayoutParams(bottomLeftParms);
        mainView.addView(bottomLeftAnchor);

        //Add bottom right anchor
        Anchor bottomRightAnchor = new Anchor(context, Anchor.ResizeMode.BOTTOM_RIGHT);
        FrameLayout.LayoutParams bottomRightParms = new FrameLayout.LayoutParams(150, 150);
        bottomRightParms.leftMargin = topRightParms.leftMargin;
        bottomRightParms.topMargin = bottomLeftParms.topMargin;
        bottomRightAnchor.setLayoutParams(bottomRightParms);
        mainView.addView(bottomRightAnchor);

    }

Current example after update

Boardy
  • 35,417
  • 104
  • 256
  • 447
  • 1
    `getLeft()`, `getTop()` etc returns the coordinate of a view relative to its parent, i.e. the coordinate system is "shifted". Most likely you should know coordinates of the views relative to the root view and do the calculation w.t.r. A bit of extra explanation is [here](https://stackoverflow.com/a/47317233/3290339) – Onik Jun 22 '18 at 21:02

1 Answers1

3

Since the top-level layout is a RelativeLayout, you will need to use the view positioning that is available to RelativeLayout to achieve what you want. (See the documentation.)

Here is a mock-up of what you want to achieve in XML. This mock-up will demonstrate how we can approach the actual solution. I am using standard views, but it shouldn't matter. The technique will apply to your custom views. The image is from Android Studio's designer, so no code was used to create the image.

enter image description here

activity_main.xml

<RelativeLayout 
    android:id="@+id/relativeLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <RelativeLayout
        android:id="@+id/customView"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerInParent="true"
        android:background="@android:color/holo_green_light" />

    <ImageView
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_alignStart="@id/customView"
        android:layout_alignTop="@id/customView"
        android:src="@drawable/circle"
        android:translationX="-10dp"
        android:translationY="-10dp" />

    <ImageView
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_alignEnd="@id/customView"
        android:layout_alignTop="@id/customView"
        android:src="@drawable/circle"
        android:translationX="10dp"
        android:translationY="-10dp" />

    <ImageView
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_alignBottom="@id/customView"
        android:layout_alignStart="@id/customView"
        android:src="@drawable/circle"
        android:translationX="-10dp"
        android:translationY="10dp" />

    <ImageView
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_alignBottom="@id/customView"
        android:layout_alignEnd="@id/customView"
        android:src="@drawable/circle"
        android:translationX="10dp"
        android:translationY="10dp" />

</RelativeLayout>

circle.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <!-- fill color -->
    <solid android:color="@android:color/holo_red_light" />
    <size
        android:width="20dp"
        android:height="20dp" />
</shape>

The Actual Solution

Now that we have demonstrated that the mocked-up approach works, we now have to reproduce the effect in code. We will have to add the circle view and position it within the parent RelativeLayout using RelativeLayout view positioning and translations. The following code shows just the top left circle positioned, but the other circles will be positioned in a similar way.

activity_main.java

public class MainActivity extends AppCompatActivity {

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

        Drawable circle = ContextCompat.getDrawable(this, R.drawable.circle);
        ImageView imageView = new ImageView(this);
        imageView.setImageDrawable(circle);
        int circleSize = dpToPx(CIRCLE_SIZE_DP);
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(circleSize, circleSize);

        // Position top left circle within the custom view.
        lp.addRule(RelativeLayout.ALIGN_START, R.id.customView);
        lp.addRule(RelativeLayout.ALIGN_TOP, R.id.customView);

        // Uncomment these 2 lines to position the top left circle with translation.
        imageView.setTranslationX(-circleSize / 2);
        imageView.setTranslationY(-circleSize / 2);

        // Uncomment these 3 lines to position the top left circle with margins.
//        View customView = findViewById(R.id.customView);
//        lp.leftMargin = customView.getLeft() - circleSize / 2;
//        lp.topMargin = customView.getTop() - circleSize / 2;

        ((RelativeLayout) findViewById(R.id.relativeLayout)).addView(imageView, lp);
    }

    private int dpToPx(int dp) {
        return (int) (dp * getResources().getDisplayMetrics().density);
    }

    private static final int CIRCLE_SIZE_DP = 20;
}

The code above uses a shortened layout:

activity_main.xml

<RelativeLayout 
    android:id="@+id/relativeLayout"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <RelativeLayout
        android:id="@+id/customView"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerInParent="true"
        android:background="@android:color/holo_green_light" />

</RelativeLayout>

It is also possible to produce the same positioning using margins. The code to use margins is commented out but will work. (I think that negative margins may also work, but I have read that they are not officially supported, so I try to avoid them.)

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • Thanks. I'm not sure this is what I'm after. As the anchors on the corner shouldn't be available by default which this looks like your example they'll always be there. What I'm planning is having a class, which takes any view in the constructor, and the class will then add the 4 anchor points at each corner when the class is instantiated. – Boardy Jun 27 '18 at 17:43
  • @Boardy I am not following you. The line `ImageView imageView = new ImageView(this)` creates the anchor in code so it is not there by default. The only thing present is the parent `RelativeLayout` and the custom view. Given the parent layout, the custom view could be added so you would just need to define the parent group view and even that could be created in code. Look at the second part of the answer and not just the first which does define the anchor points. – Cheticamp Jun 27 '18 at 17:51
  • Ah apologise, I was being thick. I misread the initial example of how to do it in XML and thought that was part of the solution for what I was after. This has done what I'm after. Thanks for your help – Boardy Jun 27 '18 at 18:59
  • @Boardy No problem. I'll update the answer to make the actual solution stand out a little more. – Cheticamp Jun 27 '18 at 21:12