创建一个表/网格冻结列和冷冻头网格、创建一个

2023-09-03 21:26:31 作者:单方面意淫

我工作的一个小的Andr​​oid应用程序。什么,我需要为这个Android应用一部分是有一个网格在水平和垂直滚动,能。然而,最左边的列需要被冻结(总是在屏幕上,而不是部分的水平滚动的)。类似地,顶部标题行需要被冻结(垂直滚动的一部分)

I am working on a small Android app. Part of what I need for this android app is to have a grid that is both horizontally and vertically scroll-able. However, the leftmost column needs to be frozen (always on screen, and not part of the horizontal scrolling). Similarly, the top header row needs to be frozen (not part of the vertical scrolling)

这画面将有望描述这个显然,如果上面没有什么太大的意义:

This picture will hopefully describe this clearly if the above doesn't make too much sense:

白:不滚动在所有 蓝:垂直滚动 红:水平滚动 紫:这两个滚动垂直和水平

要做到这些方面之一是很容易的,我已经这样做了。但是,我无法得到这两个层面的工作。 (也就是说,我可以得到的底部是所有蓝色,或者我可以得到正确的部分是全红的,但并非完全如上)的code我是以下,基本上会产生以下内容:

To do one of these dimensions is easy enough, and I have done so. However, I am having trouble getting both of these dimensions to work. (i.e., I can get the bottom portion to be all blue, or I can get the right portion to be all red, but not entirely as above) The code I have is below, and will basically produce the following:

result_grid.xml:

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的code:

Java code:

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);
    }
}

这是什么样子的:

因此​​,这是什么样子与水平滚动一点点

So this is what it looks like with a little bit of horizontal scrolling

这是个什么样子垂直滚动时一样,请注意,你失去了头!这是一个问题!

This is what it looks like when scrolling vertically, note that you lose the headers! This is a problem!

最后两件事情要注意!

Two last things to note!

首先,我简直不敢相信,这并不地方已经存在。 (我不拥有一个Android,所以我一直没能到处寻找可以做到这一点的应用程序)。不过,我已搜查了至少两天内的计算器,并在互联网上进行寻找一个既GridView控件或TableLayout一个解决方案,将提供给我什么我想要做的,还没有找到一个解决方案。由于不好意思,我会为被错过了,如果有人知道一个资源在那里,说明如何做到这一点,我将不胜感激!

First off, I cannot believe that this doesn't exist somewhere already. (I do not own an Android, so I have not been able to look around for apps that may do this). However, I have searched for at least two days within StackOverflow and in the Internet at large looking for a solution for either GridView or TableLayout that will provide me for what I'd like to do, and have yet to find a solution. As embarrassed as I would be for having missed it, if someone knows of a resource out there that describes how to do this, I would be grateful!

其次,我曾尝试武力解决这一点,我添加了两个LinearLayouts,一是捕捉我想创建网格的头的一部分,另一个的下方的内容部分网格我想创建。我可以张贴此code,但这已经是很长,我希望我的意思是显而易见的。这的部分工作的,但这里的问题是,标题和内容列从未一字排开。我想在TableRows内TextViews使用的getWidth()和setMinimumWidth(),但如这里的onCreate在这一数据是无法访问的(并且也是人迹罕至之内onPostCreate)。我一直无法找到一个方法来得到这个工作,并在这个领域中的解决方案将是美好的!

Secondly, I did try to "force" a solution to this, in that I added two LinearLayouts, one capturing the "Header" part of the grid I want to create, and another for the bottom "content" part of the grid I want to create. I can post this code, but this is already quite long and I'm hoping that what I mean is obvious. This partially worked but the problem here is that the headers and content columns were never lined up. I wanted to use getWidth() and setMinimumWidth() on the TextViews within the TableRows, but as described here this data was inaccessible during onCreate (and was also inaccessible within onPostCreate). I have been unable to find a way to get this to work, and a solution in this realm would be wonderful as well!

如果你做这一步到底,荣誉给你!

If you made it this far to the end, kudos to you!

推荐答案

大约一​​个星期前,我重新审视这个问题,并想出了一个解决方案。该解决方案需要我做大量的手工宽度设置在该网格的列,而我认为这是非常低于标准杆的这个时代。不幸的是,我也继续寻找一个更全面的解决方案,原生Android平台,但我没有拒绝任何事了。

About a week ago I revisited this problem and came up with a solution. The solution requires me to do a lot of manual width setting for the columns in this grid, and I consider that to be extremely sub-par in this day and age. Unfortunately, I have also continued to look for a more well-rounded solution native to the Android platform, but I have not turned anything up.

以下是code来创建此同一电网,若有人跟着我需要它。我将解释下面一些!更多的相关细节。

The following is the code to create this same grid, should any one following me need it. I will explain some of the more pertinent details below!

布局: 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);
    }
}

滚动视图监听器(挂接两个以上): Horizo​​ntalScrollViewListener.java

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

实现这个监听器的滚动型类: ObservableHorizo​​ntalScrollView.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);
       }
   }
}

这样做的真正重要的部分是有点三个方面:

The really important part of this is sort of three-fold:

的ObservableHorizo​​ntalScrollView使得头表和内容表中同步滚动。基本上,这提供了所有的水平运动的网格。 ,其中它们保持对齐的方式是通过检测,就可以在一列最大串。这样做是在年底PopulateMainTable()。尽管我们正在经历的每个TextViews,并把它们添加到该行,你会发现有两个数组 maxFrozenChars maxContentChars 的跟踪是我们所见过的最大的字符串值。在结束时 PopulateMainTable()通过每个行和每个其最小和最大宽度基于所述最大弦我们设置细胞我们循环中,我们在该列中看见。这是由 setChildTextViewWidths 处理。 ,使这项工作的最后一个项目是采用等宽字体。你会发现,在的onCreate 我加载consola.ttf字体,后来将它应用到每个网格的TextViews充当细胞的网格。这让我们有理由相信,该文本将不会被渲染大于我们设定的最小和最大宽度在先行一步。我做花哨的一点点在这里,有什么与整个cellWidthFactor和列的最大尺寸。这是真的,这样较小的字符串将适合的肯定,同时,我们可以最大限度地减少白色空间较大的字符串是(我的系统)不会是全部大写。如果你跑了麻烦用这个和你有那不适合在您设置列大小的字符串,这是在那里你会想要编辑的东西。你想改变一些其他的公式 replacementWidth 变量来确定单元格的宽度,如 50 *宽度[I] 这将是相当大的!但会离开你与空白的一些列一个良好的数额。基本上,这取决于你打算把你的网格是什么​​,这可能需要进行调整。以上是为我工作。 The ObservableHorizontalScrollView allows the header table and the content table to scroll in sync. Basically, this provides all of the horizontal motion for the grid. The way in which they stay aligned is by detecting the largest string that will be in a column. This is done at the end of PopulateMainTable(). While we're going through each of the TextViews and adding them to the rows, you'll notice that there are two arrays maxFrozenChars and maxContentChars that keep track of what the largest string value we've seen is. At the end of PopulateMainTable() we loop through each of the rows and for each of the cells we set its min and max width based on the largest string we saw in that column. This is handled by setChildTextViewWidths. The last item that makes this work is to use a monospaced font. You'll notice that in onCreate I am loading a consola.ttf font, and later applying it to each of the grid's TextViews that act as the cells in the grid. This allows us to be reasonably sure that the text will not be rendered larger than we have set the minimum and maximum width to in the prior step. I am doing a little bit of fanciness here, what with the whole cellWidthFactor and the maximum size of that column. This is really so that smaller strings will fit for sure, while we can minimize the white space for larger strings that are (for my system) not going to be all capital letters. If you ran in to trouble using this and you got strings that did not fit in the column size you set, this is where you would want to edit things. You would want to change the replacementWidth variable with some other formula for determining the cell width, such as 50 * widths[i] which would be quite large! But would leave you with a good amount of whitespace in some columns. Basically, depending on what you plan on putting in your grid, this may need to be tweaked. Above is what worked for me.

我希望这可以帮助别人的未来!

I hope this helps someone else in the future!