web-dev-qa-db-fra.com

Effet de glissement vers le bas sur ExpandableListView

Est-il possible d'avoir un bel effet de glissement vers le haut/bas lors de l'expansion/réduction d'un élément d'une ExpandableListView?

Si oui, comment?

Merci d'avance.

31
thomaus

Il s'agit donc d'un double complet de this . En bref, j'ai utilisé une liste régulière, créé ma propre vue déroulante, utilisé une animation déroulante personnalisée et voila le succès (regardez le lien pour plus de description).

Edit: Guide étape par étape:

Je crée d'abord le list_row xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:id="@+id/row_parent"
    Android:orientation="vertical">
    <RelativeLayout 
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:id="@+id/row_simple_parent"
        >
    <View
        Android:id="@+id/row_simple_parent_invis_cover"
        Android:visibility="gone"
        Android:layout_height="something that fills out your content"
        Android:layout_width="match_parent"
        Android:background="@Android:color/transparent"/>
    </RelativeLayout>

    <!-- Dropdown -->
    <RelativeLayout 
        Android:id="@+id/row_dropdown"
        Android:layout_height="wrap_content"
        Android:layout_width="match_parent">
    </RelativeLayout>
</LinearLayout>

L'animation de la liste déroulante est la suivante:

import Android.app.Activity;
import Android.util.DisplayMetrics;
import Android.view.View;
import Android.view.View.MeasureSpec;
import Android.view.animation.Animation;
import Android.view.animation.Transformation;

/**
 * Class for handling collapse and expand animations.
 * @author Esben Gaarsmand
 *
 */
public class ExpandCollapseAnimation extends Animation {
    private View mAnimatedView;
    private int mEndHeight;
    private int mStartVisibility;

    /**
     * Initializes expand collapse animation. If the passed view is invisible/gone the animation will be a drop down, 
     * if it is visible the animation will be collapse from bottom
     * @param view The view to animate
     * @param duration
     */ 
    public ExpandCollapseAnimation(View view, int duration) {
        setDuration(duration);
        mAnimatedView = view;
        mEndHeight = mAnimatedView.getLayoutParams().height;
        mStartVisibility = mAnimatedView.getVisibility();
        if(mStartVisibility == View.GONE || mStartVisibility == View.INVISIBLE) {
            mAnimatedView.setVisibility(View.VISIBLE);
            mAnimatedView.getLayoutParams().height = 0;
        }
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        if (interpolatedTime < 1.0f) {
            if(mStartVisibility == View.GONE || mStartVisibility == View.INVISIBLE) {
                mAnimatedView.getLayoutParams().height = (int) (mEndHeight * interpolatedTime);
            } else {
                mAnimatedView.getLayoutParams().height = mEndHeight - (int) (mEndHeight * interpolatedTime);
            }
            mAnimatedView.requestLayout();
        } else {
            if(mStartVisibility == View.GONE || mStartVisibility == View.INVISIBLE) {
                mAnimatedView.getLayoutParams().height = mEndHeight;
                mAnimatedView.requestLayout();
            } else {
                mAnimatedView.getLayoutParams().height = 0;
                mAnimatedView.setVisibility(View.GONE);
                mAnimatedView.requestLayout();
                mAnimatedView.getLayoutParams().height = mEndHeight;
            }
        }
    }

    /**
     * This methode can be used to calculate the height and set itm for views with wrap_content as height. 
     * This should be done before ExpandCollapseAnimation is created.
     * @param activity
     * @param view
     */
    public static void setHeightForWrapContent(Activity activity, View view) {
        DisplayMetrics metrics = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);

        int screenWidth = metrics.widthPixels;

        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY);

        view.measure(widthMeasureSpec, heightMeasureSpec);
        int height = view.getMeasuredHeight();
        view.getLayoutParams().height = height;
    }
}

Puis à l'intérieur de mon adaptateur (vous ajouterez bien sûr plus de syntaxe, et si vous souhaitez que la liste déroulante ne se ferme pas lorsqu'elle n'est pas visible dans la liste, vous devez vous en souvenir dans le support avec une sorte de paramètre également):

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final ViewHolder holder;
    if(convertView == null) {
        // setup holder
        holder = new ViewHolder();
        convertView = mInflater.inflate(R.layout.list_row, null);
        holder.mDropDown = convertView.findViewById(R.id.row_dropdown);
        convertView.setTag(holder);
    } else {
        // get existing row view
        holder = (ViewHolder) convertView.getTag();
    }
    holder.mDropDown.setVisibility(View.GONE);
    return convertView;
}

Ensuite, la magie opère dans vos listes surItemClick:

@Override
public void onListItemClick(ListView list, View view, int position, long id) {
    final ListItem item = (ListItem) list.getAdapter().getItem(position);
    // set dropdown data
    ViewHolder holder = (ViewHolder) view.getTag();
    final View dropDown = holder.mDropDown;

    // set click close on top part of view, this is so you can click the view
    // and it can close or whatever, if you start to add buttons etc. you'll loose
    // the ability to click the view until you set the dropdown view to gone again.
    final View simpleView = view.findViewById(R.id.row_simple_parent_invis_cover);
    simpleView.setVisibility(View.VISIBLE);

    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == 0) {
                // first we measure height, we need to do this because we used wrap_content
                // if we use a fixed height we could just pass that in px.
                ExpandCollapseAnimation.setHeightForWrapContent(getActivity(), dropDown);
                ExpandCollapseAnimation expandAni = new ExpandCollapseAnimation(dropDown, DROP_DOWN_TIME);
                dropDown.startAnimation(expandAni);

                Message newMsg = new Message();

            } else if(msg.what == 1) {
                ExpandCollapseAnimation expandAni = new ExpandCollapseAnimation(dropDown, DROP_DOWN_TIME);
                dropDown.startAnimation(expandAni);

                simpleView.setOnClickListener(null);
                simpleView.setVisibility(View.GONE);
            }
        }
    };

    simpleView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            handler.sendEmptyMessage(1);
        }
    });

    // start drop down animation
    handler.sendEmptyMessage(0);
}

Dernier commentaire: je ne suis pas sûr que ce soit la meilleure façon de le faire, mais c'est ce qui a fonctionné pour moi.

Edit : Il existe une solution différente de DevBytes sur youtube qui peut être consultée ici .

30
Warpzit

La réponse signalée par Warpzit est correcte. J'ai utilisé cette approche pour fournir une bibliothèque que vous pouvez facilement intégrer dans votre application sans avoir à savoir comment cela fonctionne réellement:

https://github.com/tjerkw/Android-SlideExpandableListView

Plus d'informations à ce sujet peuvent être lues dans cet article de blog: http://tjerktech.wordpress.com/2012/06/23/an-emerging-Android-ui-pattern-for-contextual-actions/

5
TjerkW

L'implémentation par Warpzit fonctionne certainement, mais elle est inutilisable si vous devez soutenir des groupes avec beaucoup d'enfants (disons 100) car vous n'utiliserez pas la structure optimisée de ListView (c'est-à-dire la réutilisation/recyclage des vues enfants ). Au lieu de cela, j'ai fini par étendre ExpandableListView pour créer un AnimatedExpandableListView qui utilise la technique que j'ai décrite ici . Ce faisant, AnimatedExpandableListView peut animer l'expansion de groupe tout en offrant des performances optimales. Jetez un oeil.

3
idunnololz

Le développement/réduction ne fonctionne pas avec le code d'ici: https://github.com/tjerkw/Android-SlideExpandableListView parce que OnItemExpandCollapseListener expandCollapseListener De AbstractSlideExpandableListAdapter est null; la méthode notifiyExpandCollapseListener est appelée au démarrage de l'animation, mais l'écouteur est null car: vous avez le ActionSlideExpandableListView:

ActionSlideExpandableListView lv = (ActionSlideExpandableListView) findViewById(R.id.list_view);
SlideExpandableListAdapter slideAdapter = new SlideExpandableListAdapter(adapter,R.id.expandable_toggle_button, R.id.expandable);

et vous définissez l'adaptateur: lv.setAdapter(slideAdapter); qui appelle la méthode setAdapter de SlideExpandableListView et là une nouvelle instance de SlideExpandableListAdapter est créée.

J'ai changé comme ceci: setAdapter méthode de ActionSlideExpandableListView prend comme paramètre également AbstractSlideExpandableListAdapter.OnItemExpandCollapseListener Qui est passé à la méthode setAdapter de SlideExpandableListView. Là quand je crée SlideExpandableListAdapter je passe aussi cet écouteur:

 public void setAdapter(ListAdapter adapter, AbstractSlideExpandableListAdapter.OnItemExpandCollapseListener expandCollapseListener) {
        this.adapter = new SlideExpandableListAdapter(adapter, expandCollapseListener);
        super.setAdapter(this.adapter);
    }

    public SlideExpandableListAdapter(ListAdapter wrapped, OnItemExpandCollapseListener expandCollapseListener) {
        this(wrapped, R.id.expandable_toggle_button, R.id.expandable);
        setItemExpandCollapseListener(expandCollapseListener);
    }
1
Paul

Une solution simple consiste à utiliser la classe AnimatedExpandableListView créée par Gary Guo, disponible ici , si vous utilisez déjà la BaseExpandableListAdapter, qui étend ExpandableListAdapter. De cette façon, il n'est pas nécessaire d'utiliser une modification d'un ListView.

Il vous suffit de sous-classer AnimatedExpandableListAdapter au lieu de BaseExpandableListAdapter et AnimatedExpandableListView à la place de ExpandableListView.

Au lieu de @Override getChildrenCount utilisez simplement @Override getRealChildrenCount. Faites de même pour @Override getChildView, en utilisant @Override getRealChildView à sa place.

Ensuite, vous utilisez l'animation comme suit:

    expandableListView.setOnGroupClickListener(new AnimatedExpandableListView.OnGroupClickListener() {
        @Override
        public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
            if (expandableListView.isGroupExpanded(groupPosition))
                expandableListView.collapseGroupWithAnimation(groupPosition);
            else
                expandableListView.expandGroupWithAnimation(groupPosition);
            return true;
        }

    });

Un autre détail est que dans votre fichier xml de mise en page, vous devez référencer AnimatedExpandableListView et non ExpandableListView:

<com.your.package.project.class.location.AnimatedExpandableListView
    Android:id="@+id/listView"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent" />

L'exemple de code dans le projet et les commentaires sur la classe AnimatedExpandableListView sont très utiles si vous avez besoin d'aide.

0
Winston