2011-09-28 11 views
18

私は小さなAndroidアプリを開発中です。このアンドロイドアプリに必要なものの1つは、水平方向と垂直方向にスクロール可能なグリッドを持つことです。ただし、一番左の列はフリーズする必要があります(常に画面上で、水平スクロールの一部ではありません)。フリーズしたカラムとフリーズしたヘッダを使ってテーブル/グリッドを作成する

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!

で次のようになります!これは問題だ!

最後に2つ注意してください!

まずは、これは既にどこかに存在しないとは思えません。 (私はAndroidを所有していないので、これを行う可能性のあるアプリを探すことはできませんでした)。しかし、私はStackOverflowとインターネットで少なくとも2日間、GridViewまたはTableLayoutのソリューションを探しています。これは私が何をしたいのかを提供し、まだ解決策を見つけることはありません。私はそれを逃したためになると恥ずかしいように、誰かがこれを行う方法を説明するリソースを知っていれば、私は感謝します!

2つ目は、作成したいグリッドの「ヘッダー」部分をキャプチャするLinearLayoutsと、作成したいグリッドの「ヘッダー」部分をキャプチャする2つのLinearLayoutsを追加して、ソリューションを「強制」しようとしました。私が作成したいグリッド。私はこのコードを投稿することができますが、これはすでにかなり長いので、私の言うことが明らかであることを望んでいます。このは部分的にで処理されましたが、ここでの問題はヘッダーとコンテンツの列が一列に並べられていないことです。私は、TableRows内のTextViewsでgetWidth()とsetMinimumWidth()を使用したいと考えていましたが、hereのように、このデータはonCreateではアクセスできませんでした(またonPostCreateではアクセスできませんでした)。私はこれを動作させる方法を見つけることができず、この分野の解決策も素晴らしいでしょう!

これまでにこれを作成していれば、あなたに誇り!私の頭の上オフ

+1

+1の素敵な書き込みのために –

+0

@Kevek:「これは既にどこかに存在しないとは思えません」 - それは存在する可能性がありますが、再利用可能なコンポーネントとしてパッケージ化されていることは知らないオープンソースです。 – CommonsWare

+0

@CommonsWareこれは非常によく似ているかもしれませんが、将来私が使用すると思われるUIコントロール/アルゴリズムのブックマークをブックマークしたり、プラットフォーム/言語ではないと思っています。同じ、これを以前に見つけました。そうでないかもしれない! – Kevek

答えて

9

約1週間前に私はこの問題を再訪し、解決策を思いつきました。このソリューションでは、このグリッドの列の手動幅の設定を多くする必要があります。この日および年齢では、非常にサブパールになると考えています。残念なことに、私は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); 
     } 
    } 
} 

本の本当に重要な部分は、3倍の一種である:

  1. ObservableHorizo​​ntalScrollViewは、ヘッダテーブルおよびコンテンツテーブルをスクロールすることができますに同期中。基本的に、これはグリッドのすべての水平移動を提供します。
  2. 列が一致する方法は、列内にある最大の文字列を検出することです。これはPopulateMainTable()の最後に行われます。それぞれのTextViewを処理して行に追加しているうちに、最大の文字列値が何であるかを把握している2つの配列maxFrozenCharsmaxContentCharsがあることがわかります。 PopulateMainTable()の最後に、各行をループし、各セルについて、その列で見た最大の文字列に基づいて最小幅と最大幅を設定します。これはsetChildTextViewWidthsによって処理されます。
  3. この作業を行う最後の項目は、モノスペースフォントを使用することです。 onCreateでconsola.ttfフォントを読み込み、後でグリッドのセルとして機能する各グリッドのTextViewに適用していることがわかります。これにより、テキストが前のステップで最小と最大の幅に設定されているよりも大きくレンダリングされないことを合理的に確かめることができます。私はここで少し気が利いています。cellWidthFactor全体とその列の最大サイズはどういう意味ですか?これは実際には小さな文字列が確実に収まるようにするためですが、大文字ではない大きな文字列の空白を最小限に抑えることができます。これを使用して問題が発生した場合、設定したカラムサイズに合わない文字列がある場合は、これを編集したい場所です。 50 * widths[i]など、セル幅を決定するための式を使用してreplacementWidth変数を変更したい場合があります。しかし、いくつかの欄には空白を残しておきます。基本的には、グリッドを配置する予定に応じて、これを微調整する必要があります。上記は私のために働いたものです。

今後これが他の人に役立つことを祈っています!

+0

良いwriteupと答え、私はあなたのような同様の問題に直面している。私はあなたのソリューションを通過したが、私のコードで実装することができませんでした。私の問題で私を助けてもらえますか?ここにリンクhttp://stackoverflow.com/questions/16140735/table-view-look-and-usability-enhancementがあります – kittu88

1

、これは私がこのアプローチする方法を次のとおりです。)

1を、あなたの活動は、スクロール座標を受信すると、あなたのScrollViewは、ときに戻って呼び出すことができることを実装してひとつの方法とのインタフェースを作成します。スクロールが発生します。

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

2)メインscrollviewがちょうどにスクロールしたことを位置に2制約scrollviewsをスクロールするには、あなたの活動でこれを実装します。

@Override 
public void scrollChanged(int newXPos, int newYPos) { 
    mVerticalScrollView.scrollTo(0, newYPos); 
    mHorizontalScrollView.scrollTo(newXPos, 0); 
} 
新しいクラスおよび呼び出しを使用してXMLに置き換え株式ScrollViewを

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

3)サブクラスScrollViewはonScrollChanged()メソッドをオーバーライドして、戻って活動するために呼び出すメソッドとメンバ変数を追加しますsetScrollCallback(this)onCreate()に設定します。

+0

私はあなたの質問を読み直しました。 _partially_作業中の解決策があると言いますが、そのコメントでは、あなたが働くアプローチについて考えていると言います。あなたがそれを投稿したはずの解決策があれば、同じ問題を抱えている人にとっては役に立ちます。 – jvergeldedios

+0

これは*部分的に*動作する解決策です。その中で、すべてが機能し、2つのリンクされたスクロールビュー(水平方向)と1つのメインスクロールビューを持つことができますが、アラインメントは機能しません。それは、私が実際にこの問題に戻ってきて、先日それを修正して解決策を投稿する予定だが、Androidが非常に標準的な問題と考えているものをより良くサポートすることを本当に期待していた。悲しいことに、プラットフォームはこの分野では不足しているようです。 – Kevek

+0

私はあなたのコードを実装することにいくつか問題があります、あなたは私の問題で私を助けてくれますか?ここでリンクhttp://stackoverflow.com/questions/18440471/android-grid-view-concomitant-scrolling-delay –

関連する問題