com.haarman.listviewanimations.itemmanipulation.ExpandableListItemAdapter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of library Show documentation
Show all versions of library Show documentation
ListViewAnimations library
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