Updated: 24 March 2023

This section describes all of the components implemented on the “Layouts” tab identified by the Layout icon .Android and ATAK UI elements that act as containers or scaffolds to organize and arrange common UI components. For a more in-depth tutorial on creating layouts, checkout the UI Layouts Tutorial.

Contents


The ATAK API provides a method to actively resize the plugin’s dropdown pane, which is shown in the LayoutsFragment.onCreateView. The provided ATAK API method callResize(double fractionWidth, double fractionHeight) can be called on the instance of the DropDownReceiver class to set the size of the pane as percentages of the total screen. There are provided fraction constants for height (FULL_HEIGHT, HALF_HEIGHT, THIRD_HEIGHT) and width (FULL_WIDTH, HALF_WIDTH, THIRD_WIDTH).

  // Fullscreen
dropDownReceiver.callResize(FULL_WIDTH, FULL_HEIGHT));
// Bottom Half
dropDownReceiver.callResize(FULL_WIDTH, HALF_HEIGHT));
// Bottom Third
dropDownReceiver.callResize(FULL_WIDTH, THIRD_HEIGHT));
// Right Half
dropDownReceiver.callResize(0.5, FULL_HEIGHT));
// Right Third
dropDownReceiver.callResize(THIRD_WIDTH, FULL_HEIGHT));
  

Additionally when it comes to determining the initial size of your plugin’s DropDownReceiver pane you will want to override the onReceive method. The following code snippet breaks down the variables of the DropDownReceiver.showDropDown method.

  public void showDropDown(
    android.view.View contentView,  // inflated plugin layout view
    double lwFraction,         // landscape-mode width fraction
    double lhFraction,         // landscape-mode height fraction 
    double pwFraction,         // portrait-mode width fraction 
    double phFraction,         // portrait=mode height fraction
    boolean ignoreBackButton,  // almost always false as this allows back navigation to close the pane
    com.atakmap.android.dropdown.DropDown.OnStateListener stateListener )
  

Examples of a full screen dropdown initial rendering can be found on the Icon2dDropDown.onReceive or Icon3dDropDown.show methods. The primary plugin dropdown pane renders at three-eights width and full height.

TOP ⇑

Layouts

Layouts are used to define the structure of a user interface in your app or in this case your ATAK plugin. It is important to be aware of the common layouts to enable you to use the appropriate one to achieve your envisioned layout design, with the goal of limiting nesting to improve rendering speeds. Some common layouts demonstrated in the hello world plugin are Linear Layouts, Relative Layouts and Constraint Layouts. Additionally there are examples of Recycler Views (a more performant List View for rendering long lists of items) and Grid Views.

Relative Layout vs Constraint Layout

Relative and constraint layouts can produce similar UI structures doing so with different properties. The Constraint Layout is more performant than Relative Layout, but is not included in the base Android packages. You will need to include the dependency in your project’s Gradle dependencies to use it in your project.

  dependencies {
	implementation ('androidx.constraintlayout:constraintlayout:2.1.4')
}
  

The following table outlines how to accomplish the same layout positioning with either Relative or Constraint Layout as the parent container.

RelativeLayoutConstraint Layout
Centering
    android:layout_centerInParent="true"
    
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    
    android:layout_centerHorizontal="true"
    
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    
    android:layout_centerVertical="true"
    
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintTop_totopOf="parent"
    
Match Item Edge to Parent Edge
android:layout_alignParentLeft="true"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_alignParentStart="true"
app:layout_constraintStart_toStartOf="parent"
android:layout_alignParentRight="true"
app:layout_constraintRight_toRightOf="parent"
android:layout_alignParentEnd="true"
app:layout_constraintEnd_toEndOf="parent"
android:layout_alignParentTop="true"
app:layout_constraintTop_toTopOf="parent"
android:layout_alignParentBottom="true"
app:layout_constraintBottom_toBottomOf="parent"
Match Item Edge to Reference Item Edge
android:layout_alignStart="@+id/view"
app:layout_constraintStart_toStartOf="@+id/view"
android:layout_alignLeft="@+id/view"
app:layout_constraintLeft_toLeftOf="@+id/view"
android:layout_alignEnd="@+id/view"
app:layout_constraintEnd_toEndOf="@+id/view"
android:layout_alignRight="@+id/view"
app:layout_constrainRight_toRightOf="@+id/view"
android:layout_alignTop="@+id/view"
app:layout_constraintTop_toTopOf="@+id/view"
android:layout_alignBaseline="@+id/view"
app:layout_constraintBaseline_toBaselineOf="@+id/view"
android:layout_alignBottom="@+id/view"
app:layout_constraintBottom_toBottomOf="@+id/view"
Align Item to Reference Item Side
android:layout_toStartOf="@+id/view"
app:layout_constraintEnd_toStartOf="@+id/view"
android:layout_toLeftOf="@+id/view"
app:layout_constraintRight_toLeftOf="@+id/view"
android:layout_toEndOf="@+id/view"
app:layout_constraintStart_toEndOf="@+id/view"
android:layout_toRightOf="@+id/view"
app:layout_constraintLeft_toRightOf="@+id/view"
android:layout_above="@+id/view"
app:layout_constraintBottom_toTopOf="@+id/view"
android:layout_below="@+id/view"
app:layout_constraintTop_toBottomOf="@+id/view"

Breakdown: Recycler View

Source Code: LayoutsFragment, DemoAdapter
Resources: tab_layouts.xml, item_user.xml
Dependencies: androidx.recyclerview:recyclerview:1.1.0

The demonstration recycler view provides the simplest and minimum components needed to understand how to initialize a RecyclerView and programmatically add it to a layout. There is a more complex and realistic example in the DynamicRecyclerView used by the the Icon2dDropDown.

  dependencies {
    // ensure to avoid duplicating androidx dependency libraries
    // provided by ATAK core for recyclerview
    testImplementation 'junit:junit:4.12'
    implementation ('androidx.recyclerview:recyclerview:1.1.0') {
        exclude module: 'collection'
        exclude module: 'core'
        exclude module: 'lifecycle'
        exclude module: 'core-common'
        exclude module: 'collection'
        exclude module: 'customview'
    }
}
  

The RecyclerView is not included in the core of Android and requires it to be listed as a dependency in the app Gradle file. It is important for to use the specific version RecyclerView and exclude duplicate dependencies which are already provided by the core of ATAK. The one thing to note is we also needed to include junit:4.12 as it is required by RecyclerView. This version of RecyclerView is compatible with the versions of dependencies already included in ATAK core

  public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.ViewHolder> {

    private static final String TAG = DemoAdapter.class.getSimpleName();
    private final String[] localDataSet;

    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final TextView textView;
        
        public ViewHolder(View view) {
            super(view);
            textView = view.findViewById(R.id.username);
        }
        
        public TextView getTextView() { return textView; }
    }

    public DemoAdapter(String[] dataSet) { localDataSet = dataSet; }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        // Create a new view, which defines the UI of the list item
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.item_user, viewGroup, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, final int position) {
        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        try { viewHolder.getTextView().setText(localDataSet[position]); } 
        catch (Exception e) { Log.e(TAG, "!!!! FAILED TO BIND TEXT !!!"); }
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() { return localDataSet.length; }
}
  

The driving component of any RecyclerView is the Adapter as it provides the logic to the view for how to grab data from the list of data items to render in a re-used layout view item based on the tracked scroll position. The custom ViewHolder class is always provided the R.layout.item_user and implements the functions to fetch the R.id.username TextView element. This then allows the onBindViewHolder to set the TextView to the dataset value when it’s position is rendered within the view.

  public class LayoutsFragment extends Fragment {

    private Context pluginCtx;
    private String[] users;
    protected RecyclerView demoRecyclerView;
    protected DemoAdapter demoRecyclerAdapter;

    /** Create and instance of the LayoutsFragment */
    public LayoutsFragment construct(final HelloWorldDropDown receiver) {
        pluginCtx = receiver.getPluginCtx();
        users = new String[100];
        for (int i=0; i < users.length; i++) {
            users[i] = String.format(Locale.US, "User %d", i+1);
        }
        return this;
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = LayoutInflater.from(pluginCtx).inflate(R.layout.tab_layouts, container, false);
        LinearLayout layout_container = view.findViewById(R.id.linear_layouts_container);
        demoRecyclerView = new RecyclerView(pluginCtx);
        demoRecyclerView.setLayoutParams(new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                200));
        demoRecyclerView.setBackgroundColor(Color.parseColor("#000000"));
        demoRecyclerView.setHorizontalScrollBarEnabled(true);
        demoRecyclerAdapter = new DemoAdapter(users);
        demoRecyclerView.setAdapter(demoRecyclerAdapter);
        demoRecyclerView.setLayoutManager(new LinearLayoutManager(pluginCtx,
                LinearLayoutManager.HORIZONTAL, false));
        layout_container.addView(demoRecyclerView);
        return view;
    }
}
  

The above code snippet is the reduced LayoutsFragment class to show the relevant parts of code responsible for setting up the RecyclerView and data to be provided. The construct method creates a list of 100 mock user strings with the format “User ###”. In the onCreateView method we create a RecyclerView UI element and set the layout properties to occupy the entire width of the parent and only have a height of 200px. Then we create an instance of our DemoAdapter providing the list of mock user string values and set the adapter to scroll horizontally. Finally we use the main LinearLayout element from the fragment layout to append the RecyclerView as the last child component adding it to the UI.

Potential Errors:

If you don’t set the context properly for the Recycler View attempting to open your plugin will crash ATAK and result in an error message like the one below referencing the onCreateView() method as the source of the problem in attempts to render the component.

  E  FATAL EXCEPTION: main
Process: com.atakmap.app.civ, PID: 25132
android.view.InflateException: Binary XML file line #155: Binary XML file line #155: Error inflating class androidx.recyclerview.widget.RecyclerView
Caused by: android.view.InflateException: Binary XML file line #155: Error inflating class androidx.recyclerview.widget.RecyclerView
  

TOP ⇑