android - ssrs - Crear una tabla/grilla con una columna congelada y encabezados congelados
salto de pagina en reporting services (3)
Fuera de mi cabeza, así es como me acercaría a esto:
1) Cree una interfaz con un método que su actividad implementaría para recibir coordenadas de desplazamiento y que su ScrollView pueda devolver cuando se produzca un desplazamiento:
public interface ScrollCallback {
public void scrollChanged(int newXPos, int newYPos);
}
2) Implemente esto en su actividad para desplazar las dos vistas de desplazamiento restringidas a la posición a la que se desplazó la vista de desplazamiento principal:
@Override
public void scrollChanged(int newXPos, int newYPos) {
mVerticalScrollView.scrollTo(0, newYPos);
mHorizontalScrollView.scrollTo(newXPos, 0);
}
3) Subclase ScrollView para anular el método onScrollChanged () y agregar un método y una variable miembro para devolver la llamada a la actividad:
private ScrollCallback mCallback;
//...
@Override
protected void onScrollChanged (int l, int t, int oldl, int oldt) {
mCallback.scrollChanged(l, t);
super.onScrollChanged(l, t, oldl, oldt);
}
public void setScrollCallback(ScrollCallback callback) {
mCallback = callback;
}
4) Reemplace el stock ScrollView en su XML con su nueva clase y llame a setScrollCallback(this)
en onCreate()
.
Estoy trabajando en una pequeña aplicación de Android. Parte de lo que necesito para esta aplicación de Android es tener una grilla que se pueda desplazar horizontal y verticalmente. Sin embargo, la columna de la izquierda debe estar congelada (siempre en la pantalla, y no como parte del desplazamiento horizontal). Del mismo modo, la fila del encabezado superior debe estar congelada (no forma parte del desplazamiento vertical)
Esta imagen con suerte lo describirá claramente si lo anterior no tiene demasiado sentido:
Clave :
- Blanco: no desplazarse en absoluto
- Azul: desplazarse verticalmente
- Rojo: desplazarse horizontalmente
- Púrpura: desplazarse tanto vertical como horizontalmente
Hacer una de estas dimensiones es bastante fácil, y lo he hecho. Sin embargo, tengo problemas para lograr que estas dos dimensiones funcionen. (es decir, puedo hacer que la parte inferior sea toda azul, o puedo obtener la parte derecha para que sea roja, pero no del todo como se indicó anteriormente) El código que tengo está debajo, y básicamente generará lo siguiente:
result_grid.xml:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/lightGrey">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_below="@id/summaryTableLayout"
android:layout_weight="0.1"
android:layout_marginBottom="50dip"
android:minHeight="100dip">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TableLayout
android:id="@+id/frozenTable"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="2dip"
android:layout_marginLeft="1dip"
android:stretchColumns="1"
/>
<HorizontalScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/frozenTable"
android:layout_marginTop="2dip"
android:layout_marginLeft="4dip"
android:layout_marginRight="1dip">
<TableLayout
android:id="@+id/contentTable"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"/>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
</LinearLayout>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical"
android:layout_weight="0.1"
android:layout_alignParentBottom="true">
<Button
android:id="@+id/backButton"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="Return"/>
</LinearLayout>
</RelativeLayout>
Código Java:
private boolean showSummaries;
private TableLayout summaryTable;
private TableLayout frozenTable;
private TableLayout contentTable;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.result_grid);
Button backButton = (Button)findViewById(R.id.backButton);
frozenTable = (TableLayout)findViewById(R.id.frozenTable);
contentTable = (TableLayout)findViewById(R.id.contentTable);
ArrayList<String[]> content;
// [Removed Code] Here I get some data from getIntent().getExtras() that will populate the content ArrayList
PopulateMainTable(content);
}
private void PopulateMainTable(ArrayList<String[]> content) {
// [Removed Code] There is some code here to style the table (so it has lines for the rows)
for (int i = 0; i < content.size(); i++){
TableRow frozenRow = new TableRow(this);
// [Removed Code] Styling of the row
TextView frozenCell = new TextView(this);
frozenCell.setText(content.get(i)[0]);
// [Removed Code] Styling of the cell
frozenRow.addView(frozenCell);
frozenTable.addView(frozenRow);
// The rest of them
TableRow row = new TableRow(this);
// [Renoved Code] Styling of the row
for (int j = 1; j < content.get(0).length; j++) {
TextView rowCell = new TextView(this);
rowCell.setText(content.get(i)[j]);
// [Removed Code] Styling of the cell
row.addView(rowCell);
}
contentTable.addView(row);
}
}
Esto es lo que parece:
Así que esto es lo que parece con un poco de desplazamiento horizontal
Esto es lo que parece cuando se desplaza verticalmente, ¡tenga en cuenta que pierde los encabezados! ¡Esto es un problema!
¡Dos últimas cosas para notar!
En primer lugar, no puedo creer que esto no exista en algún lugar. (No tengo un Android, así que no he podido buscar aplicaciones que puedan hacer esto). Sin embargo, he buscado al menos dos días dentro de StackOverflow y en Internet en general buscando una solución para GridView o TableLayout que me proporcione lo que me gustaría hacer y aún no he encontrado una solución. Tan avergonzado como me sentiría por haberlo perdido, si alguien sabe de un recurso que describe cómo hacerlo, ¡le estaría muy agradecido!
En segundo lugar, traté de "forzar" una solución para esto, en el sentido de que agregué dos LinearLayouts, uno que capturaba la parte "Encabezado" de la cuadrícula que quiero crear, y otro para la parte inferior de "contenido" de la cuadrícula que quiero crear. Puedo publicar este código, pero esto ya es bastante largo y espero que lo que quiero decir sea obvio. Esto funcionó parcialmente, pero el problema aquí es que los encabezados y las columnas de contenido nunca se alinearon. Quería usar getWidth () y setMinimumWidth () en las TextViews dentro de las TableRows, pero como se describe aquí, esta información era inaccesible durante onCreate (y también era inaccesible dentro de onPostCreate). No he podido encontrar la manera de hacer que esto funcione, ¡y una solución en este ámbito también sería maravillosa!
Si has llegado tan lejos hasta el final, felicitaciones para ti!
Hace aproximadamente una semana revisé este problema y se me ocurrió una solución. La solución requiere que realice una gran cantidad de ajustes de ancho manual para las columnas en esta cuadrícula, y considero que es extremadamente bajo en este día y edad. Desafortunadamente, también he seguido buscando una solución más completa, nativa de la plataforma Android, pero no he inventado nada.
El siguiente es el código para crear esta misma grilla, si alguien que me sigue lo necesita. Explicaré algunos de los detalles más pertinentes a continuación.
El diseño: grid.xml
:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/lightGrey">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="2dip"
android:layout_weight="1"
android:minHeight="100dip">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TableLayout
android:id="@+id/frozenTableHeader"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="2dip"
android:layout_marginLeft="1dip"
android:stretchColumns="1"
/>
<qvtcapital.mobile.controls.ObservableHorizontalScrollView
android:id="@+id/contentTableHeaderHorizontalScrollView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/frozenTableHeader"
android:layout_marginTop="2dip"
android:layout_marginLeft="4dip"
android:layout_marginRight="1dip">
<TableLayout
android:id="@+id/contentTableHeader"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"/>
</qvtcapital.mobile.controls.ObservableHorizontalScrollView>
</LinearLayout>
<ScrollView
android:id="@+id/verticalScrollView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TableLayout
android:id="@+id/frozenTable"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="2dip"
android:layout_marginLeft="1dip"
android:stretchColumns="1"
/>
<qvtcapital.mobile.controls.ObservableHorizontalScrollView
android:id="@+id/contentTableHorizontalScrollView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/frozenTable"
android:layout_marginTop="2dip"
android:layout_marginLeft="4dip"
android:layout_marginRight="1dip">
<TableLayout
android:id="@+id/contentTable"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"/>
</qvtcapital.mobile.controls.ObservableHorizontalScrollView>
</LinearLayout>
</ScrollView>
</TableLayout>
La actividad: Grid.java
:
public class ResultGrid extends Activity implements HorizontalScrollViewListener {
private TableLayout frozenHeaderTable;
private TableLayout contentHeaderTable;
private TableLayout frozenTable;
private TableLayout contentTable;
Typeface font;
float fontSize;
int cellWidthFactor;
ObservableHorizontalScrollView headerScrollView;
ObservableHorizontalScrollView contentScrollView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.result_grid);
font = Typeface.createFromAsset(getAssets(), "fonts/consola.ttf");
fontSize = 11; // Actually this is dynamic in my application, but that code is removed for clarity
final float scale = getBaseContext().getResources().getDisplayMetrics().density;
cellWidthFactor = (int) Math.ceil(fontSize * scale * (fontSize < 10 ? 0.9 : 0.7));
Button backButton = (Button)findViewById(R.id.backButton);
frozenTable = (TableLayout)findViewById(R.id.frozenTable);
contentTable = (TableLayout)findViewById(R.id.contentTable);
frozenHeaderTable = (TableLayout)findViewById(R.id.frozenTableHeader);
contentHeaderTable = (TableLayout)findViewById(R.id.contentTableHeader);
headerScrollView = (ObservableHorizontalScrollView) findViewById(R.id.contentTableHeaderHorizontalScrollView);
headerScrollView.setScrollViewListener(this);
contentScrollView = (ObservableHorizontalScrollView) findViewById(R.id.contentTableHorizontalScrollView);
contentScrollView.setScrollViewListener(this);
contentScrollView.setHorizontalScrollBarEnabled(false); // Only show the scroll bar on the header table (so that there aren''t two)
backButton.setOnClickListener(backButtonClick);
InitializeInitialData();
}
protected void InitializeInitialData() {
ArrayList<String[]> content;
Bundle myBundle = getIntent().getExtras();
try {
content = (ArrayList<String[]>) myBundle.get("gridData");
} catch (Exception e) {
content = new ArrayList<String[]>();
content.add(new String[] {"Error", "There was an error parsing the result data, please try again"} );
e.printStackTrace();
}
PopulateMainTable(content);
}
protected void PopulateMainTable(ArrayList<String[]> content) {
frozenTable.setBackgroundResource(R.color.tableBorder);
contentTable.setBackgroundResource(R.color.tableBorder);
TableLayout.LayoutParams frozenRowParams = new TableLayout.LayoutParams(
TableLayout.LayoutParams.WRAP_CONTENT,
TableLayout.LayoutParams.WRAP_CONTENT);
frozenRowParams.setMargins(1, 1, 1, 1);
frozenRowParams.weight=1;
TableLayout.LayoutParams tableRowParams = new TableLayout.LayoutParams(
TableLayout.LayoutParams.WRAP_CONTENT,
TableLayout.LayoutParams.WRAP_CONTENT);
tableRowParams.setMargins(0, 1, 1, 1);
tableRowParams.weight=1;
TableRow frozenTableHeaderRow=null;
TableRow contentTableHeaderRow=null;
int maxFrozenChars = 0;
int[] maxContentChars = new int[content.get(0).length-1];
for (int i = 0; i < content.size(); i++){
TableRow frozenRow = new TableRow(this);
frozenRow.setLayoutParams(frozenRowParams);
frozenRow.setBackgroundResource(R.color.tableRows);
TextView frozenCell = new TextView(this);
frozenCell.setText(content.get(i)[0]);
frozenCell.setTextColor(Color.parseColor("#FF000000"));
frozenCell.setPadding(5, 0, 5, 0);
if (0 == i) { frozenCell.setTypeface(font, Typeface.BOLD);
} else { frozenCell.setTypeface(font, Typeface.NORMAL); }
frozenCell.setTextSize(TypedValue.COMPLEX_UNIT_DIP, fontSize);
frozenRow.addView(frozenCell);
if (content.get(i)[0].length() > maxFrozenChars) {
maxFrozenChars = content.get(i)[0].length();
}
// The rest of them
TableRow row = new TableRow(this);
row.setLayoutParams(tableRowParams);
row.setBackgroundResource(R.color.tableRows);
for (int j = 1; j < content.get(0).length; j++) {
TextView rowCell = new TextView(this);
rowCell.setText(content.get(i)[j]);
rowCell.setPadding(10, 0, 0, 0);
rowCell.setGravity(Gravity.RIGHT);
rowCell.setTextColor(Color.parseColor("#FF000000"));
if ( 0 == i) { rowCell.setTypeface(font, Typeface.BOLD);
} else { rowCell.setTypeface(font, Typeface.NORMAL); }
rowCell.setTextSize(TypedValue.COMPLEX_UNIT_DIP, fontSize);
row.addView(rowCell);
if (content.get(i)[j].length() > maxContentChars[j-1]) {
maxContentChars[j-1] = content.get(i)[j].length();
}
}
if (i==0) {
frozenTableHeaderRow=frozenRow;
contentTableHeaderRow=row;
frozenHeaderTable.addView(frozenRow);
contentHeaderTable.addView(row);
} else {
frozenTable.addView(frozenRow);
contentTable.addView(row);
}
}
setChildTextViewWidths(frozenTableHeaderRow, new int[]{maxFrozenChars});
setChildTextViewWidths(contentTableHeaderRow, maxContentChars);
for (int i = 0; i < contentTable.getChildCount(); i++) {
TableRow frozenRow = (TableRow) frozenTable.getChildAt(i);
setChildTextViewWidths(frozenRow, new int[]{maxFrozenChars});
TableRow row = (TableRow) contentTable.getChildAt(i);
setChildTextViewWidths(row, maxContentChars);
}
}
private void setChildTextViewWidths(TableRow row, int[] widths) {
if (null==row) {
return;
}
for (int i = 0; i < row.getChildCount(); i++) {
TextView cell = (TextView) row.getChildAt(i);
int replacementWidth =
widths[i] == 1
? (int) Math.ceil(widths[i] * cellWidthFactor * 2)
: widths[i] < 3
? (int) Math.ceil(widths[i] * cellWidthFactor * 1.7)
: widths[i] < 5
? (int) Math.ceil(widths[i] * cellWidthFactor * 1.2)
:widths[i] * cellWidthFactor;
cell.setMinimumWidth(replacementWidth);
cell.setMaxWidth(replacementWidth);
}
}
public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY) {
if (scrollView==headerScrollView) {
contentScrollView.scrollTo(x, y);
} else if (scrollView==contentScrollView) {
headerScrollView.scrollTo(x, y);
}
}
El oyente de la vista de desplazamiento (para unir los dos): HorizontalScrollViewListener.java
:
public interface HorizontalScrollViewListener {
void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY);
}
La clase ScrollView que implementa este oyente: ObservableHorizontalScrollView.java
:
public class ObservableHorizontalScrollView extends HorizontalScrollView {
private HorizontalScrollViewListener scrollViewListener=null;
public ObservableHorizontalScrollView(Context context) {
super(context);
}
public ObservableHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ObservableHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(HorizontalScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
@Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (null!=scrollViewListener) {
scrollViewListener.onScrollChanged(this, x, y, oldX, oldY);
}
}
}
La parte realmente importante de esto es una especie de triple:
- El ObservableHorizontalScrollView permite que la tabla de encabezado y la tabla de contenido se desplacen sincronizados. Básicamente, esto proporciona todo el movimiento horizontal para la grilla.
- La forma en que permanecen alineados es mediante la detección de la cadena más grande que estará en una columna. Esto se hace al final de
PopulateMainTable()
. Mientras examinamos cada una de las TextViews y las agregamos a las filas, observará que hay dos matricesmaxFrozenChars
ymaxContentChars
que hacen un seguimiento de cuál es el valor de cadena más grande que hemos visto. Al final dePopulateMainTable()
recorremos cada una de las filas y para cada una de las celdas establecemos su ancho mínimo y máximo en función de la cadena más grande que vimos en esa columna. Esto es manejado porsetChildTextViewWidths
. - El último elemento que hace que esto funcione es utilizar una fuente monoespaciada. Notarás que en
onCreate
estoy cargando una fuente consola.ttf y luego aplicándola a cada una de las vistas de texto de la cuadrícula que actúan como las celdas de la grilla. Esto nos permite estar razonablemente seguros de que el texto no se volverá más grande de lo que hemos establecido el ancho mínimo y máximo en el paso anterior. Estoy haciendo un poco de fantasía aquí, con todo el cellWidthFactor y el tamaño máximo de esa columna. Esto es realmente para que las cadenas más pequeñas se ajusten con seguridad, mientras que podemos minimizar el espacio en blanco para cadenas más grandes que (para mi sistema) no van a ser todas letras mayúsculas. Si te encontraste en problemas para usar esto y obtuviste cadenas que no encajaban en el tamaño de columna que configuraste, aquí es donde te gustaría editar las cosas. ¡Desea cambiar la variablereplacementWidth
con alguna otra fórmula para determinar el ancho de la celda, como50 * widths[i]
que sería bastante grande! Pero te dejaría con una buena cantidad de espacios en blanco en algunas columnas. Básicamente, dependiendo de lo que planeas poner en tu grilla, esto puede necesitar ser retocado. Arriba es lo que funcionó para mí.
¡Espero que esto ayude a alguien más en el futuro!
La biblioteca TableFixHeaders puede ser útil para usted en este caso.