'Android how to create simple custom UI elements

I would like to create simple custom UI elements in Android like the ones from the screenshot:

enter image description here

The light bulb should always have the same size but the rectangle should vary in the width. One option of doing this is to use Canvas elements. But I would like to ask whether there is also an easier approach for doing this. Is it possible to maybe only do this by using XML files? I would like to use these UI elements then in the LayoutEditor like e.g. a TextView where I can adjust the widht and height either in the XML layout file or programmatically.

Any idea how I can do that in an easy way?

Update: I tried the suggested approach from Cheticamp and I have the following code inside my Fragment:

public class Test extends Fragment implements Runnable {

    /*
    Game variables
     */

    public static final int DELAY_MILLIS = 100;
    public static final int TIME_OF_A_LEVEL_IN_SECONDS = 90;
    private int currentTimeLeftInTheLevel_MILLIS;
    private Handler handler = new Handler();
    private FragmentGameBinding binding;

    private boolean viewHasBeenCreated = false;


    public Test() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentGameBinding.inflate(inflater, container, false);
        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        container.getContext();
        viewHasBeenCreated = true;
        startRound();
        return binding.getRoot();


    }

    public void startRound () {
        currentTimeLeftInTheLevel_MILLIS =TIME_OF_A_LEVEL_IN_SECONDS * 1000;
        updateScreen();
        handler.postDelayed(this, 1000);

    }
    private void updateScreen() {
        binding.textViewTimeLeftValue.setText("" + currentTimeLeftInTheLevel_MILLIS/1000);

        /*
        IMPORTANT PART: This should create a simple custom UI element but it creates an error
         */
        View view = new View(getActivity());
        view.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
        Drawable dr = ContextCompat.getDrawable(getActivity(),R.drawable.light_bulb_layer_list);
        view.setBackground(dr);

        ConstraintLayout constraintLayout = binding.constraintLayout;
        ConstraintSet constraintSet = new ConstraintSet();
        constraintSet.clone(constraintLayout);
        constraintSet.connect(view.getId(),ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM,0);
        constraintSet.connect(view.getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID ,ConstraintSet.TOP,0);
        constraintSet.connect(view.getId(),ConstraintSet.LEFT,ConstraintSet.PARENT_ID ,ConstraintSet.LEFT,0);
        constraintSet.connect(view.getId(),ConstraintSet.RIGHT,ConstraintSet.PARENT_ID ,ConstraintSet.RIGHT,0);
        constraintSet.setHorizontalBias(view.getId(), 0.16f);
        constraintSet.setVerticalBias(view.getId(), 0.26f);
        constraintSet.applyTo(constraintLayout);
    }

    private void countDownTime(){
        currentTimeLeftInTheLevel_MILLIS = currentTimeLeftInTheLevel_MILLIS -DELAY_MILLIS;
        updateScreen();
    }

    @Override
    public void run() {
        if(viewHasBeenCreated) {
            countDownTime();
        }
}
}

Unfortunately, this code leads to a "java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.Context.isUiContext()' on a null object reference". It is thrown by the line View view = new View(getActivity());. Here is the complete error message:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.game, PID: 12176
    java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.Context.isUiContext()' on a null object reference
        at android.view.ViewConfiguration.get(ViewConfiguration.java:502)
        at android.view.View.<init>(View.java:5317)
        at com.example.game.Test.updateScreen(Test.java:72)
        at com.example.game.Test.countDownTime(Test.java:91)
        at com.example.game.Test.run(Test.java:97)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

Any idea what the problem is? Without the custom UI element the Fragment works fine.



Solution 1:[1]

Sure thing.

In this case a simple xml file like so would suffice. Let's name it something.xml inside the layout folder.

<LinearLayout ...>
  <ImageView ...>
</LinearLayout>

In another layout xml file you may just:

<ConstraintLayout ...>
  <include android:id="@+id/something"" layout="@layout/something" android:layout_width="70dp">
</ConstraintLayout>

See Reusing layouts

If you'd like to get a children you can always get them by using findViewById on your Activity or Fragment. If you're using Databinding or Viewbinding it just gets better: They'll appear as fields in the XBinding class that was generated out of the XML file


Hi VanessaF, going a little bit further with the clarifications you asked in the comments:

<include />

The <include /> tag is a special XML tag that we can use in our Android XML layout files to indicate that where we placed the <include/> we'd like it to be replaced by some other XML determined via the layout attribute inside the <include /> tag.

Here's an example:

Considering layout/example.xml

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

And considering layout/parent.xml

<LinearLayout 
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical">

  <Button .../>
  <include layout="@layout/example"/>
  <ImageView android:drawable="@drawable/ic_send"/>
</LinearLayout>

Whenever I use R.layout.parent somewhere (for example in setContent from the Activity the view that would get generated would be as follows:

<LinearLayout 
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical">

  <Button .../>
  <!-- PLEASE NOTICE THAT <include/> IS GONE -->
  <!-- AND HAS BEEN REPLACED WITH THE CONTENTS the specified layout -->
  <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Hello!"/>
  <ImageView android:drawable="@drawable/ic_send"/>
</LinearLayout>

Effectively re-using the layout without writing a full-blown custom view.

Notice: All attributes you specify inside the <include/> tag will effectively override the others specified inside the layout file. Let me illustrate this using an example:

Consider again layout/example.xml. Notice that this time the TextView will shrink to the size of the text both in height and width.

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

And consider the parent: layout/parent.xml. Notice that I am setting the attributes android:layout_width and android:layout_height.

<LinearLayout 
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical">
  <include
    layout="@layout/example"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />
</LinearLayout>

In this case, when Android replaces <include/> for the contents of @layout/example it will also set android:layout_width="match_parent" and android:layout_height="match_parent" because they were specified on the <include/> tag effectively ignoring the original attributes set inside layout/example.xml (which were set to "wrap_content")

Solution 2:[2]

I suggest reading Custom View Components from the official Android documentation. In fact, you should become very familiar with this documentation for everything you do with Android apps.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Code-Apprentice