19

我正在开发一个小型 Android 应用程序。我需要这个 android 应用程序的一部分是有一个可以水平和垂直滚动的网格。但是,需要冻结最左边的列(始终在屏幕上,而不是水平滚动的一部分)。同样,需要冻结顶部标题行(不是垂直滚动的一部分)

如果上述内容没有太大意义,这张图片有望清楚地描述这一点:

我想做的事的例子

关键

  1. 白色:根本不滚动
  2. 蓝色:垂直滚动
  3. 红色:水平滚动
  4. 紫色:垂直和水平滚动

做这些维度之一很容易,我已经做到了。但是,我无法让这两个维度都起作用。(即,我可以让底部全部为蓝色,或者我可以让正确的部分全部为红色,但不完全如上)我拥有的代码如下,基本上会产生以下内容:

是)我有的!

结果_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);
    }
}

这是它的样子:

看,头条!

所以这就是水平滚动的样子

呸,没有标题!

这是垂直滚动时的样子,注意你会丢失标题!这是个问题!

最后两点要注意!

首先,我不敢相信这在某个地方不存在。(我没有 Android,所以我无法四处寻找可能会这样做的应用程序)。但是,我已经在 StackOverflow 和整个 Internet 中搜索了至少两天,寻找 GridView 或 TableLayout 的解决方案,这将为我提供我想做的事情,但还没有找到解决方案。尽管我会因为错过它而感到尴尬,但如果有人知道那里有描述如何做到这一点的资源,我将不胜感激!

其次,我确实尝试“强制”解决这个问题,因为我添加了两个线性布局,一个捕获我想要创建的网格的“标题”部分,另一个捕获我想要的网格的底部“内容”部分创造。我可以发布这段代码,但这已经很长了,我希望我的意思很明显。这部分工作,但这里的问题是标题和内容列从未排列。我想在 TableRows 中的 TextViews 上使用 getWidth() 和 setMinimumWidth(),但正如这里所描述的,这些数据在 onCreate 期间无法访问(在 onPostCreate 中也无法访问)。我一直无法找到让它工作的方法,而这个领域的解决方案也很棒!

如果你能走到最后,向你致敬!

4

3 回答 3

9

大约一周前,我重新审视了这个问题并提出了解决方案。该解决方案需要我为此网格中的列进行大量手动宽度设置,我认为这在当今时代非常低于标准。不幸的是,我还继续寻找更全面的 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);
       }
   }
}

真正重要的部分是三方面的:

  1. ObservableHorizo​​ntalScrollView 允许标题表和内容表同步滚动。基本上,这提供了网格的所有水平运动。
  2. 它们保持对齐的方式是检测列中的最大字符串。这是在结束时完成的PopulateMainTable()。当我们遍历每个 TextView 并将它们添加到行中时,您会注意到有两个数组maxFrozenCharsmaxContentChars它们会跟踪我们看到的最大字符串值是多少。最后,PopulateMainTable()我们循环遍历每一行,并根据我们在该列中看到的最大字符串为每个单元格设置其最小和最大宽度。这是由setChildTextViewWidths.
  3. 完成这项工作的最后一项是使用等宽字体。你会注意到onCreate我正在加载一个 consola.ttf 字体,然后将它应用到每个网格的 TextViews 中,这些 TextViews 充当网格中的单元格。这使我们能够合理地确定文本不会呈现大于我们在上一步中设置的最小和最大宽度。我在这里做了一些花哨的事情,整个 cellWidthFactor 和该列的最大大小。这确实是为了确保较小的字符串适合,而我们可以最小化较大字符串(对于我的系统)不会全是大写字母的空格。如果您在使用它时遇到了问题,并且得到的字符串不适合您设置的列大小,那么您可以在此处进行编辑。你会想要改变replacementWidth使用其他一些公式来确定单元格宽度的变量,例如50 * widths[i]哪个会很大!但是会在某些列中留下大量空白。基本上,根据您计划在网格中放置的内容,这可能需要进行调整。以上是对我有用的。

我希望这对将来的其他人有所帮助!

于 2011-10-17T13:33:31.823 回答
4

在这种情况下, TableFixHeaders库可能对您有用。

于 2014-11-13T15:51:45.907 回答
1

在我的脑海中,这就是我将如何处理这个问题:

1)使用您的 Activity 将实现以接收滚动坐标并且您的 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()方法,并添加回调activity的方法和成员变量:

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) 用新类替换 XML 中的库存 ScrollView 并调用setScrollCallback(this).onCreate()

于 2011-10-17T09:38:20.153 回答