スキップしてメイン コンテンツに移動

ArrayAdapterで作ったListView上のリスト項目に動的にViewを追加する方法

マニアックな話なんですけど、

ArrayAdapterで作ったListView上に、カスタマイズしたViewを動的に配置したい場合、Android 2.x系にはちょっとクセがあるようで、Android 4.0.3や3.1では表示されるのに、2.2や2.1では表示されず試行錯誤したのでメモしておきます。

例えば、

(特に意味はないですけど)リストに「春、夏、秋、冬」と表示させ、リストをタップすると赤い円を描きたいような場合



ListViewや項目のレイアウトはこんな感じ



CustmView.javaは、

public class CustomView extends View {

    private Paint mPaint;

    public CustomView(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.RED);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 背景透明
        canvas.drawColor(Color.TRANSPARENT);
        // 円の描画
        canvas.drawCircle(100, 20, 20, mPaint);
    }
}

Viewを継承した円を描くだけのシンプルなもの。これをリストタップで追加すべくActivity内のArrayAdapter継承クラスは、

/**
 * ListViewとListのアダプタ
 * @author takaiwa
 *
 */
private class ListAdapter extends ArrayAdapter<SeasonsItem> {

    private LayoutInflater mInflater;

    public ListAdapter(Context context, List<SeasonsItem> objects) {
        super(context, 0, objects);
        this.mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, View convertView, final ViewGroup parent) {

        final SeasonsItem season_item = this.getItem(position);
        if(null == convertView) {
            // 行のXMLレイアウトを取得
            convertView = this.mInflater.inflate(R.layout.list_row, null);
        }

        if(null != season_item) {
            // 季節名の設定
            TextView text_view = (TextView)convertView.findViewById(R.id.textView1);
            text_view.setText(season_item.getName());

            final FrameLayout lyt_list_row =
                (FrameLayout)convertView.findViewById(R.id.FrameLayout1);

            // リストクリックでViewを追加
            convertView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    CustomView custom_view = new CustomView(MainActivity.this);
                    lyt_list_row.addView(custom_view);
                }
            });

        }
        return convertView;
    }
}

これが2.x系で表示されません。ただ、2.x系でもlyt_list_row.addView(new Button(MainActivity.this))のように既存のwidgetだと表示されます。で、同じViewでも何が足りないかというと、

setLayoutParams(new LayoutParams(mWidth, mHeight));

描画するViewのレイアウトサイズが必要。

別に円が描けるサイズ分だけsetLayoutParamsすればいいのですが、せっかくFrameLayoutを使っているので、CustomViewのonDrawで好きな座標へ描画できるのでこのようなイメージにしています。

当たり前ですけど、これがListView上ではなく単なるActivityのLinearLayout上であれば、こんなsetLayoutParams()を指定する必要はないのですが.....

さて、そんなサイズをどこから持ってくるの?という話ですが、以下が考えられると思います
  • 固定値(端末にかかわらず固定の値を予め指定)
  • 表示されているリスト項目のサイズを取得する
固定値の場合、だいたいの値を予測しておくか、描きたい円に合せて指定するのが想定されますが、これだと端末によってはリストの項目が変ったり円が消えたりというリスクもありますね。

じゃあリスト項目のサイズを取得すればいいじゃないかという話ですが、簡単には取得できず難儀しました。以下をActivityのonCreateでListViewへリストをsetAdapterした後に実行します。

private int mListItemWidth, mListItemHeight;

/**
 * ListView項目のサイズを取得
 * @param list_adapter ArrayAdapterを継承しているクラス
 */
private void getListItemSize(ListAdapter list_adapter) {

    // 0:どの項目もサイズが同じという前提で一番最初のリスト項目を取得
    View list_item_view  = list_adapter.getView(0, null, (ListView)findViewById(R.id.listView1));
    list_item_view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
    Log.v(TAG, "list view item width:" + list_item_view.getMeasuredWidth()
                + " height:" + list_item_view.getMeasuredHeight());

    // 画面サイズを取得
    WindowManager windowmanager = (WindowManager)getSystemService(WINDOW_SERVICE);
    Display disp = windowmanager.getDefaultDisplay();
    Log.v(TAG, "display width:" + disp.getWidth() + " height:" + disp.getHeight());

    // ListViewの項目の幅は表示されてる部分?だけなので、画面サイズを採用する
    mListItemWidth = disp.getWidth();
    // TextViewを除いた幅が欲しい場合
//        mListItemWidth = disp.getWidth() - list_item_view.getMeasuredWidth();
    mListItemHeight = list_item_view.getMeasuredHeight();
}


リストをクリックしたタイミングでmListItemWidth, mListItemHeightを参照し、CustomViewに渡して、setLayoutParams()を実行します。

CustomViewのコンストラクタを以下のように書き換えれば良いと思います。

public CustomView2(Context context, int width, int height) {
    super(context);
    mPaint = new Paint();
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setColor(Color.RED);

    // 追加
    setLayoutParams(new LayoutParams(width, height));
}

一連のソースコードはGithubへ


・takaiwa/AddDynamicViewToArrayAdapter · GitHub
https://github.com/takaiwa/AddDynamicViewToArrayAdapter

コメント

このブログの人気の投稿

Javaでprivateなfieldやmethodにアクセスする

JUnitでテストしてると、privateなフィールドにアクセスして、値を参照したりセットしたりしたくなるわけですが、よく使うのでメモしておきます。 例えば、次のような対象のクラスがあるとします。 public class ParentClass { private String hoge = "ParentClass!!"; public void dispMsg() { System.out.println("dispMsg:" + hoge); } private void privateDispMsg(String msg) { System.out.println("dispMsg:" + msg); } }

GolangでWindows GUIアプリケーション

GUIアプリ作成の前提 社内ツールとしてexeで配布 開発環境はGoLandを使う 社内ツールとしてexeを配布ということであれば、Visual StudioでC#による開発だと思います。しかしながら、Go言語を習得したいのと、GoLandの補完機能が便利で、Android Studio使っていたこともあり、とっつきやすいという点からGo縛りでGUIアプリケーションを考えたいと思います。 lxn/walk Windows application library kit for Go. Windows向けしか考えていないので、まずこのライブラリなのですが、ボタンやコンボボックスが思ったように並ばなかったり、手軽にイメージボタンを配置したりなど、簡単にレイアウトを変更できない課題に直面しました。レイアウト作成だけで時間を取られてしまいます。そう言えば、Androidアプリ開発のときは、XMLでデザイン部分を切り離してたのを思い出して、今回の調査の運びとなりました。 fyne-io/fyne Cross platform native GUIs designed for Go based on Material Design. Supports: Linux, macOS, Windows, BSD, iOS and Android. walkと比べると、クロスプラットフォームで作成できるのですが、こちらもコードの中にデザインを書いていく形でした。 therecipe/qt therecipe/qt allows you to write Qt applications entirely in Go or JavaScript. Qt Creatorなるものがあるようで、デザイン部分を切り離せる印象はあるものの、最新のPCでもビルドに時間がかかるようで、学習コストもかかるという記事を見かけて断念しました。 どうしたものかとツイートしましたら、下記のようにご助言をいただいた。 同じ課題に対し色々試した結果、PWAのフルキャッシュドに落ち着いた。中身はGo+WASMで。 https://t.co/e60whDTV16 — のぼのぼ📡 (@nobonobo) July 21, 2020 PWA 早速調べてみました。Googleが進めているプロジェクトで、ネイティブアプリのよ

Eclipseの高速化メモ

Eclipseが重いと一言に言っても、いろいろな工程での話があると思いますが、過去記事のなども含めてこの記事にピックアップしておきたいと思います。以下はWindows環境での話です。