All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.haarman.listviewanimations.itemmanipulation.ExpandableListItemAdapter Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
package com.haarman.listviewanimations.itemmanipulation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import com.haarman.listviewanimations.ArrayAdapter;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ValueAnimator;

/**
 * An {@link ArrayAdapter} which allows items to be expanded using an animation.
 */
public abstract class ExpandableListItemAdapter extends ArrayAdapter {

	private static final int DEFAULTTITLEPARENTRESID = 10000;
	private static final int DEFAULTCONTENTPARENTRESID = 10001;

	private Context mContext;
	private int mViewLayoutResId;
	private int mTitleParentResId;
	private int mContentParentResId;
	private int mActionViewResId;
	private List mVisibleIds;

	private int mLimit;
	private Map mExpandedViews;

	/**
	 * Creates a new ExpandableListItemAdapter with an empty list.
	 */
	protected ExpandableListItemAdapter(Context context) {
		this(context, null);
	}

	/**
	 * Creates a new {@link ExpandableListItemAdapter} with the specified list,
	 * or an empty list if items == null.
	 */
	protected ExpandableListItemAdapter(Context context, List items) {
		super(items);
		mContext = context;
		mTitleParentResId = DEFAULTTITLEPARENTRESID;
		mContentParentResId = DEFAULTCONTENTPARENTRESID;

		mVisibleIds = new ArrayList();
	}

	/**
	 * Creates a new ExpandableListItemAdapter with an empty list. Uses given
	 * layout resource for the view; titleParentResId and contentParentResId
	 * should be identifiers for ViewGroups within that layout.
	 */
	protected ExpandableListItemAdapter(Context context, int layoutResId, int titleParentResId, int contentParentResId) {
		this(context, layoutResId, titleParentResId, contentParentResId, null);
	}

	/**
	 * Creates a new ExpandableListItemAdapter with the specified list, or an
	 * empty list if items == null. Uses given layout resource for the view;
	 * titleParentResId and contentParentResId should be identifiers for
	 * ViewGroups within that layout.
	 */
	protected ExpandableListItemAdapter(Context context, int layoutResId, int titleParentResId, int contentParentResId, List items) {
		super(items);
		mContext = context;
		mViewLayoutResId = layoutResId;
		mTitleParentResId = titleParentResId;
		mContentParentResId = contentParentResId;

		mVisibleIds = new ArrayList();
		mExpandedViews = new HashMap();
	}

	/**
	 * Set the resource id of the child {@link View} contained in the View returned by
	 * {@link #getTitleView(int, View, ViewGroup)} that will be the actuator of the expand / collapse animations.
* If there is no View in the title View with given resId, a {@link NullPointerException} is thrown.

* Default behavior: the whole title View acts as the actuator. * @param resId the resource id. */ public void setActionViewResId(int resId) { mActionViewResId = resId; } /** * Set the maximum number of items allowed to be expanded. When the (limit+1)th item is expanded, the first expanded item will collapse. * @param limit the maximum number of items allowed to be expanded. Use <= 0 for no limit. */ public void setLimit(int limit) { mLimit = limit; mVisibleIds.clear(); mExpandedViews.clear(); notifyDataSetChanged(); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewGroup view = (ViewGroup) convertView; ViewHolder viewHolder; if (view == null) { view = createView(parent); viewHolder = new ViewHolder(); viewHolder.titleParent = (ViewGroup) view.findViewById(mTitleParentResId); viewHolder.contentParent = (ViewGroup) view.findViewById(mContentParentResId); view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); } if (mLimit > 0) { if (mVisibleIds.contains(getItemId(position))) { mExpandedViews.put(getItemId(position), view); } else if (mExpandedViews.containsValue(view) && !mVisibleIds.contains(getItemId(position))) { mExpandedViews.remove(getItemId(position)); } } View titleView = getTitleView(position, viewHolder.titleView, viewHolder.titleParent); if (titleView != viewHolder.titleView) { viewHolder.titleParent.removeAllViews(); viewHolder.titleParent.addView(titleView); if (mActionViewResId == 0) { view.setOnClickListener(new TitleViewOnClickListener(viewHolder.contentParent)); } else { view.findViewById(mActionViewResId).setOnClickListener(new TitleViewOnClickListener(viewHolder.contentParent)); } } viewHolder.titleView = titleView; View contentView = getContentView(position, viewHolder.contentView, viewHolder.contentParent); if (contentView != viewHolder.contentView) { viewHolder.contentParent.removeAllViews(); viewHolder.contentParent.addView(contentView); } viewHolder.contentView = contentView; viewHolder.contentParent.setVisibility(mVisibleIds.contains(getItemId(position)) ? View.VISIBLE : View.GONE); viewHolder.contentParent.setTag(getItemId(position)); ViewGroup.LayoutParams layoutParams = viewHolder.contentParent.getLayoutParams(); layoutParams.height = LayoutParams.WRAP_CONTENT; viewHolder.contentParent.setLayoutParams(layoutParams); return view; } private ViewGroup createView(ViewGroup parent) { ViewGroup view; if (mViewLayoutResId == 0) { view = new RootView(mContext); } else { view = (ViewGroup) LayoutInflater.from(mContext).inflate(mViewLayoutResId, parent, false); } return view; } /** * Get a View that displays the title of the data at the specified position * in the data set. You can either create a View manually or inflate it from * an XML layout file. When the View is inflated, the parent View (GridView, * ListView...) will apply default layout parameters unless you use * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)} * to specify a root view and to prevent attachment to the root. * * @param position * The position of the item within the adapter's data set of the * item whose view we want. * @param convertView * The old view to reuse, if possible. Note: You should check * that this view is non-null and of an appropriate type before * using. If it is not possible to convert this view to display * the correct data, this method can create a new view. * @param parent * The parent that this view will eventually be attached to * @return A View corresponding to the title of the data at the specified * position. */ public abstract View getTitleView(int position, View convertView, ViewGroup parent); /** * Get a View that displays the content of the data at the specified * position in the data set. You can either create a View manually or * inflate it from an XML layout file. When the View is inflated, the parent * View (GridView, ListView...) will apply default layout parameters unless * you use * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)} * to specify a root view and to prevent attachment to the root. * * @param position * The position of the item within the adapter's data set of the * item whose view we want. * @param convertView * The old view to reuse, if possible. Note: You should check * that this view is non-null and of an appropriate type before * using. If it is not possible to convert this view to display * the correct data, this method can create a new view. * @param parent * The parent that this view will eventually be attached to * @return A View corresponding to the content of the data at the specified * position. */ public abstract View getContentView(int position, View convertView, ViewGroup parent); private static class ViewHolder { ViewGroup titleParent; ViewGroup contentParent; View titleView; View contentView; } private static class RootView extends LinearLayout { private ViewGroup mTitleViewGroup; private ViewGroup mContentViewGroup; public RootView(Context context) { super(context); init(); } private void init() { setOrientation(VERTICAL); mTitleViewGroup = new FrameLayout(getContext()); mTitleViewGroup.setId(DEFAULTTITLEPARENTRESID); addView(mTitleViewGroup); mContentViewGroup = new FrameLayout(getContext()); mContentViewGroup.setId(DEFAULTCONTENTPARENTRESID); addView(mContentViewGroup); } } private class TitleViewOnClickListener implements View.OnClickListener { private View mContentParent; private TitleViewOnClickListener(View contentParent) { this.mContentParent = contentParent; } @Override public void onClick(View view) { boolean isVisible = mContentParent.getVisibility() == View.VISIBLE; if (!isVisible && mLimit > 0 && mVisibleIds.size() >= mLimit) { Long firstId = mVisibleIds.get(0); View firstEV = mExpandedViews.get(firstId); if (firstEV != null) { ViewHolder firstVH = ((ViewHolder) firstEV.getTag()); ViewGroup contentParent = firstVH.contentParent; ExpandCollapseHelper.animateCollapsing(contentParent); mExpandedViews.remove(mVisibleIds.get(0)); } mVisibleIds.remove(mVisibleIds.get(0)); } if (isVisible) { ExpandCollapseHelper.animateCollapsing(mContentParent); mVisibleIds.remove(mContentParent.getTag()); mExpandedViews.remove(mContentParent.getTag()); } else { ExpandCollapseHelper.animateExpanding(mContentParent); mVisibleIds.add((Long) mContentParent.getTag()); if (mLimit > 0) { View parent = (View) mContentParent.getParent(); mExpandedViews.put((Long) mContentParent.getTag(), parent); } } } } private static class ExpandCollapseHelper { public static void animateCollapsing(final View view) { int origHeight = view.getHeight(); ValueAnimator animator = createHeightAnimator(view, origHeight, 0); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { view.setVisibility(View.GONE); } }); animator.start(); } public static void animateExpanding(final View view) { view.setVisibility(View.VISIBLE); final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); view.measure(widthSpec, heightSpec); ValueAnimator animator = createHeightAnimator(view, 0, view.getMeasuredHeight()); animator.start(); } public static ValueAnimator createHeightAnimator(final View view, int start, int end) { ValueAnimator animator = ValueAnimator.ofInt(start, end); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { int value = (Integer) valueAnimator.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); layoutParams.height = value; view.setLayoutParams(layoutParams); } }); return animator; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy