2011-09-28 105 views
18

我正在開發一個小型Android應用程序。我需要這個android應用程序的一部分是具有水平和垂直滾動能力的網格。但是,最左邊的列需要被凍結(始終在屏幕上,而不是水平滾動的一部分)。同樣,頂部標題行需要被凍結(不垂直滾動的一部分)使用凍結列和凍結標題創建表/網格

這張圖片將有望形容這顯然如果上面沒有什麼太大的意義:

Example of what I would like to do

主要

  1. 白:在所有
  2. 藍不滾動:垂直滾動
  3. 紅:水平滾動
  4. 紫色:滾動垂直和水平

做這些維度之一是很容易的,我已經這樣做了。但是,我無法使這兩個維度都起作用。 (即我可以得到底部全部是藍色的,或者我可以得到正確的部分全部是紅色的,但不完全如上)我的代碼如下,基本上會產生以下內容:

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!

這是個什麼樣子垂直滾動時一樣,需要注意的是你失去了頭!這是個問題!

最後兩點要注意!

首先,我不能相信這不存在的地方已經存在。 (我沒有安卓系統,所以我一直無法查看可能會這樣做的應用程序)。不過,我在StackOverflow和Internet上至少搜索了兩天,尋找GridView或TableLayout的解決方案,這些解決方案將爲我提供我想做的事情,並且還沒有找到解決方案。由於我錯過了它,所以很尷尬,如果有人知道那裏描述如何做到這一點的資源,我將不勝感激!其次,我確實試圖「強制」解決這個問題,因爲我添加了兩個LinearLayouts,一個捕獲我想要創建的網格的「Header」部分,另一個捕獲底部「內容」部分我想創建的網格。我可以發佈這些代碼,但這已經很長了,我希望我的意思很明顯。這部分工作但這裏的問題是標題和內容列從未排隊。我想在TableRows中的TextViews上使用getWidth()和setMinimumWidth(),但如here所述,此數據在onCreate期間無法訪問(並且在onPostCreate內也無法訪問)。我一直無法找到一種方法來實現這個目標,並且在這個領域的解決方案也會很棒!

如果你做到了這一步,到最後,讚美你!

+1

+1,用於很好的寫作 –

+0

@Kevek:「我不能相信這個東西已經不存在了」 - 它可能存在,但我不知道它被封裝爲可重用組件,更不用說可能是一個可重用組件是開源的。 – CommonsWare

+0

@CommonsWare很可能是這種情況,但正如我經常收藏我期望在將來使用的UI控件/算法的書寫,或者認爲對於平臺/語言缺少想法,或許有人會這樣做一樣的,並且之前已經找到了。也許不會! – Kevek

回答

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

實現這個監聽器滾動視圖類: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,這些TextView充當網格中的單元格。這使我們可以合理確信文本不會比我們在上一步中設置的最小和最大寬度大。我在這裏做了一些幻想,整個cellWidthFactor和那個列的最大尺寸。這確實是爲了讓更小的字符串能夠適合,而我們可以最小化大字符串(對於我的系統)不會全部爲大寫字母的空格。如果您遇到麻煩,並且您的字符串不符合您設置的列大小,則這是您想要編輯的地方。你會想改變replacementWidth變量與其他公式,以確定單元格的寬度,如50 * widths[i]這將是非常大的!但是會在某些列中留下大量空白。基本上,根據你打算放入網格的計劃,這可能需要調整。以上是爲我工作的。

我希望這可以幫助別人在未來!

+0

很好的寫作和答案,我面臨類似的問題,像你的。我已經通過你的解決方案,但無法在我的代碼中實現它。你能幫我解決我的問題嗎?這裏是鏈接http://stackoverflow.com/questions/16140735/table-view-look-and-usability-enhancement – kittu88

1

關閉我的頭頂,這是我會怎麼處理這:

1)創建一個方法的接口,你的活動將實施接收滾動座標和你的滾動型可以回調時滾動出現:

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

2)在活動中滾動兩個約束scrollviews的位置實現此主滾動視圖只是滾動到:

@Override 
public void scrollChanged(int newXPos, int newYPos) { 
    mVerticalScrollView.scrollTo(0, newYPos); 
    mHorizontalScrollView.scrollTo(newXPos, 0); 
} 
與新類和呼叫

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:

3)子類滾動型覆蓋onScrollChanged()方法,並添加方法和成員變量回調到活動setScrollCallback(this) in onCreate()

+0

我剛剛重讀了你的問題;你提到你有一個_partially_工作解決方案,並在評論中說你有一個可以工作的方法。如果你有一個解決方案,你應該發佈它,這對遇到同樣問題的人會很有用。 – jvergeldedios

+0

這是*部分*工作解決方案。在這一點上,一切正常,我可以有兩個鏈接滾動視圖(水平)和一個主滾動視圖,但對齊不起作用。也就是說,我真的回到了這個問題,並在前些日子修復它,我計劃發佈解決方案,但我真的希望Android能夠更好地支持我認爲是非常標準的問題。可悲的是,這個平臺似乎缺乏這個平臺。 – Kevek

+0

我在實現你的代碼時遇到了一些問題,你能幫我解決我的問題嗎?這裏是鏈接http://stackoverflow.com/questions/18440471/android-grid-view-concomitant-scrolling-delay –