Подтвердить что ты не робот

Создание таблицы/сетки с замороженным столбцом и замороженными заголовками

Я работаю над небольшим Android-приложением. Часть того, что мне нужно для этого приложения для Android, должно иметь сетку, которая имеет горизонтальную и вертикальную прокрутку. Однако крайний левый столбец необходимо заморозить (всегда на экране, а не в горизонтальной прокрутке). Аналогично, верхняя строка заголовка должна быть заморожена (не является частью вертикальной прокрутки)

Эта картина, мы надеемся, будет описать это ясно, если вышеизложенное не имеет особого смысла:

Example of what I would like to do

Key

  • Белый: не прокручивать вообще
  • Синий: прокрутка по вертикали
  • Красный: прокрутка по горизонтали
  • Фиолетовый: прокручивать как по вертикали, так и по горизонтали

Сделать одно из этих измерений достаточно просто, и я это сделал. Однако у меня возникают проблемы с тем, чтобы оба эти измерения работали. (т.е. я могу получить нижнюю часть, которая будет синей, или я могу получить правильную часть, чтобы она была красной, но не полностью, как указано выше). Код, который у меня есть, приведен ниже и будет в основном производить следующее:

What I have!

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>

Код 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);
    }
}

Это выглядит так:

Look, headers!

Итак, это то, что похоже на немного горизонтальной прокрутки

Bah, no headers!

Это то, что выглядит при прокрутке по вертикали, обратите внимание, что вы теряете заголовки! Это проблема!

Два последних примечания!

Во-первых, я не могу поверить, что этого уже не существует. (У меня нет Android, поэтому я не смог оглянуться на приложения, которые могут это сделать). Тем не менее, я искал, по крайней мере, два дня в StackOverflow и в Интернете в целом, ища решение для GridView или TableLayout, которое предоставит мне то, что я хотел бы сделать, и еще не нашел решение. Как смущенный, поскольку я был бы за то, что пропустил его, если кто-то знает о ресурсе, который описывает, как это сделать, я был бы благодарен!

Во-вторых, я попытался "принудительно" решить это, добавив два LinearLayouts, один из которых захватил часть "Заголовка" сетки, которую я хочу создать, а другую для нижней "контентной" части сетку, которую я хочу создать. Я могу опубликовать этот код, но это уже довольно долго, и я надеюсь, что то, что я имею в виду, очевидно. Это частично сработало, но проблема здесь в том, что заголовки и столбцы контента никогда не выстраивались в линию. Я хотел использовать getWidth() и setMinimumWidth() в TextViews в TableRows, но, как описано здесь, эти данные были недоступны во время onCreate (а также были недоступны внутри onPostCreate), Я не смог найти способ заставить это работать, и решение в этой области тоже было бы замечательно!

Если вы сделали это так далеко до конца, то вам очень понравится!

4b9b3361

Ответ 1

Примерно неделю назад я снова просмотрел эту проблему и придумал решение. Для решения требуется, чтобы я сделал много настроек ширины ручек для столбцов в этой сетке, и я считаю, что это очень субар в этот день и возраст. К сожалению, я также продолжал искать более рациональное решение для платформы Android, но я ничего не сделал.

Ниже приведен код для создания этой же сетки, если кто-то следующий за мной нуждается в этом. Я объясню некоторые из наиболее подходящих деталей ниже!

Макет: 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>

Деятельность: 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);
    }
}

Прослушивание списка прокрутки (чтобы перехватить две кнопки вверх): HorizontalScrollViewListener.java:

public interface HorizontalScrollViewListener {
    void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY);
}

Класс ScrollView, который реализует этот прослушиватель: 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);
       }
   }
}

Действительно важная часть этого рода - это три раза:

  • ObservableHorizontalScrollView позволяет прокручивать таблицу заголовков и таблицу содержимого в синхронизации. В принципе, это обеспечивает все горизонтальное движение для сетки.
  • Путь, в котором они остаются выровненными, заключается в обнаружении самой большой строки, которая будет находиться в столбце. Это делается в конце PopulateMainTable(). Пока мы проходим через каждый из TextViews и добавляем их в строки, вы заметите, что есть два массива maxFrozenChars и maxContentChars, которые отслеживают, какое наибольшее значение строки мы видели. В конце PopulateMainTable() мы прокручиваем каждый из строк, и для каждой из ячеек мы устанавливаем его минимальную и максимальную ширину на основе самой большой строки, которую мы видели в этом столбце. Это обрабатывается setChildTextViewWidths.
  • Последний элемент, который делает эту работу, заключается в использовании моноширинного шрифта. Вы заметите, что в onCreate я загружаю шрифт consola.ttf и позже применяю его к каждому из текстовых элементов сетки, которые действуют как ячейки в сетке. Это позволяет нам быть достаточно уверенным, что текст не будет отображаться больше, чем мы установили минимальную и максимальную ширину на предыдущем шаге. Здесь я немного причудливо, что с целым cellWidthFactor и максимальным размером этого столбца. Это действительно так, что маленькие строки будут соответствовать наверняка, в то время как мы можем свести к минимуму пустое пространство для больших строк, которые (для моей системы) не будут заглавными буквами. Если у вас возникли проблемы с этим, и вы получили строки, которые не соответствовали размеру столбца, который вы установили, это то, где вы хотите редактировать вещи. Вы хотите изменить переменную replacementWidth с помощью какой-либо другой формулы для определения ширины ячейки, например 50 * widths[i], которая будет довольно большой! Но в некоторых столбцах вы останетесь с большим количеством пробелов. В принципе, в зависимости от того, что вы планируете вкладывать в свою сетку, это может потребоваться изменить. Выше это то, что сработало для меня.

Я надеюсь, что это поможет кому-то еще в будущем!

Ответ 2

TableFixHeaders может быть полезна для вас в этом случае.

Ответ 3

Сверху моей головы, вот как я подхожу к этому:

1) Создайте интерфейс с помощью одного метода, который будет выполняться вашей Деятельностью для получения прокручиваемых координат и что ваш ScrollView может перезвонить, когда происходит прокрутка:

public interface ScrollCallback {
    public void scrollChanged(int newXPos, int newYPos);
}

2) Внесите это в свою деятельность, чтобы прокрутить два ограниченных прокрутки до положения, которое прокрутка основного прокрутки просто прокручивается до:

@Override
public void scrollChanged(int newXPos, int newYPos) {
    mVerticalScrollView.scrollTo(0, newYPos);
    mHorizontalScrollView.scrollTo(newXPos, 0);
}

3) Подкласс ScrollView для переопределения метода onScrollChanged() и добавления метода и переменной-члена для возврата к активности:

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) Замените запас ScrollView в своем XML новым классом и вызовите setScrollCallback(this) в onCreate().