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

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

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

我想做什么的例子

关键

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

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

我拥有的!

result_grid.xml:

            

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 content; // [Removed Code] Here I get some data from getIntent().getExtras() that will populate the content ArrayList PopulateMainTable(content); } private void PopulateMainTable(ArrayList 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的解决方案,它将为我提供我想做的事情,并且尚未find解决方案。 由于我错过了它的尴尬,如果有人知道有一个描述如何做到这一点的资源,我将不胜感激!

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

如果你把它做到这一点到最后,那就是你的荣耀!

大约一个星期前,我重新审视了这个问题,并想出了一个解决方案。 该解决方案要求我为此网格中的列进行大量手动宽度设置,并且我认为这在当今时代非常低。 不幸的是,我还在继续寻找Android平台原生的更全面的解决方案,但我没有做任何事情。

以下是创建同一网格的代码,如果有人跟随我需要它。 我将在下面解释一些更相关的细节!

布局: grid.xml

                   

活动: 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 content; Bundle myBundle = getIntent().getExtras(); try { content = (ArrayList) myBundle.get("gridData"); } catch (Exception e) { content = new ArrayList(); content.add(new String[] {"Error", "There was an error parsing the result data, please try again"} ); e.printStackTrace(); } PopulateMainTable(content); } protected void PopulateMainTable(ArrayList 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以及该列的最大大小。 这确实是因为较小的字符串确实适合,而我们可以最小化较大字符串的空白区域(对于我的系统)不会是全部大写字母。 如果你在使用它时遇到麻烦,并且你得到的字符串不符合你设置的列大小,那么这就是你想要编辑的东西。 您可能希望使用其他一些公式更改replacementWidthvariables以确定单元格宽度,例如50 * widths[i]这将是非常大的! 但是在某些列中会留下大量的空白。 基本上,根据您计划放入网格的计划,可能需要进行调整。 以上是对我有用的。

我希望将来可以帮助别人!

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

在我的头脑中,这就是我接近这个的方法:

1)使用一个方法创建一个接口,您的Activity将实现该方法以接收滚动坐标,并且ScrollView可以在滚动发生时回调:

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

2)在您的活动中实现此function,将两个受约束的滚动视图滚动到主滚动视图刚刚滚动到的位置:

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

3)子类ScrollView覆盖onScrollChanged()方法,并添加一个方法和成员variables来回调活动:

 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,并在onCreate()调用setScrollCallback(this) onCreate()