使用冻结列和冻结标题创建表/网格

时间:2011-09-28 16:52:59

标签: android android-layout android-gridview

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

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

Example of what I would like to do

关键

  1. 怀特:不要滚动
  2. 蓝色:垂直滚动
  3. 红色:水平滚动
  4. 紫色:垂直和水平滚动
  5. 要做到这些尺寸中的一个很容易,我已经这样做了。但是,我无法让这两个维度发挥作用。 (也就是说,我可以将底部部​​分全部变为蓝色,或者我可以将正确的部分全部变为红色,但不完全如上所述)我的代码如下所示,基本上会产生以下内容:

    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和Internet上搜索了至少两天,寻找GridView或TableLayout的解决方案,它将为我提供我想做的事情,并且尚未找到解决方案。由于我错过了它的尴尬,如果有人知道那里的资源描述了如何做到这一点,我将不胜感激!

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

    如果你做到这一点到目前为止,对你赞不绝口!

3 个答案:

答案 0 :(得分: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字体,然后将它应用到作为网格中单元格的每个网格的TextView中。这使我们可以合理地确定文本不会比我们在前一步骤中设置的最小和最大宽度更大。我在这里做了一点点的幻想,整个cellWidthFactor以及该列的最大大小。这确实是因为较小的字符串确实适合,而我们可以最小化较大字符串的空白区域(对于我的系统)不是全部大写字母。如果你在使用它时遇到麻烦,并且你得到的字符串不符合你设置的列大小,那么这就是你想要编辑的东西。您可能希望使用其他公式更改replacementWidth变量以确定单元格宽度,例如50 * widths[i],这将非常大!但是在某些列中会留下大量的空白。基本上,根据您计划放入网格的计划,可能需要进行调整。以上是对我有用的。
  4. 我希望将来可以帮助其他人!

答案 1 :(得分:3)

在这种情况下,

TableFixHeaders库可能对您有用。

答案 2 :(得分: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()方法,并添加一个方法和成员变量来回调活动:

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()