web-dev-qa-db-ja.com

RecyclerViewアイテムの展開/折りたたみ

より多くの情報を表示するために、自分のrecyclerViewの項目を展開/折りたたみたいです。 SlideExpandableListView と同じ効果を得たいのですが。

基本的に私のviewHolderには見えないビューがあり、可視性をVISIBLE/GONEのみに設定するのではなくスムーズな展開/折りたたみアニメーションを行いたいと思います。一度に展開する必要があるのはアイテムだけです。アイテムが選択されていることを示すためにいくつかの立面図を表示しておくといいでしょう。

これは新しいAndroidの最近の通話履歴リストと同じ効果です。オプション「CALL BACK」および「DETAILS」は、項目が選択されているときにのみ表示されます。

RecyclerView expandable items

131
stanete

この効果のためにライブラリを使用しないでください。代わりにGoogle I/Oに従って推奨される方法を使用してください。 recyclerViewのonBindViewHolderメソッドでこれを行います。

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1:position;
        TransitionManager.beginDelayedTransition(recyclerView);
        notifyDataSetChanged();
    }
});
  • ここでdetailsは私の意見です。あなたの場合は連絡先の詳細が表示されます。
  • mExpandedPositionは-1に初期化されたint変数です。

そしてあなたが望んでいた素晴らしい効果のために、あなたのlist_item属性としてこれらを使用してください:

Android:background="@drawable/comment_background"
Android:stateListAnimator="@animator/comment_selection"

comment_backgroundは次のとおりです。

<selector
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:constantSize="true"
Android:enterFadeDuration="@Android:integer/config_shortAnimTime"
Android:exitFadeDuration="@Android:integer/config_shortAnimTime">

<item Android:state_activated="true" Android:drawable="@color/selected_comment_background" />
<item Android:drawable="@color/comment_background" />
</selector>

そしてcomment_selectionは次のとおりです。

<selector xmlns:Android="http://schemas.Android.com/apk/res/Android">

<item Android:state_activated="true">
    <objectAnimator
        Android:propertyName="translationZ"
        Android:valueTo="@dimen/z_card"
        Android:duration="2000"
        Android:interpolator="@Android:interpolator/fast_out_slow_in" />
</item>

<item>
    <objectAnimator
        Android:propertyName="translationZ"
        Android:valueTo="0dp"
        Android:duration="2000"
        Android:interpolator="@Android:interpolator/fast_out_slow_in" />
</item>
</selector>
173
Heisenberg

これが最善の方法であると言っていないが、それは私のために働くようです。

完全なコードは、次の場所にあります。コード例: https://github.com/dbleicher/recyclerview-grid-quickreturn

最初に、セル/アイテムのレイアウトに拡張領域を追加して、囲むセルのレイアウトをanimateLayoutChanges = "true"にします。これにより、展開/縮小が確実にアニメーション化されます。

<LinearLayout
    Android:id="@+id/llCardBack"
    Android:layout_width="fill_parent"
    Android:layout_height="wrap_content"
    Android:background="@Android:color/white"
    Android:animateLayoutChanges="true"
    Android:padding="4dp"
    Android:orientation="vertical">

    <TextView
        Android:id="@+id/tvTitle"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:layout_gravity="center|fill_horizontal"
        Android:padding="10dp"
        Android:gravity="center"
        Android:background="@Android:color/holo_green_dark"
        Android:text="This is a long title to show wrapping of text in the view."
        Android:textColor="@Android:color/white"
        Android:textSize="16sp" />

    <TextView
        Android:id="@+id/tvSubTitle"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:layout_gravity="center|fill_horizontal"
        Android:background="@Android:color/holo_purple"
        Android:padding="6dp"
        Android:text="My subtitle..."
        Android:textColor="@Android:color/white"
        Android:textSize="12sp" />

    <LinearLayout
        Android:id="@+id/llExpandArea"
        Android:visibility="gone"
        Android:layout_width="fill_parent"
        Android:layout_height="wrap_content"
        Android:gravity="center"
        Android:orientation="horizontal">

        <TextView
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:layout_margin="6dp"
            Android:text="Item One" />

        <TextView
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:layout_margin="6dp"
            Android:text="Item Two" />

    </LinearLayout>
</LinearLayout>

次に、クリックされているアイテムに対処できるように、RVアダプタクラスにView.OnClickListenerを実装させます。 1つの展開ビューの位置を保持するためのintフィールドを追加し、それを負の値に初期化します。

private int expandedPosition = -1;

最後に、ViewHolder、onBindViewHolder()メソッドを実装し、onClick()メソッドをオーバーライドします。 onBindViewHolderの位置が "expansionPosition"と等しい場合はビューを拡大し、そうでない場合は非表示にします。 onClickリスナでexpansionPositionの値を設定します。

@Override
public void onBindViewHolder(RVAdapter.ViewHolder holder, int position) {

    int colorIndex = randy.nextInt(bgColors.length);
    holder.tvTitle.setText(mDataset.get(position));
    holder.tvTitle.setBackgroundColor(bgColors[colorIndex]);
    holder.tvSubTitle.setBackgroundColor(sbgColors[colorIndex]);

    if (position == expandedPosition) {
        holder.llExpandArea.setVisibility(View.VISIBLE);
    } else {
        holder.llExpandArea.setVisibility(View.GONE);
    }
}

@Override
public void onClick(View view) {
    ViewHolder holder = (ViewHolder) view.getTag();
    String theString = mDataset.get(holder.getPosition());

    // Check for an expanded view, collapse if you find one
    if (expandedPosition >= 0) {
        int prev = expandedPosition;
        notifyItemChanged(prev);
    }
    // Set the current position to "expanded"
    expandedPosition = holder.getPosition();
    notifyItemChanged(expandedPosition);

    Toast.makeText(mContext, "Clicked: "+theString, Toast.LENGTH_SHORT).show();
}

/**
 * Create a ViewHolder to represent your cell layout
 * and data element structure
 */
public static class ViewHolder extends RecyclerView.ViewHolder {
    TextView tvTitle;
    TextView tvSubTitle;
    LinearLayout llExpandArea;

    public ViewHolder(View itemView) {
        super(itemView);

        tvTitle = (TextView) itemView.findViewById(R.id.tvTitle);
        tvSubTitle = (TextView) itemView.findViewById(R.id.tvSubTitle);
        llExpandArea = (LinearLayout) itemView.findViewById(R.id.llExpandArea);
    }
}

これは、レイアウトの変更にシステムデフォルトのアニメーションを使用して、一度に1つのアイテムだけを拡張する必要があります。少なくともそれは私のために働く。それが役に立てば幸い。

68
MojoTosh

このためには、複雑ではなく単純な行だけが必要でした。

onBindViewHolderメソッドに以下のコードを追加します。

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1:position;
        notifyItemChanged(position);
    }
});

mExpandedPositionは、-1に初期化されたintグローバル変数です。

1つのアイテムだけを展開したい人、その他の人は折りたたむ人のために。これを使う

最初にpreviousExpandedPosition = -1でグローバル変数を宣言します

それから

    final boolean isExpanded = position==mExpandedPosition;
    holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
    holder.itemView.setActivated(isExpanded);

    if (isExpanded)
       previousExpandedPosition = position;

    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mExpandedPosition = isExpanded ? -1:position;
            notifyItemChanged(previousExpandedPosition);
            notifyItemChanged(position);
        }
    });

完了しました!!!。シンプルで控え目な.. :)

63

Gradleサポート付きのライブラリを使用するのは非常に簡単です: https://github.com/cachapa/ExpandableLayout

図書館の資料から。

<net.cachapa.expandablelayout.ExpandableLinearLayout
    Android:id="@+id/container"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    app:el_duration="1000"
    app:el_expanded="true">

    <TextView
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:text="Click here to toggle expansion" />

    <TextView
        Android:layout_width="match_parent"
        Android:layout_height="50dp"
        Android:text="Fixed height"
        app:layout_expandable="true" />

 </net.cachapa.expandablelayout.ExpandableLinearLayout>

展開可能ビューにマークを付けたら、コンテナ上の次のメソッドのいずれかを呼び出します。expand()collapse()、またはtoggle()

14
Laurențiu Onac

サードパーティのライブラリを使用する必要はまったくありません。 Google I/O 2016で示された方法のlittle Tweakと、このトピックのハイゼンベルクがトリックを行います。

notifyDataSetChanged()完全なRecyclerViewを再描画するので、ViewHolderの位置とViewHolderがあるため、notifyDataItemChanged()がより良いオプションです(最良ではありません)処分、およびnotifyDataItemChanged()のみ特定のViewHolderを特定の位置に再描画します

しかし、問題は、クリックするとCardViewが早まって消えてしまい、notifyDataItemChanged()を使用してもその出現が除去されないことです。

次のコードしないnotifyDataSetChanged()またはnotifyDataItemChanged()に依存し、API 23でテストされ、各ViewHolderに_ [VARIABLE]があるRecyclerViewで使用すると、チャームのように動作します_ルート要素として:

holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            final boolean visibility = holder.details.getVisibility()==View.VISIBLE;

            if (!visibility)
            {
                holder.itemView.setActivated(true);
                holder.details.setVisibility(View.VISIBLE);
                if (prev_expanded!=-1 && prev_expanded!=position)
                {
                    recycler.findViewHolderForLayoutPosition(prev_expanded).itemView.setActivated(false);
                    recycler.findViewHolderForLayoutPosition(prev_expanded).itemView.findViewById(R.id.cpl_details).setVisibility(View.GONE);
                }
                prev_expanded = position;
            }
            else
            {
                holder.itemView.setActivated(false);
                holder.details.setVisibility(View.GONE);
            }
            TransitionManager.beginDelayedTransition(recycler);              
        }
});

prev_positionは、-1に初期化されたグローバル整数です。 detailsは完全なビューであり、展開すると表示され、折りたたむと非表示になります。

前述のように、ViewHolderのルート要素はCardViewであり、foreground属性とstateListAnimator属性は、このトピックに関してハイゼンベルクが正確に述べたとおりに定義されています。

PDATE:上記のデモンストレーションは、以前に展開されたアイテムのいずれかが展開されている場合、折りたたまれます。この動作を変更し、別のアイテムが展開されている場合でも展開されたアイテムをそのまま保持するには、次のコードが必要です。

if (row.details.getVisibility()!=View.VISIBLE)
    {
        row.details.setVisibility(View.VISIBLE);
        row.root.setActivated(true);
        row.details.animate().alpha(1).setStartDelay(500);
    }
    else
    {
        row.root.setActivated(false);
        row.details.setVisibility(View.GONE);
        row.details.setAlpha(0);
    }
    TransitionManager.beginDelayedTransition(recycler);

PDATE:リストの最後のアイテムを展開すると、展開された部分が画面の下に表示されるため、完全に表示されない場合があります。画面内のアイテム全体を取得するには、次のコードを使用します。

LinearLayoutManager manager = (LinearLayoutManager) recycler.getLayoutManager();
    int distance;
    View first = recycler.getChildAt(0);
    int height = first.getHeight();
    int current = recycler.getChildAdapterPosition(first);
    int p = Math.abs(position - current);
    if (p > 5) distance = (p - (p - 5)) * height;
    else       distance = p * height;
    manager.scrollToPositionWithOffset(position, distance);

重要:上記のデモンストレーションが機能するためには、RecyclerViewとそのLayoutManagerのインスタンスをコード内に保持する必要があります(柔軟性のために後者)

元の質問が投稿されてから長い時間が経ったことを私は知っています。しかし、私のような遅い人には、@ Heisenbergの答えを少し説明しておくと役立つと思います。

アダプタクラスの2つの変数を次のように宣言します。

private int mExpandedPosition= -1;
private RecyclerView recyclerView = null;

次にonBindViewHolderを元の答えで与えられているように続けて。

      // This line checks if the item displayed on screen 
      // was expanded or not (Remembering the fact that Recycler View )
      // reuses views so onBindViewHolder will be called for all
      // items visible on screen.
    final boolean isExpanded = position==mExpandedPosition;

        //This line hides or shows the layout in question
        holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);

        // I do not know what the heck this is :)
        holder.itemView.setActivated(isExpanded);

        // Click event for each item (itemView is an in-built variable of holder class)
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

 // if the clicked item is already expaned then return -1 
//else return the position (this works with notifyDatasetchanged )
                mExpandedPosition = isExpanded ? -1:position;
    // fancy animations can skip if like
                TransitionManager.beginDelayedTransition(recyclerView);
    //This will call the onBindViewHolder for all the itemViews on Screen
                notifyDataSetChanged();
            }
        });

そして最後に、アダプタオーバーライドでrecyclerViewオブジェクトを取得する

@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);

    this.recyclerView = recyclerView;
}

お役に立てれば。

6
shahroz butt

RecyclerView expand/collapse itemsRecyclerViewにある拡張可能/折りたたみ可能なアイテムを実装する推奨方法を使用した後、 HeisenBerg によって回答され、いくつかの顕著なアーティファクトを見ましたRecyclerViewTransitionManager.beginDelayedTransition(ViewGroup)を呼び出して更新され、その後notifyDatasetChanged()を呼び出すたびに。

彼の元の答え:

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1 : position;
        TransitionManager.beginDelayedTransition(recyclerView);
        notifyDataSetChanged();
    }
});

変更:

final boolean isExpanded = position == mExpandedPosition;
holder.details.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (mExpandedHolder != null) {
            mExpandedHolder.details.setVisibility(View.GONE);
            notifyItemChanged(mExpandedPosition);
        }
        mExpandedPosition = isExpanded ? -1 : holder.getAdapterPosition();
        mExpandedHolder = isExpanded ? null : holder;
        notifyItemChanged(holder.getAdapterPosition());
    }
}
  • detailsは、アイテムの展開/折りたたみ時に表示/非表示にするビューです
  • mExpandedPositionintであり、展開されたアイテムを追跡します
  • mExpandedHolderは、アイテムの折りたたみ時に使用されるViewHolderです

メソッドTransitionManager.beginDelayedTransition(ViewGroup)notifyDataSetChanged()notifyItemChanged(int)に置き換えられ、特定のアイテムといくつかの小さな調整を対象にしていることに注意してください。

変更後、以前の不要な効果はなくなります。ただし、これは完璧なソリューションではない場合があります。それは私が望んだことだけを行い、目障りなものを排除しました。

:: EDIT ::

明確にするために、mExpandedPositionmExpandedHolderは両方ともグローバルです。

6
Chan Teck Wei

OnClickリスナをViewHolderクラスに設定した後で、次の手順を実行します。

@Override
    public void onClick(View v) {
        final int originalHeight = yourLinearLayout.getHeight();
        animationDown(YourLinearLayout, originalHeight);//here put the name of you layout that have the options to expand.
    }

    //Animation for devices with KitKat and below
    public void animationDown(LinearLayout billChoices, int originalHeight){

        // Declare a ValueAnimator object
        ValueAnimator valueAnimator;
        if (!billChoices.isShown()) {
            billChoices.setVisibility(View.VISIBLE);
            billChoices.setEnabled(true);
            valueAnimator = ValueAnimator.ofInt(0, originalHeight+originalHeight); // These values in this method can be changed to expand however much you like
        } else {
            valueAnimator = ValueAnimator.ofInt(originalHeight+originalHeight, 0);

            Animation a = new AlphaAnimation(1.00f, 0.00f); // Fade out

            a.setDuration(200);
            // Set a listener to the animation and configure onAnimationEnd
            a.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    billChoices.setVisibility(View.INVISIBLE);
                    billChoices.setEnabled(false);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
            // Set the animation on the custom view
            billChoices.startAnimation(a);
        }
        valueAnimator.setDuration(200);
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer value = (Integer) animation.getAnimatedValue();
                billChoices.getLayoutParams().height = value.intValue();
                billChoices.requestLayout();
            }
        });


        valueAnimator.start();
    }
}

私はそれが助けになるべきだと思う、それは私が実装した方法であり、最近のコールビューでグーグルがするのと同じことをする。

3
Rensodarwin

ExpandableLayoutを使うことができます。 https://github.com/KyoSherlock/ExpandableLayout

このExpandableLayoutはスムーズな展開/折りたたみアニメーションCheckBoxのようなものであるため、任意の場所(ListViewまたはRecyclerView)で使用できます。

3
user2914737
//Global Variable

private int selectedPosition = -1;

 @Override
    public void onBindViewHolder(final CustomViewHolder customViewHolder, final int i) {

        final int position = i;
        final GetProductCatalouge.details feedItem = this.postBeanses.get(i);
        customViewHolder.lly_main.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                selectedPosition = i;
                notifyDataSetChanged();
            }
        });
        if (selectedPosition == i) {

          if (customViewHolder.lly_hsn_code.getVisibility() == View.VISIBLE) {

            customViewHolder.lly_hsn_code.setVisibility(View.GONE);
            customViewHolder.lly_sole.setVisibility(View.GONE);
            customViewHolder.lly_sole_material.setVisibility(View.GONE);

        } else {

            customViewHolder.lly_hsn_code.setVisibility(View.VISIBLE);
            customViewHolder.lly_sole.setVisibility(View.VISIBLE);
            customViewHolder.lly_sole_material.setVisibility(View.VISIBLE);
        }


        } else {
            customViewHolder.lly_hsn_code.setVisibility(View.GONE);
            customViewHolder.lly_sole.setVisibility(View.GONE);
            customViewHolder.lly_sole_material.setVisibility(View.GONE);
        }
}

enter image description here

1
Keshav Gera

RVAdapterには2つのビュータイプを使用してください。 1つは拡張レイアウト用、もう1つは折りたたみ用です。そしてRecyclerViewAndroid:animateLayoutChanges="true"を設定することで魔法が起こります this video

0
penduDev