Data Table¶
A data table displays labeled columns and rows used to present numbers, text, or images.
Anatomy¶
Data table rows consist of a collection of columns, each containing an image or up to two lines of text or numbers. In the case of a header row, only one line of text is allowed.
A data table view consists of a horizontally and vertically scrollable list of data table rows. The first row is a sticky header containing the labels for the columns. Optionally, the first column can be a sticky column. A sticky column of chevrons can optionally be displayed at the end of the screen.
Usage¶
A data table row is used to display info about an item and should lead to more information when the user clicks the row. A data table view is used to display these rows.
Construction¶
You can create a data table view using the constructor in code:
DataTableView dataTable = new DataTableView(getContext());
You can also create a data table view by declaring a DataTableView
element in XML like this:
<com.sap.cloud.mobile.fiori.object.DataTableView
android:id="@+id/dataTable"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.sap.cloud.mobile.fiori.object.DataTableView>
Populating the Table¶
To populate the data table view with data, create an adapter extending
DataTableProvider
and add it to the DataTableView
. Within this class,
override onCreateViewHolder(ViewGroup, int)
and onBindViewHolder(DataTableRowViewHolder, int)
with the implementation for each DataTableRow
, and override getItemCount()
with the number of rows in the table. For example:
DataTableAdapter adapter = new DataTableAdapter();
dataTable.setAdapter(adapter);
public class DataTableAdapter extends DataTableView.DataTableProvider {
public DataTableAdapter() {}
@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
DataTableRow row = holder.mRow;
row.setTextAlignment(DataTableRow.ALIGN_START,
DataTableRow.ALIGN_START,
DataTableRow.ALIGN_START,
DataTableRow.ALIGN_END,
DataTableRow.ALIGN_START);
row.setEditType(DataTableCellView.EDIT_TYPE_NONE,
DataTableCellView.EDIT_TYPE_NONE,
DataTableCellView.EDIT_TYPE_TEXT,
DataTableCellView.EDIT_TYPE_LIST_PICKER,
DataTableCellView.EDIT_TYPE_DATE_TIME_PICKER);
return holder;
}
@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
DataTableRow row = (DataTableRow)holder.itemView;
row.setColumnWidths(110, 150, 400, 100, 400);
if (pos == 0) {
row.setText(0, "");
row.setText(1, "Work Number");
row.setText(2, "Headline");
row.setText(3, "Price (USD)");
row.setText(4, "Description");
} else {
row.setLines(2);
row.setImage(0, getResources().getDrawable(R.drawable.image));
row.setText(1, "Work number info");
row.setText(2, "Headline info");
row.setText(3, "Price info");
row.setText(4, "Description info");
}
}
@Override
public int getItemCount() {
return 25;
}
}
Edit Mode¶
The data table can enter edit mode, which alters the behavior of each
column according to their specified edit type. You can utilize these unique
behaviors to edit the data within the table. The adapter keeps track of
edit mode in mEditMode
, so you can use it to determine whether to
display tentative data or saved data.
The edit type for each column can be customized using setEditType(int...)
.
Each argument corresponds to the column of that index. By default, a column's
edit type is EDIT_TYPE_NONE
.
Alternatively, you could use setCellEditType(int, int)
to assign the
edit type for a specific column.
dataTable.setEditMode(true);
public class DataTableAdapter extends DataTableView.DataTableProvider {
public DataTableAdapter() {}
@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
DataTableRow row = holder.mRow;
row.setEditType(DataTableCellView.EDIT_TYPE_NONE,
DataTableCellView.EDIT_TYPE_TEXT,
DataTableCellView.EDIT_TYPE_LIST_PICKER,
DataTableCellView.EDIT_TYPE_DATE_TIME_PICKER,
DataTableCellView.EDIT_TYPE_DURATION_PICKER);
// alternative method
row.setCellEditType(1, DataTableCellView.EDIT_TYPE_TEXT);
row.setCellEditType(2, DataTableCellView.EDIT_TYPE_LIST_PICKER);
row.setCellEditType(3, DataTableCellView.EDIT_TYPE_DATE_TIME_PICKER);
row.setCellEditType(4, DataTableCellView.EDIT_TYPE_DURATION_PICKER);
return holder;
}
}
EDIT_TYPE_NONE¶
EDIT_TYPE_NONE is the default edit type. Columns with this edit type are not clickable.
EDIT_TYPE_TEXT¶
Clicking a column with EDIT_TYPE_TEXT will open a text box above the column
where the user can directly edit the text in the column. To have your
data reflect the changes in the text box, attach a DataTableEditTextListener
to the column using setTextWatcher(int, TextWatcher)
and override
onTextChanged(CharSequence, int, int, int)
with the intended interactions
with your data. Use mPosition
and setPosition(int)
to keep the
DataTableEditTextListener
synchronized with the movement around the
data table.
@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
DataTableRow row = holder.mRow;
row.setCellEditType(1, DataTableCellView.EDIT_TYPE_TEXT);
// attach a DataTableEditTextListener to the column with index 1
row.setTextWatcher(1, new DataTableView.DataTableProvider.DataTableEditTextListener() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// update data at mPosition with new text
mObjectsCopy.get(mPosition).setHeadline(s.toString());
// define error state depending on whether the text is in an appropriate format
boolean isError = ...
// update data at mPosition with the error state
mObjectsCopy.get(mPosition).setError(isError);
// tell the column whether it is in error state
((DataTableCellView)row.getColumn(1)).setIsError(isError);
// update adapter with the error state
boolean prev = hasError();
setIsError(mPosition, row.hasError());
boolean curr = hasError();
// perform logic depending on error state, such as displaying a banner
if (prev != curr) {
if (prev) {
mBanner.dismiss();
} else {
mBanner.show();
}
}
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
// the saved data source
obj = mObjects.get(pos);
// the tentative data source
objCopy = mObjectsCopy.get(pos);
DataTableRow row = (DataTableRow)holder.itemView;
row.setColumnWidths(110, 150, 400, 100, 400);
// display tentative data and update listener position
if (mEditMode) {
row.setText(1, objCopy.getHeadline());
((DataTableCellView)row.getColumn(1)).setIsError(objCopy.isError());
TextWatcher watcher = row.getTextWatcher(1);
if (watcher instanceof DataTableEditTextListener) {
((DataTableEditTextListener) watcher).setPosition(pos);
}
} else { // display saved data
row.setText(1, obj.getHeadline());
}
}
EDIT_TYPE_LIST_PICKER¶
The EDIT_TYPE_LIST_PICKER edit type makes the column clickable and displays a chevron at its end to indicate that clicking the column will open a list picker. The content of what will be opened is left to the developer to customize. In this example, clicking will move to another activity containing a list picker:
@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
DataTableRow row = holder.mRow;
row.setCellEditType(2, DataTableCellView.EDIT_TYPE_LIST_PICKER);
return holder;
}
@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
// the saved data source
obj = mObjects.get(pos);
// the tentative data source
objCopy = mObjectsCopy.get(pos);
DataTableRow row = (DataTableRow)holder.itemView;
row.setColumnWidths(110, 150, 400, 100, 400);
// attach a listener to the column with index 2
DataTableCellView cell = (DataTableCellView)row.getColumn(2);
if (!cell.hasOnClickListeners()) {
if (!row.getHeader()) {
cell.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mEditMode && cell.getEditType() == DataTableCellView.EDIT_TYPE_LIST_PICKER) {
Context context = row.getContext();
Intent intent = new Intent(context, DataTableListPickerActivity.class);
intent.putExtra("pos", holder.getBindingAdapterPosition());
startActivityForResult(intent, 0);
}
}
});
}
}
// display tentative data
if (mEditMode) {
row.setText(2, objCopy.getPrice());
} else { // display saved data
row.setText(2, obj.getPrice());
}
}
Since we are moving to another activity in this example, we need to retrieve the results when we return to the data table:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Bundle bundle = data.getExtras();
mAdapter.mObjectsCopy.get(bundle.getInt("pos")).setPrice(bundle.getInt("value"));
}
EDIT_TYPE_DATE_TIME_PICKER, EDIT_TYPE_DATE_PICKER, EDIT_TYPE_TIME_PICKER¶
Clicking a column with any of these edit types will open a date time picker.
To have your data reflect the results of the picker, attach a FormCell.CellValueChangeListener<Date>
to the column using setPickerListener(int, FormCell.CellValueChangeListener<Date>)
and override cellChangeHandler(Date)
with the intended interactions with
your data.
@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
DataTableRow row = holder.mRow;
row.setCellEditType(3, DataTableCellView.EDIT_TYPE_DATE_TIME_PICKER);
//row.setCellEditType(3, DataTableCellView.EDIT_TYPE_DATE_PICKER);
//row.setCellEditType(3, DataTableCellView.EDIT_TYPE_TIME_PICKER);
// use the appropriate format to update the data
DateFormat formatter = new SimpleDateFormat("MMM dd, yyyy hh:mm a");
//DateFormat formatter = new SimpleDateFormat("MMM dd, yyyy");
//DateFormat formatter = new SimpleDateFormat("hh:mm a");
// attach a CellValueChangeListener to the column with index 3
row.setPickerListener(3, new FormCell.CellValueChangeListener<Date>() {
@Override
protected void cellChangeHandler(@Nullable Date value) {
mObjectsCopy.get(holder.getBindingAdapterPosition()).setDateTime(formatter.format(value));
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
// the saved data source
obj = mObjects.get(pos);
// the tentative data source
objCopy = mObjectsCopy.get(pos);
DataTableRow row = (DataTableRow)holder.itemView;
row.setColumnWidths(110, 150, 400, 100, 400);
// set a FragmentManager so the picker has proper styling
((DataTableCellView)row.getColumn(3)).setManager(mManager);
// display tentative data
if (mEditMode) {
row.setText(3, objCopy.getDateTime());
} else { // display saved data
row.setText(3, obj.getDateTime());
}
}
EDIT_TYPE_DURATION_PICKER¶
Clicking a column with the EDIT_TYPE_DURATION_PICKER edit type opens a duration picker. To
have your data reflect the results of the picker, attach a FormCell.CellValueChangeListener<Duration>
to the column using setDurationPickerListener(int, FormCell.CellValueChangeListener<Duration>)
and override cellChangeHandler(Duration)
with the intended interactions
with your data.
@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
DataTableRow row = holder.mRow;
row.setCellEditType(4, DataTableCellView.EDIT_TYPE_DURATION_PICKER);
// attach a CellValueChangeListener to the column with index 4
row.setDurationPickerListener(4, new FormCell.CellValueChangeListener<Duration>() {
@Override
protected void cellChangeHandler(@Nullable Duration value) {
mObjectsCopy.get(holder.getBindingAdapterPosition()).setDuration(value.toString());
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
// the saved data source
obj = mObjects.get(pos);
// the tentative data source
objCopy = mObjectsCopy.get(pos);
DataTableRow row = (DataTableRow)holder.itemView;
row.setColumnWidths(110, 150, 400, 100, 400);
// display tentative data
if (mEditMode) {
row.setText(4, objCopy.getDuration());
} else { // display saved data
row.setText(4, obj.getDuration());
}
}
Customization¶
Text/Image¶
The content for each column can be specified using setText(int, CharSequence)
and setImage(int, Drawable)
. If both text and a non-null image are set,
the image will be displayed rather than the text.
row.setImage(0, getResources().getDrawable(R.drawable.image)); // first column is an image
row.setText(1, "Work Number"); // second column is text
row.setText(2, "10007611"); // third column is text
Sticky Column¶
The sticky column keeps the first column on the screen displayed as the screen is scrolled. By default, the sticky column is not enabled.
dataTable.setStickyMode(true);
Chevron Column¶
The chevron column is a sticky column displayed at the end of the screen to help indicate the row is clickable. By default, the chevron column is enabled.
In edit mode, the chevron column will not be displayed.
dataTable.setShowChevron(true);
Text Alignment¶
The text alignment for each column can be customized by assigning
ALIGN_START
or ALIGN_END
in setTextAlignment(int...)
. Each argument
corresponds to the column of that index. By default, a column's alignment
is ALIGN_START
.
Alternatively, you could use setCellTextAlignment(int, int)
to assign
the text alignment for a specific column.
row.setTextAlignment(DataTableRow.ALIGN_START, // first column aligned with start
DataTableRow.ALIGN_END, // second column aligned with end
DataTableRow.ALIGN_START); // third and any other columns aligned with start
// alternative method
row.setCellTextAlignment(1, DataTableRow.ALIGN_END);
Column Width¶
The width for each column can be customized by assigning a dp value in
setColumnWidths(float...)
. Each argument corresponds to the column of
that index.
row.setColumnWidths(110, 150, 400, 100, 400);
By default, columns have a maximum width of half of the screen's width. The sticky column has a maximum width of a third of the screen's width. These maximum widths will adjust accordingly when the screen is rotated 90 degrees. The maximum widths can be customized:
row.setMaxColumnWidth(450);
row.setMaxStickyColumnWidth(300);
Dynamic Column¶
On a tablet, you can specify one column to be a dynamic column using
setDynamicColumn(int)
. The presence of a dynamic column will cause the row
width to be equal to screen width, and the dynamic column's width will be
equal to the remaining width after calculating the other columns' widths.
Passing a negative value into setDynamicColumn(int)
will disable the
dynamic column. By default, no column has dynamic width.
row.setDynamicColumn(2); // third column has dynamic width
row.setDynamicColumn(-1); // no column has dynamic width
Click Listener¶
The Data Table View accepts a FioriItemClickListener
that defines click
behavior for each item.
In edit mode, this listener will be ignored.
FioriItemClickListener listener = new FioriItemClickListener() {
@Override
public void onClick(@NonNull View view, int position) {
if (position == 0) {
Toast.makeText(view.getContext(), "Header has been clicked", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(view.getContext(), "Row " + position + " has been clicked", Toast.LENGTH_SHORT).show();
Context context = view.getContext();
Intent intent = new Intent(context, ObjectHeaderActivity.class);
context.startActivity(intent);
}
}
@Override
public void onLongClick(@NonNull View view, int position) {
}
};
dataTable.setClickListener(listener);