Android、Java、Web系、Linux、マラソン等の備忘録

2012/12/08

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

0 件のコメント
マニアックな話なんですけど、

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

0 件のコメント :

コメントを投稿