Нужно сделать разварачиваемый анимированый список я нашел вот этот код
https://github.com/idunnololz/AnimatedExpandableListView
И вроде все работает как нужно, но есть проблема когда мне нужно задать для группы цвет разделителя один, а для childViews
в этой групе другой
Насколько я понимаю то это кастомное AnimatedExpandableListView
вью при разворачивании перересовывает разделитель для детей, но почему то в момент анимации оно перересовывает разделитель детей разделителем от группы, а когда анимация заканчивается то остается только разделитель который задан для childView
вот видео, обратите внимание что при разворачивании разделитель детей красного цвета а в конце синий
https://youtu.be/WW69QUMYa1s
Вот XML
<com.idunnololz.widgets.AnimatedExpandableListView
android:id="@+id/listView"
android:divider="#f40303"
android:childDivider="#0323f4"
android:dividerHeight="1.5dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
Вот AnimatedExpandableListView
public class AnimatedExpandableListView extends ExpandableListView {
/*
* A detailed explanation for how this class works:
*
* Animating the ExpandableListView was no easy task. The way that this
* class does it is by exploiting how an ExpandableListView works.
*
* Normally when {@link ExpandableListView#collapseGroup(int)} or
* {@link ExpandableListView#expandGroup(int)} is called, the view toggles
* the flag for a group and calls notifyDataSetChanged to cause the ListView
* to refresh all of it's view. This time however, depending on whether a
* group is expanded or collapsed, certain childViews will either be ignored
* or added to the list.
*
* Knowing this, we can come up with a way to animate our views. For
* instance for group expansion, we tell the adapter to animate the
* children of a certain group. We then expand the group which causes the
* ExpandableListView to refresh all views on screen. The way that
* ExpandableListView does this is by calling getView() in the adapter.
* However since the adapter knows that we are animating a certain group,
* instead of returning the real views for the children of the group being
* animated, it will return a fake dummy view. This dummy view will then
* draw the real child views within it's dispatchDraw function. The reason
* we do this is so that we can animate all of it's children by simply
* animating the dummy view. After we complete the animation, we tell the
* adapter to stop animating the group and call notifyDataSetChanged. Now
* the ExpandableListView is forced to refresh it's views again, except this
* time, it will get the real views for the expanded group.
*
* So, to list it all out, when {@link #expandGroupWithAnimation(int)} is
* called the following happens:
*
* 1. The ExpandableListView tells the adapter to animate a certain group.
* 2. The ExpandableListView calls expandGroup.
* 3. ExpandGroup calls notifyDataSetChanged.
* 4. As an result, getChildView is called for expanding group.
* 5. Since the adapter is in "animating mode", it will return a dummy view.
* 6. This dummy view draws the actual children of the expanding group.
* 7. This dummy view's height is animated from 0 to it's expanded height.
* 8. Once the animation completes, the adapter is notified to stop
* animating the group and notifyDataSetChanged is called again.
* 9. This forces the ExpandableListView to refresh all of it's views again.
* 10.This time when getChildView is called, it will return the actual
* child views.
*
* For animating the collapse of a group is a bit more difficult since we
* can't call collapseGroup from the start as it would just ignore the
* child items, giving up no chance to do any sort of animation. Instead
* what we have to do is play the animation first and call collapseGroup
* after the animation is done.
*
* So, to list it all out, when {@link #collapseGroupWithAnimation(int)} is
* called the following happens:
*
* 1. The ExpandableListView tells the adapter to animate a certain group.
* 2. The ExpandableListView calls notifyDataSetChanged.
* 3. As an result, getChildView is called for expanding group.
* 4. Since the adapter is in "animating mode", it will return a dummy view.
* 5. This dummy view draws the actual children of the expanding group.
* 6. This dummy view's height is animated from it's current height to 0.
* 7. Once the animation completes, the adapter is notified to stop
* animating the group and notifyDataSetChanged is called again.
* 8. collapseGroup is finally called.
* 9. This forces the ExpandableListView to refresh all of it's views again.
* 10.This time when the ListView will not get any of the child views for
* the collapsed group.
*/
@SuppressWarnings("unused")
private static final String TAG = AnimatedExpandableListAdapter.class.getSimpleName();
/**
* The duration of the expand/collapse animations
*/
private static final int ANIMATION_DURATION = 300;
private AnimatedExpandableListAdapter adapter;
public AnimatedExpandableListView(Context context) {
super(context);
}
public AnimatedExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AnimatedExpandableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* @see ExpandableListView#setAdapter(ExpandableListAdapter)
*/
public void setAdapter(ExpandableListAdapter adapter) {
super.setAdapter(adapter);
// Make sure that the adapter extends AnimatedExpandableListAdapter
if(adapter instanceof AnimatedExpandableListAdapter) {
this.adapter = (AnimatedExpandableListAdapter) adapter;
this.adapter.setParent(this);
} else {
throw new ClassCastException(adapter.toString() + " must implement AnimatedExpandableListAdapter");
}
}
/**
* Expands the given group with an animation.
* @param groupPos The position of the group to expand
* @return Returns true if the group was expanded. False if the group was
* already expanded.
*/
@SuppressLint("NewApi")
public boolean expandGroupWithAnimation(int groupPos) {
boolean lastGroup = groupPos == adapter.getGroupCount() - 1;
if (lastGroup && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return expandGroup(groupPos, true);
}
int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
if (groupFlatPos != -1) {
int childIndex = groupFlatPos - getFirstVisiblePosition();
if (childIndex < getChildCount()) {
// Get the view for the group is it is on screen...
View v = getChildAt(childIndex);
if (v.getBottom() >= getBottom()) {
// If the user is not going to be able to see the animation
// we just expand the group without an animation.
// This resolves the case where getChildView will not be
// called if the children of the group is not on screen
// We need to notify the adapter that the group was expanded
// without it's knowledge
adapter.notifyGroupExpanded(groupPos);
return expandGroup(groupPos);
}
}
}
// Let the adapter know that we are starting the animation...
adapter.startExpandAnimation(groupPos, 0);
// Finally call expandGroup (note that expandGroup will call
// notifyDataSetChanged so we don't need to)
return expandGroup(groupPos);
}
/**
* Collapses the given group with an animation.
* @param groupPos The position of the group to collapse
* @return Returns true if the group was collapsed. False if the group was
* already collapsed.
*/
public boolean collapseGroupWithAnimation(int groupPos) {
int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
if (groupFlatPos != -1) {
int childIndex = groupFlatPos - getFirstVisiblePosition();
if (childIndex >= 0 && childIndex < getChildCount()) {
// Get the view for the group is it is on screen...
View v = getChildAt(childIndex);
if (v.getBottom() >= getBottom()) {
// If the user is not going to be able to see the animation
// we just collapse the group without an animation.
// This resolves the case where getChildView will not be
// called if the children of the group is not on screen
return collapseGroup(groupPos);
}
} else {
// If the group is offscreen, we can just collapse it without an
// animation...
return collapseGroup(groupPos);
}
}
// Get the position of the firstChild visible from the top of the screen
long packedPos = getExpandableListPosition(getFirstVisiblePosition());
int firstChildPos = getPackedPositionChild(packedPos);
int firstGroupPos = getPackedPositionGroup(packedPos);
// If the first visible view on the screen is a child view AND it's a
// child of the group we are trying to collapse, then set that
// as the first child position of the group... see
// {@link #startCollapseAnimation(int, int)} for why this is necessary
firstChildPos = firstChildPos == -1 || firstGroupPos != groupPos ? 0 : firstChildPos;
// Let the adapter know that we are going to start animating the
// collapse animation.
adapter.startCollapseAnimation(groupPos, firstChildPos);
// Force the listview to refresh it's views
adapter.notifyDataSetChanged();
return isGroupExpanded(groupPos);
}
private int getAnimationDuration() {
return ANIMATION_DURATION;
}
/**
* Used for holding information regarding the group.
*/
private static class GroupInfo {
boolean animating = false;
boolean expanding = false;
int firstChildPosition;
/**
* This variable contains the last known height value of the dummy view.
* We save this information so that if the user collapses a group
* before it fully expands, the collapse animation will start from the
* CURRENT height of the dummy view and not from the full expanded
* height.
*/
int dummyHeight = -1;
}
/**
* A specialized adapter for use with the AnimatedExpandableListView. All
* adapters used with AnimatedExpandableListView MUST extend this class.
*/
public static abstract class AnimatedExpandableListAdapter extends BaseExpandableListAdapter {
private SparseArray<GroupInfo> groupInfo = new SparseArray<GroupInfo>();
private AnimatedExpandableListView parent;
private static final int STATE_IDLE = 0;
private static final int STATE_EXPANDING = 1;
private static final int STATE_COLLAPSING = 2;
private void setParent(AnimatedExpandableListView parent) {
this.parent = parent;
}
public int getRealChildType(int groupPosition, int childPosition) {
return 0;
}
public int getRealChildTypeCount() {
return 1;
}
public abstract View getRealChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent);
public abstract int getRealChildrenCount(int groupPosition);
private GroupInfo getGroupInfo(int groupPosition) {
GroupInfo info = groupInfo.get(groupPosition);
if (info == null) {
info = new GroupInfo();
groupInfo.put(groupPosition, info);
}
return info;
}
public void notifyGroupExpanded(int groupPosition) {
GroupInfo info = getGroupInfo(groupPosition);
info.dummyHeight = -1;
}
private void startExpandAnimation(int groupPosition, int firstChildPosition) {
GroupInfo info = getGroupInfo(groupPosition);
info.animating = true;
info.firstChildPosition = firstChildPosition;
info.expanding = true;
}
private void startCollapseAnimation(int groupPosition, int firstChildPosition) {
GroupInfo info = getGroupInfo(groupPosition);
info.animating = true;
info.firstChildPosition = firstChildPosition;
info.expanding = false;
}
private void stopAnimation(int groupPosition) {
GroupInfo info = getGroupInfo(groupPosition);
info.animating = false;
}
/**
* Override {@link #getRealChildType(int, int)} instead.
*/
@Override
public final int getChildType(int groupPosition, int childPosition) {
GroupInfo info = getGroupInfo(groupPosition);
if (info.animating) {
// If we are animating this group, then all of it's children
// are going to be dummy views which we will say is type 0.
return 0;
} else {
// If we are not animating this group, then we will add 1 to
// the type it has so that no type id conflicts will occur
// unless getRealChildType() returns MAX_INT
return getRealChildType(groupPosition, childPosition) + 1;
}
}
/**
* Override {@link #getRealChildTypeCount()} instead.
*/
@Override
public final int getChildTypeCount() {
// Return 1 more than the childTypeCount to account for DummyView
return getRealChildTypeCount() + 1;
}
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
/**
* Override {@link #getChildView(int, int, boolean, View, ViewGroup)} instead.
*/
@Override
public final View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
final GroupInfo info = getGroupInfo(groupPosition);
if (info.animating) {
// If this group is animating, return the a DummyView...
if (convertView instanceof DummyView == false) {
convertView = new DummyView(parent.getContext());
convertView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, 0));
}
if (childPosition < info.firstChildPosition) {
// The reason why we do this is to support the collapse
// this group when the group view is not visible but the
// children of this group are. When notifyDataSetChanged
// is called, the ExpandableListView tries to keep the
// list position the same by saving the first visible item
// and jumping back to that item after the views have been
// refreshed. Now the problem is, if a group has 2 items
// and the first visible item is the 2nd child of the group
// and this group is collapsed, then the dummy view will be
// used for the group. But now the group only has 1 item
// which is the dummy view, thus when the ListView is trying
// to restore the scroll position, it will try to jump to
// the second item of the group. But this group no longer
// has a second item, so it is forced to jump to the next
// group. This will cause a very ugly visual glitch. So
// the way that we counteract this is by creating as many
// dummy views as we need to maintain the scroll position
// of the ListView after notifyDataSetChanged has been
// called.
convertView.getLayoutParams().height = 0;
return convertView;
}
final ExpandableListView listView = (ExpandableListView) parent;
final DummyView dummyView = (DummyView) convertView;
// Clear the views that the dummy view draws.
dummyView.clearViews();
// Set the style of the divider
dummyView.setDivider(listView.getDivider(), parent.getMeasuredWidth(), listView.getDividerHeight());
// Make measure specs to measure child views
final int measureSpecW = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY);
final int measureSpecH = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int totalHeight = 0;
int clipHeight = parent.getHeight();
final int len = getRealChildrenCount(groupPosition);
for (int i = info.firstChildPosition; i < len; i++) {
View childView = getRealChildView(groupPosition, i, (i == len - 1), null, parent);
LayoutParams p = (LayoutParams) childView.getLayoutParams();
if (p == null) {
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
childView.setLayoutParams(p);
}
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = measureSpecH;
}
childView.measure(measureSpecW, childHeightSpec);
totalHeight += childView.getMeasuredHeight();
if (totalHeight < clipHeight) {
// we only need to draw enough views to fool the user...
dummyView.addFakeView(childView);
} else {
dummyView.addFakeView(childView);
// if this group has too many views, we don't want to
// calculate the height of everything... just do a light
// approximation and break
int averageHeight = totalHeight / (i + 1);
totalHeight += (len - i - 1) * averageHeight;
break;
}
}
Object o;
int state = (o = dummyView.getTag()) == null ? STATE_IDLE : (Integer) o;
if (info.expanding && state != STATE_EXPANDING) {
ExpandAnimation ani = new ExpandAnimation(dummyView, 0, totalHeight, info);
ani.setDuration(this.parent.getAnimationDuration());
ani.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
stopAnimation(groupPosition);
notifyDataSetChanged();
dummyView.setTag(STATE_IDLE);
}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationStart(Animation animation) {}
});
dummyView.startAnimation(ani);
dummyView.setTag(STATE_EXPANDING);
} else if (!info.expanding && state != STATE_COLLAPSING) {
if (info.dummyHeight == -1) {
info.dummyHeight = totalHeight;
}
ExpandAnimation ani = new ExpandAnimation(dummyView, info.dummyHeight, 0, info);
ani.setDuration(this.parent.getAnimationDuration());
ani.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
stopAnimation(groupPosition);
listView.collapseGroup(groupPosition);
notifyDataSetChanged();
info.dummyHeight = -1;
dummyView.setTag(STATE_IDLE);
}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationStart(Animation animation) {}
});
dummyView.startAnimation(ani);
dummyView.setTag(STATE_COLLAPSING);
}
return convertView;
} else {
return getRealChildView(groupPosition, childPosition, isLastChild, convertView, parent);
}
}
@Override
public final int getChildrenCount(int groupPosition) {
GroupInfo info = getGroupInfo(groupPosition);
if (info.animating) {
return info.firstChildPosition + 1;
} else {
return getRealChildrenCount(groupPosition);
}
}
}
private static class DummyView extends View {
private List<View> views = new ArrayList<View>();
private Drawable divider;
private int dividerWidth;
private int dividerHeight;
public DummyView(Context context) {
super(context);
}
public void setDivider(Drawable divider, int dividerWidth, int dividerHeight) {
if(divider != null) {
this.divider = divider;
this.dividerWidth = dividerWidth;
this.dividerHeight = dividerHeight;
divider.setBounds(0, 0, dividerWidth, dividerHeight);
}
}
/**
* Add a view for the DummyView to draw.
* @param childView View to draw
*/
public void addFakeView(View childView) {
childView.layout(0, 0, getWidth(), childView.getMeasuredHeight());
views.add(childView);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
final int len = views.size();
for(int i = 0; i < len; i++) {
View v = views.get(i);
v.layout(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
}
}
public void clearViews() {
views.clear();
}
@Override
public void dispatchDraw(Canvas canvas) {
canvas.save();
if(divider != null) {
divider.setBounds(0, 0, dividerWidth, dividerHeight);
}
final int len = views.size();
for(int i = 0; i < len; i++) {
View v = views.get(i);
canvas.save();
canvas.clipRect(0, 0, getWidth(), v.getMeasuredHeight());
v.draw(canvas);
canvas.restore();
if(divider != null) {
divider.draw(canvas);
canvas.translate(0, dividerHeight);
}
canvas.translate(0, v.getMeasuredHeight());
}
canvas.restore();
}
}
private static class ExpandAnimation extends Animation {
private int baseHeight;
private int delta;
private View view;
private GroupInfo groupInfo;
private ExpandAnimation(View v, int startHeight, int endHeight, GroupInfo info) {
baseHeight = startHeight;
delta = endHeight - startHeight;
view = v;
groupInfo = info;
view.getLayoutParams().height = startHeight;
view.requestLayout();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
int val = baseHeight + (int) (delta * interpolatedTime);
view.getLayoutParams().height = val;
groupInfo.dummyHeight = val;
view.requestLayout();
} else {
int val = baseHeight + delta;
view.getLayoutParams().height = val;
groupInfo.dummyHeight = val;
view.requestLayout();
}
}
}
}
В итоге сделал так в AnimatedExpandableListView
добавил метод
public void setChildDivider(int iChildDividerColor)
{
Drawable childDividerColor = new ColorDrawable(getResources().getColor(iChildDividerColor));
AnimatedExpandableListAdapter.setChildDividerColor(childDividerColor);
super.setChildDivider(childDividerColor);
}
и в AnimatedExpandableListAdapter
вот такое
private static Drawable mChildDividerColor = null;
static void setChildDividerColor(Drawable iChildDividerColor)
{
mChildDividerColor = iChildDividerColor;
}
Вот так применяю
listView.setChildDivider(R.color.black);
И почему то все равно нужно указывать в XML разметке тоже цвет
В итоге вот весь файл
public class AnimatedExpandableListView extends ExpandableListView
{
@SuppressWarnings("unused")
private static final String TAG =
AnimatedExpandableListAdapter.class.getSimpleName();
/**
* The duration of the expand/collapse animations
*/
private static final int ANIMATION_DURATION = 500;
private AnimatedExpandableListAdapter adapter;
public AnimatedExpandableListView(Context context)
{
super(context);
}
public AnimatedExpandableListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public AnimatedExpandableListView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
/**
* @see ExpandableListView#setAdapter(ExpandableListAdapter)
*/
public void setAdapter(ExpandableListAdapter adapter)
{
super.setAdapter(adapter);
// Make sure that the adapter extends AnimatedExpandableListAdapter
if (adapter instanceof AnimatedExpandableListAdapter)
{
this.adapter = (AnimatedExpandableListAdapter) adapter;
this.adapter.setParent(this);
}
else
{
throw new ClassCastException(adapter.toString() + " must implement AnimatedExpandableListAdapter");
}
}
public void setChildDivider(int iChildDividerColor)
{
Drawable childDividerColor = new ColorDrawable(getResources().getColor(iChildDividerColor));
AnimatedExpandableListAdapter.setChildDividerColor(childDividerColor);
super.setChildDivider(childDividerColor);
}
/**
* Expands the given group with an animation.
*
* @param groupPos The position of the group to expand
*
* @return Returns true if the group was expanded. False if the group was
* already expanded.
*/
@SuppressLint("NewApi")
public boolean expandGroupWithAnimation(int groupPos)
{
boolean lastGroup = groupPos == adapter.getGroupCount() - 1;
if (lastGroup && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
{
return expandGroup(groupPos, true);
}
int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
if (groupFlatPos != -1)
{
int childIndex = groupFlatPos - getFirstVisiblePosition();
if (childIndex < getChildCount())
{
// Get the view for the group is it is on screen...
View v = getChildAt(childIndex);
if (v.getBottom() >= getBottom())
{
// If the user is not going to be able to see the animation
// we just expand the group without an animation.
// This resolves the case where getChildView will not be
// called if the children of the group is not on screen
// We need to notify the adapter that the group was expanded
// without it's knowledge
adapter.notifyGroupExpanded(groupPos);
return expandGroup(groupPos);
}
}
}
// Let the adapter know that we are starting the animation...
adapter.startExpandAnimation(groupPos, 0);
// Finally call expandGroup (note that expandGroup will call
// notifyDataSetChanged so we don't need to)
return expandGroup(groupPos);
}
/**
* Collapses the given group with an animation.
*
* @param groupPos The position of the group to collapse
*
* @return Returns true if the group was collapsed. False if the group was
* already collapsed.
*/
public boolean collapseGroupWithAnimation(int groupPos)
{
int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
if (groupFlatPos != -1)
{
int childIndex = groupFlatPos - getFirstVisiblePosition();
if (childIndex >= 0 && childIndex < getChildCount())
{
// Get the view for the group is it is on screen...
View v = getChildAt(childIndex);
if (v.getBottom() >= getBottom())
{
// If the user is not going to be able to see the animation
// we just collapse the group without an animation.
// This resolves the case where getChildView will not be
// called if the children of the group is not on screen
return collapseGroup(groupPos);
}
}
else
{
// If the group is offscreen, we can just collapse it without an
// animation...
return collapseGroup(groupPos);
}
}
// Get the position of the firstChild visible from the top of the screen
long packedPos = getExpandableListPosition(getFirstVisiblePosition());
int firstChildPos = getPackedPositionChild(packedPos);
int firstGroupPos = getPackedPositionGroup(packedPos);
// If the first visible view on the screen is a child view AND it's a
// child of the group we are trying to collapse, then set that
// as the first child position of the group... see
// {@link #startCollapseAnimation(int, int)} for why this is necessary
firstChildPos = firstChildPos == -1 || firstGroupPos != groupPos ? 0 : firstChildPos;
// Let the adapter know that we are going to start animating the
// collapse animation.
adapter.startCollapseAnimation(groupPos, firstChildPos);
// Force the listview to refresh it's views
adapter.notifyDataSetChanged();
return isGroupExpanded(groupPos);
}
private int getAnimationDuration()
{
return ANIMATION_DURATION;
}
/**
* Used for holding information regarding the group.
*/
private static class GroupInfo
{
boolean animating = false;
boolean expanding = false;
int firstChildPosition;
/**
* This variable contains the last known height value of the dummy view.
* We save this information so that if the user collapses a group
* before it fully expands, the collapse animation will start from the
* CURRENT height of the dummy view and not from the full expanded
* height.
*/
int dummyHeight = -1;
}
/**
* A specialized adapter for use with the AnimatedExpandableListView. All
* adapters used with AnimatedExpandableListView MUST extend this class.
*/
public static abstract class AnimatedExpandableListAdapter extends BaseExpandableListAdapter
{
private SparseArray<GroupInfo> groupInfo = new SparseArray<>();
private AnimatedExpandableListView parent;
private static final int STATE_IDLE = 0;
private static final int STATE_EXPANDING = 1;
private static final int STATE_COLLAPSING = 2;
private static Drawable mChildDividerColor = null;
static void setChildDividerColor(Drawable iChildDividerColor)
{
mChildDividerColor = iChildDividerColor;
}
private void setParent(AnimatedExpandableListView parent)
{
this.parent = parent;
}
public int getRealChildType(int groupPosition, int childPosition)
{
return 0;
}
public int getRealChildTypeCount()
{
return 1;
}
public abstract View getRealChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent);
public abstract int getRealChildrenCount(int groupPosition);
private GroupInfo getGroupInfo(int groupPosition)
{
GroupInfo info = groupInfo.get(groupPosition);
if (info == null)
{
info = new GroupInfo();
groupInfo.put(groupPosition, info);
}
return info;
}
public void notifyGroupExpanded(int groupPosition)
{
GroupInfo info = getGroupInfo(groupPosition);
info.dummyHeight = -1;
}
private void startExpandAnimation(int groupPosition, int firstChildPosition)
{
GroupInfo info = getGroupInfo(groupPosition);
info.animating = true;
info.firstChildPosition = firstChildPosition;
info.expanding = true;
}
private void startCollapseAnimation(int groupPosition, int firstChildPosition)
{
GroupInfo info = getGroupInfo(groupPosition);
info.animating = true;
info.firstChildPosition = firstChildPosition;
info.expanding = false;
}
private void stopAnimation(int groupPosition)
{
GroupInfo info = getGroupInfo(groupPosition);
info.animating = false;
}
/**
* Override {@link #getRealChildType(int, int)} instead.
*/
@Override
public final int getChildType(int groupPosition, int childPosition)
{
GroupInfo info = getGroupInfo(groupPosition);
if (info.animating)
{
// If we are animating this group, then all of it's children
// are going to be dummy views which we will say is type 0.
return 0;
}
else
{
// If we are not animating this group, then we will add 1 to
// the type it has so that no type id conflicts will occur
// unless getRealChildType() returns MAX_INT
return getRealChildType(groupPosition, childPosition) + 1;
}
}
/**
* Override {@link #getRealChildTypeCount()} instead.
*/
@Override
public final int getChildTypeCount()
{
// Return 1 more than the childTypeCount to account for DummyView
return getRealChildTypeCount() + 1;
}
protected ViewGroup.LayoutParams generateDefaultLayoutParams()
{
return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
/**
* Override {@link #getChildView(int, int, boolean, View, ViewGroup)} instead.
*/
@Override
public final View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, final ViewGroup parent)
{
final GroupInfo info = getGroupInfo(groupPosition);
if (info.animating)
{
// If this group is animating, return the a DummyView...
if (convertView instanceof DummyView == false)
{
convertView = new DummyView(parent.getContext());
convertView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, 0));
}
if (childPosition < info.firstChildPosition)
{
// The reason why we do this is to support the collapse
// this group when the group view is not visible but the
// children of this group are. When notifyDataSetChanged
// is called, the ExpandableListView tries to keep the
// list position the same by saving the first visible item
// and jumping back to that item after the views have been
// refreshed. Now the problem is, if a group has 2 items
// and the first visible item is the 2nd child of the group
// and this group is collapsed, then the dummy view will be
// used for the group. But now the group only has 1 item
// which is the dummy view, thus when the ListView is trying
// to restore the scroll position, it will try to jump to
// the second item of the group. But this group no longer
// has a second item, so it is forced to jump to the next
// group. This will cause a very ugly visual glitch. So
// the way that we counteract this is by creating as many
// dummy views as we need to maintain the scroll position
// of the ListView after notifyDataSetChanged has been
// called.
convertView.getLayoutParams().height = 0;
return convertView;
}
final ExpandableListView listView = (ExpandableListView) parent;
final DummyView dummyView = (DummyView) convertView;
// Clear the views that the dummy view draws.
dummyView.clearViews();
// Set the style of the divider
// dummyView.setDivider(listView.getDivider(), parent.getMeasuredWidth(), listView.getDividerHeight());
// dummyView.setDivider(dummyView.getContext().getResources().getColor(R.color.white), parent.getMeasuredWidth(), listView.getDividerHeight());
dummyView.setDivider(mChildDividerColor, parent.getMeasuredWidth(), listView.getDividerHeight());
// Make measure specs to measure child views
final int measureSpecW = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY);
final int measureSpecH = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int totalHeight = 0;
int clipHeight = parent.getHeight();
final int len = getRealChildrenCount(groupPosition);
for (int i = info.firstChildPosition ; i < len ; i++)
{
View childView = getRealChildView(groupPosition, i, (i == len - 1), null, parent);
LayoutParams p = (LayoutParams) childView.getLayoutParams();
if (p == null)
{
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
childView.setLayoutParams(p);
}
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0)
{
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
}
else
{
childHeightSpec = measureSpecH;
}
childView.measure(measureSpecW, childHeightSpec);
totalHeight += childView.getMeasuredHeight();
if (totalHeight < clipHeight)
{
// we only need to draw enough views to fool the user...
dummyView.addFakeView(childView);
}
else
{
dummyView.addFakeView(childView);
// if this group has too many views, we don't want to
// calculate the height of everything... just do a light
// approximation and break
int averageHeight = totalHeight / (i + 1);
totalHeight += (len - i - 1) * averageHeight;
break;
}
}
Object o;
int state = (o = dummyView.getTag()) == null ? STATE_IDLE : (Integer) o;
if (info.expanding && state != STATE_EXPANDING)
{
ExpandAnimation ani = new ExpandAnimation(dummyView, 0, totalHeight, info);
ani.setDuration(this.parent.getAnimationDuration());
ani.setAnimationListener(new Animation.AnimationListener()
{
@Override
public void onAnimationEnd(Animation animation)
{
stopAnimation(groupPosition);
notifyDataSetChanged();
dummyView.setTag(STATE_IDLE);
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
@Override
public void onAnimationStart(Animation animation)
{
}
});
dummyView.startAnimation(ani);
dummyView.setTag(STATE_EXPANDING);
}
else if (!info.expanding && state != STATE_COLLAPSING)
{
if (info.dummyHeight == -1)
{
info.dummyHeight = totalHeight;
}
ExpandAnimation ani = new ExpandAnimation(dummyView, info.dummyHeight, 0, info);
ani.setDuration(this.parent.getAnimationDuration());
ani.setAnimationListener(new Animation.AnimationListener()
{
@Override
public void onAnimationEnd(Animation animation)
{
stopAnimation(groupPosition);
listView.collapseGroup(groupPosition);
notifyDataSetChanged();
info.dummyHeight = -1;
dummyView.setTag(STATE_IDLE);
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
@Override
public void onAnimationStart(Animation animation)
{
}
});
dummyView.startAnimation(ani);
dummyView.setTag(STATE_COLLAPSING);
}
return convertView;
}
else
{
return getRealChildView(groupPosition, childPosition, isLastChild, convertView, parent);
}
}
@Override
public final int getChildrenCount(int groupPosition)
{
GroupInfo info = getGroupInfo(groupPosition);
if (info.animating)
{
return info.firstChildPosition + 1;
}
else
{
return getRealChildrenCount(groupPosition);
}
}
}
private static class DummyView extends View
{
private List<View> views = new ArrayList<>();
private Drawable divider;
private int dividerWidth;
private int dividerHeight;
public DummyView(Context context)
{
super(context);
}
public void setDivider(Drawable divider, int dividerWidth, int dividerHeight)
{
if (divider != null)
{
this.divider = divider;
this.dividerWidth = dividerWidth;
this.dividerHeight = dividerHeight;
divider.setBounds(0, 0, dividerWidth, dividerHeight);
}
}
/**
* Add a view for the DummyView to draw.
*
* @param childView View to draw
*/
public void addFakeView(View childView)
{
childView.layout(0, 0, getWidth(), childView.getMeasuredHeight());
views.add(childView);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
super.onLayout(changed, left, top, right, bottom);
final int len = views.size();
for (int i = 0 ; i < len ; i++)
{
View v = views.get(i);
v.layout(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
}
}
public void clearViews()
{
views.clear();
}
@Override
public void dispatchDraw(Canvas canvas)
{
canvas.save();
if (divider != null)
{
divider.setBounds(0, 0, dividerWidth, dividerHeight);
}
final int len = views.size();
for (int i = 0 ; i < len ; i++)
{
View v = views.get(i);
canvas.save();
canvas.clipRect(0, 0, getWidth(), v.getMeasuredHeight());
v.draw(canvas);
canvas.restore();
if (divider != null)
{
divider.draw(canvas);
canvas.translate(0, dividerHeight);
}
canvas.translate(0, v.getMeasuredHeight());
}
canvas.restore();
}
}
private static class ExpandAnimation extends Animation
{
private int baseHeight;
private int delta;
private View view;
private GroupInfo groupInfo;
private ExpandAnimation(View v, int startHeight, int endHeight, GroupInfo info)
{
baseHeight = startHeight;
delta = endHeight - startHeight;
view = v;
groupInfo = info;
view.getLayoutParams().height = startHeight;
view.requestLayout();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f)
{
int val = baseHeight + (int) (delta * interpolatedTime);
view.getLayoutParams().height = val;
groupInfo.dummyHeight = val;
view.requestLayout();
}
else
{
int val = baseHeight + delta;
view.getLayoutParams().height = val;
groupInfo.dummyHeight = val;
view.requestLayout();
}
}
}
}
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Пишу бота для Skype проблем не было в процессе тестирования так как использовал свой аккаунт где логин типа "User123"Но когда зарегистрировал новый...
Как при определенных размерах использовать container(в bootstrap), а при меньших(телефоны, читалки, планшеты) - container-fluid?