Androidアプリ開発

ListViewでスクロール位置を
保持する方法

この記事は約17分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事は Androidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


スクロール位置を保持した状態でListViewを再表示する

ListView を再表示しても、前回選択時のスクロール位置を保持します

ポイント

Andoridアプリの開発で、選択肢をリスト表示する場合に、ListViewを使用することが多いと思います。
リストの一覧から選択後に ListView を再表示させた場合、スクロール位置がリセットされます。
開発者からすると当たり前の動作ですが、選択の度にスクロール位置がリセットされると、操作性が著しく低下します。スクロール位置を保持して状態で、ListViewを再表示する仕組みを実装します。

RecyclerViewでスクロール位置を保持する方法はこちらです↓↓↓

ListView

ListView を使うシーンとして、子画面(ダイアログ)を使用する場合が多いかと思います。
DialogFragment を継承したリスト選択ダイアログをサンプルに、スクロール位置を保持した状態で、ListView を再表示する方法について、説明します。

スクロール位置を取得する

リスト選択ダイアログを呼び出す側とのインタフェースとして、CustomDialogItemListener を用意します。
再表示の際に使用するスクロール位置の初期値は、コンストラクタで引き渡します。
選択時のスクロール位置は、
getFirstVisiblePosition() で取得して、CustomDialogItemListener のアイテム選択をイベント通知します。
リスト選択ダイアログを呼び出す側からは、インスタンス化したリスト選択ダイアログのスクロール位置を直接参照するか、スクロール位置を参照する
Getter を用意します。

public class CustomDialogList extends DialogFragment {
    private CustomDialogItemListener    customDialogItemListener = null;
    private final Context               context;
    public int                          current;
   :
    public CustomDialogList(Context context, int current) {
        this.context = context;
        this.current = current;
    }
    :
        listView.setOnItemClickListener((parent, view, position, id) -> {
            if (customDialogItemListener != null) {
                current = listView.getFirstVisiblePosition();
                customDialogItemListener.doItemClick(parent, view, position, id);
            }
            dismiss();
        });
    :
    public void setCustomDialogItemListener(CustomDialogItemListener customDialogItemListener) {
        this.customDialogItemListener = customDialogItemListener;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        if (customDialogItemListener != null) customDialogItemListener = null;
    }

リスト選択ダイアログのインタフェース(CustomDialogItemListener)

アイテム選択と閉じる操作をイベント通知するためのインタフェース

public interface CustomDialogItemListener extends EventListener {
    void doItemClick(AdapterView<?> parent, View view, int position, long id);
    void doCloseClick(View view);
}

スクロール位置を保持した状態で、ListViewを表示する

保持したスクロール位置を先頭に ListView する場合は、setSelection あるいは、setSelectionFromTop を使用します。
setSelectionFromTop では、先頭行のスクロール位置をピクセル単位で指定できます。
getChildAt()ListView のアイテムを取得し、getTop()ListView からの相対位置を取得します。
サンプルでは、リスト選択ダイアログの最大行表示(
setListViewHeight )と、選択行のテキスト色を変更(後述)しています。

    :
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        :       
        setListViewHeight(listView, max);
        int i = listView.getChildAt(0) != null ? listView.getChildAt(0).getTop() : 0;
        listView.setSelectionFromTop(current, i);
        return dialog;
    }

ListView の最大行表示(setListViewHeight)

指定した行数で ListView の高さを調整します。

    private void setListViewHeight(ListView listView, int max) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) { return; }
        int height = 0;
        int count = Math.min(listAdapter.getCount(), max);
        for (int i = 0 ; i < count ; i++) {
            View listItem = listAdapter.getView(i, null, listView);
            listItem.measure(0, 0);
            height += listItem.getMeasuredHeight();
        }
        float dp = getResources().getDisplayMetrics().density;
        ViewGroup.LayoutParams params = listView.getLayoutParams();
        ViewGroup.MarginLayoutParams  marginLayoutParams = (ViewGroup.MarginLayoutParams) params;
        params.height = height + (int)(marginLayoutParams.topMargin / dp) + (int)(marginLayoutParams.bottomMargin / dp)
                + (int)(listView.getPaddingTop() / dp) + (int)(listView.getPaddingBottom() / dp);
        listView.setLayoutParams(params);
    }

表示できる行数を指定できるListView について、詳しくは↓↓↓

選択行のテキスト色を変える

ListView の表示は ArrayAdapter を使用します。
選択行のテキスト色を変更するには、
getView をオーバライドして、表示行が選択行の場合に、テキスト色を変更したり、テキストのスタイルを変更します。

        :
        ListView listView = dialog.findViewById(R.id.dialog_item);
        ArrayList<String>    arrayList = new ArrayList<>();
        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(getActivity(), R.layout.item_dialog, R.id.item) {
            @SuppressLint("UseCompatLoadingForDrawables")
            @Override
            public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
                if (convertView == null) convertView = LayoutInflater.from(context).inflate(R.layout.item_dialog, parent, false);
                TextView textView = convertView.findViewById(R.id.item);
                textView.setTextColor(position == pos ? context.getColor(R.color.red) : context.getColor(R.color.black));
                textView.setTypeface(position == pos ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
                textView.setText(arrayList.get(position));
                return convertView;
            }
        };
     :
        listView.setAdapter(arrayAdapter);
        :

リスト選択ダイアログの呼び出し

リスト表示するテキストは、タブ区切りでバックし、パーセル( customDialogParcels )を使用して、引き渡します。
Activity
間の遷移がある場合は、Bundle を使用して、スクロール位置を保持します。
Bundle の保持は、SaveCondition RestoreCondition を使用します。

    :
    private int                         current;
    :
    private Bundle SaveCondition(Bundle bundle) {
        bundle.putString("current", String.valueOf(current));
        :
    }
    :
    private void RestoreCondition(Bundle bundle) {
        current = Integer.parseInt(bundle.getString("current"));
        :
    }
    :
   // リストダイアログ 
     String itemLists = itemList[0];
     for (int i = 1; i < itemList.length; i++) {
          itemLists = String.format("%s\t%s", itemLists, itemList[i]);
    }
    Bundle bundle = new Bundle();
    bundle.putString("TITLE", title);
    bundle.putString("MAX", "10");
    bundle.putString("POS", String.valueOf(pos));
    customDialogParcels = new CustomDialogParcel[1];
    customDialogParcels[0] = new CustomDialogParcel("non", 0, 0, itemLists);
    bundle.putParcelableArray("PAC", customDialogParcels);
    CustomDialogList customDialogList = new CustomDialogList(context, current);
    customDialogList.setCustomDialogItemListener(new CustomDialogItemListener() {
                @Override
                public void doItemClick(AdapterView<?> parent, View view, int position, long id) {
                    Bundle bundle;
                    bundle = customDialogList.getArguments();
                    customDialogParcels = (CustomDialogParcel[]) Objects.requireNonNull(bundle).getParcelableArray("PAC");
                    current = customDialogList.current;
                    :
                    }
                }
                public void doCloseClick(View view) {
                    // 処理があれば、ここに記述する
                }
            });
    fragmentManager = getSupportFragmentManager();
    customDialogList.setArguments(bundle);
    customDialogList.show(fragmentManager, title);
  : 

パーセル( customDialogParcels )

アクティビティ(またはフラグメント)からダイアログを呼び出す際、値や属性などを構造化したパーセルにして引き渡す実装がスマートです。
構造化する部分は、用途に応じてカスタムします。

public class CustomDialogParcel implements Parcelable {
    private String property;
    private int type;
    private int length;
    private String value;

    // コンストラクタ //
    public CustomDialogParcel() { }
    public CustomDialogParcel(String property, int type, int length, String value) {
        this.property = property;
        this.type = type;
        this.length = length;
        this.value = value;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(property);
        dest.writeInt(type);
        dest.writeInt(length);
        dest.writeString(value);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    protected CustomDialogParcel(Parcel in) {
        property = in.readString();
        type = in.readInt();
        length = in.readInt();
        value = in.readString();
    }

    public static final Creator<CustomDialogParcel> CREATOR = new Creator<CustomDialogParcel>() {
        @Override
        public CustomDialogParcel createFromParcel(Parcel in) {
            return new CustomDialogParcel(in);
        }
        @Override
        public CustomDialogParcel[] newArray(int size) {
            return new CustomDialogParcel[size];
        }
    };

    public String getProperty() {
        return property;
    }
    public String getValue() {
        return value;
    }
    public int getType() {
        return type;
    }
    public int getLength() {
        return length;
    }
    public void setType(int type) {
        this.type = type;
    }
    public void setLength(int length) {
        this.length = length;
    }
    public void setValue(String value) {
        this.value = value;
    }
}

今回は、ここまでです。

ListViewでスクロール位置を保持する方法 を使用している Androidアプリです。

誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にある
コメントか、
こちら
から、お願いいたします♪

ポチッとして頂けると、
次のコンテンツを作成する励みになります♪

ブログランキング・にほんブログ村へ

これからAndroidのアプリ開発やJavaでの開発を始めたい方へ

初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。

未経験者からシステムエンジニアを目指すのに最適かと、
まずは無料相談から♪

未経験者からプログラマーを目指すのに最適かと、
まずは無料カウンセリングから♪

カリキュラムとサポートがしっかりしています、
お得なキャンペーンとかいろいろやっています♪

ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、
まずは無料オンライン相談から♪

参考になったら、💛をポッチとしてね♪