19

私は小さなAndroidアプリに取り組んでいます。この Android アプリに必要なものの一部は、水平方向と垂直方向の両方にスクロール可能なグリッドを用意することです。ただし、一番左の列は固定する必要があります (常に画面に表示され、水平スクロールの一部ではありません)。同様に、上部のヘッダー行を固定する必要があります (垂直スクロールの一部ではありません)。

上記があまり意味をなさない場合、この図はこれを明確に説明することを願っています:

やりたいことの例

キー:

  1. 白: まったくスクロールしない
  2. 青: 縦にスクロール
  3. 赤: 横スクロール
  4. 紫: 縦横両方にスクロール

これらの次元の 1 つを実行するのは簡単で、私はそうしました。ただし、これらのディメンションの両方を機能させるのに問題があります。(つまり、下の部分をすべて青にすることも、右の部分をすべて赤にすることもできますが、上記のようにはできません) 私が持っているコードは以下のとおりで、基本的には次のようになります。

私が持っているもの!

結果_グリッド.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);
    }
}

これは次のようになります。

見て、ヘッダー!

少し横スクロールするとこんな感じ

ああ、ヘッダーはありません!

垂直にスクロールすると、このように表示されます。ヘッダーが失われることに注意してください。これは問題です!

最後に 2 つの注意事項があります。

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

次に、2 つの LinearLayouts を追加して、これに対する解決策を「強制」しようとしました。作成します。このコードを投稿することはできますが、これはすでにかなり長いので、私の言いたいことが明らかであることを願っています。これは部分的には機能しましたが、ここでの問題は、ヘッダーとコンテンツの列が一列に並んでいないことです。TableRows 内の TextViews で getWidth() と setMinimumWidth() を使用したかったのですが、ここで説明したように、onCreate 中はこのデータにアクセスできませんでした (onPostCreate 内でもアクセスできませんでした)。これを機能させる方法を見つけることができませんでした。この領域での解決策も素晴らしいでしょう!

最後までここまでたどり着いたのなら、あなたに敬意を表します!

4

3 に答える 3

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

スクロール ビュー リスナー (2 つを接続するため): 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()ます。maxFrozenChars各 TextView を調べて行に追加していると、2 つの配列がありmaxContentChars、これまでに見た最大の文字列値を追跡していることに気付くでしょう。最後にPopulateMainTable()、各行をループし、各セルに対して、その列で見た最大の文字列に基づいて最小幅と最大幅を設定します。これは によって処理されsetChildTextViewWidthsます。
  3. これを機能させる最後の項目は、等幅フォントを使用することです。onCreateで consola.ttf フォントをロードし、後でそれをグリッド内のセルとして機能するグリッドの TextViews のそれぞれに適用していることに気付くでしょう。これにより、前の手順で設定した最小幅と最大幅よりも大きくテキストがレンダリングされないことを合理的に確信できます。ここでは、cellWidthFactor 全体とその列の最大サイズを使って、ちょっとした工夫をしています。これは、小さな文字列が確実に収まるようにするためですが、(私のシステムでは) すべて大文字にならない大きな文字列の空白を最小限に抑えることができます。これを使用して問題が発生し、設定した列サイズに収まらない文字列が得られた場合は、ここで編集する必要があります。あなたは変更したいでしょうreplacementWidthセル幅を決定するための他の数式を使用した変数、たとえば50 * widths[i]非常に大きくなります! ただし、一部の列にはかなりの量の空白が残ります。基本的に、グリッドに何を配置するかによって、これを微調整する必要がある場合があります。上記は私のために働いたものです。

これが将来誰かに役立つことを願っています!

于 2011-10-17T13:33:31.823 に答える
4

この場合、 TableFixHeadersライブラリが役立つ場合があります。

于 2014-11-13T15:51:45.907 に答える
1

私の頭の上から、これは私がこれにアプローチする方法です:

1) アクティビティがスクロール座標を受け取るために実装し、スクロールが発生したときに ScrollView がコールバックできる 1 つのメソッドを使用してインターフェイスを作成します。

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

2) これをアクティビティに実装して、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()

于 2011-10-17T09:38:20.153 に答える