package de.lukasniemeier.mensa.ui.adapter;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.lukasniemeier.mensa.R;
public abstract class CardAdapter<T> extends ArrayAdapter<CardState<T>> {
private final LayoutInflater inflater;
private final Animator animation;
private int runningAnimations;
private final Set<T> animationSet;
private final Map<CardState<T>, ViewHolder<T>> holderMap;
private ViewTreeObserver.OnPreDrawListener cardHeightEnsurer;
public CardAdapter(Context context) {
super(context, 0, new ArrayList<CardState<T>>());
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
runningAnimations = 0;
animation = createAddAnimator(context);
animationSet = new HashSet<T>();
holderMap = new HashMap<CardState<T>, ViewHolder<T>>();
cardHeightEnsurer = null;
}
public void add(T object, boolean showAnimation) {
add(new CardState<T>(object), showAnimation);
}
public void add(CardState<T> object, boolean showAnimation) {
if (showAnimation) {
animationSet.add(object.getValue());
}
add(object);
}
public void addAll(List<? extends T> collection, boolean showAnimation) {
if (showAnimation) {
animationSet.addAll(collection);
}
addAll(Collections2.transform(collection, new Function<T, CardState<T>>() {
@Override
public CardState<T> apply(T value) {
return new CardState<T>(value);
}
}));
}
public void addAll(Collection<? extends CardState<T>> collection, boolean showAnimation) {
if (showAnimation) {
animationSet.addAll(Collections2.transform(collection, new Function<CardState<T>, T>() {
@Override
public T apply(CardState<T> state) {
return state.getValue();
}
}));
}
addAll(collection);
}
protected abstract Animator createAddAnimator(Context context);
protected abstract View inflateCardContentLayout(LayoutInflater inflater, ViewGroup container);
protected abstract void initializeView(View view, T value, boolean isTurned, boolean hasBeenTurned);
@Override
public View getView(int position, View convertView, final ViewGroup parent) {
if (cardHeightEnsurer == null) {
cardHeightEnsurer = new CardHeightChangeNotifier(parent);
parent.getViewTreeObserver().addOnPreDrawListener(cardHeightEnsurer);
}
CardState<T> object = getItem(position);
View view = convertView;
ViewHolder<T> objectHolder = holderMap.get(object);
if (objectHolder == null) {
holderMap.put(object, new ViewHolder<T>(object, object.isTurned(), parent.getMeasuredWidth()));
objectHolder = holderMap.get(object);
}
if (view == null) {
view = inflater.inflate(R.layout.card_layout, parent, false);
view.setTag(objectHolder);
FrameLayout container = (FrameLayout) view.findViewById(R.id.card_container);
container.addView(inflateCardContentLayout(inflater, container));
}
boolean hasBeenTurned = false;
@SuppressWarnings("unchecked") ViewHolder<T> currentHolder = (ViewHolder<T>) view.getTag();
if (objectHolder.equals(currentHolder)) {
hasBeenTurned = objectHolder.wasTurned != object.isTurned();
}
objectHolder.wasTurned = object.isTurned();
view.setTag(objectHolder);
T value = objectHolder.state.getValue();
if (objectHolder.maxHeight == null) {
initializeView(view, value, false, false);
int frontHeight = measureHeight(view, objectHolder.parentWidth);
initializeView(view, value, true, false);
int backHeight = measureHeight(view, objectHolder.parentWidth);
objectHolder.maxHeight = Math.max(frontHeight, backHeight);
}
initializeView(view, value, objectHolder.wasTurned, hasBeenTurned);
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = objectHolder.maxHeight;
view.setLayoutParams(layoutParams);
animateAdd(object.getValue(), view);
return view;
}
private void animateAdd(T object, View view) {
if (animationSet.contains(object)) {
view.setAlpha(0.0f);
Animator currentAnimation = animation.clone();
currentAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
runningAnimations++;
}
@Override
public void onAnimationEnd(Animator animation) {
runningAnimations--;
}
});
currentAnimation.setStartDelay(250 * runningAnimations);
currentAnimation.setTarget(view);
currentAnimation.start();
animationSet.remove(object);
}
}
private static class ViewHolder<T> {
public final CardState<T> state;
public boolean wasTurned;
public int parentWidth;
public Integer maxHeight;
private ViewHolder(CardState<T> state, boolean wasTurned, int parentWidth) {
this.state = state;
this.wasTurned = wasTurned;
this.parentWidth = parentWidth;
this.maxHeight = null;
}
}
private static int measureHeight(View view, int width) {
int measureSpecWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int measureSpecHeight = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(measureSpecWidth, measureSpecHeight);
return view.getMeasuredHeight();
}
private class CardHeightChangeNotifier implements ViewTreeObserver.OnPreDrawListener {
private final ViewGroup cardContainer;
private CardHeightChangeNotifier(ViewGroup cardContainer) {
this.cardContainer = cardContainer;
}
@Override
public boolean onPreDraw() {
if (holderMap.isEmpty()) {
return true;
}
boolean parentHeightUpdated = false;
int measuredParentWidth = cardContainer.getMeasuredWidth();
for (ViewHolder<T> holder : holderMap.values()) {
if (holder.parentWidth != measuredParentWidth) {
holder.parentWidth = measuredParentWidth;
holder.maxHeight = null;
parentHeightUpdated = true;
}
}
if (parentHeightUpdated) {
CardAdapter.this.notifyDataSetChanged();
return false;
}
return true;
}
}
}