Skip to content

Instantly share code, notes, and snippets.

@timfreiheit
Created December 7, 2015 15:03
Show Gist options
  • Save timfreiheit/0aec9a25e5bf5c854b5d to your computer and use it in GitHub Desktop.
Save timfreiheit/0aec9a25e5bf5c854b5d to your computer and use it in GitHub Desktop.
SceneView
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SceneView">
<attr name="heightChangeEnabled" format="boolean"/>
<attr name="widthChangeEnabled" format="boolean"/>
<attr name="overshootEnabled" format="boolean"/>
<attr name="progress" format="float"/>
<attr name="scene0" format="reference"/>
<attr name="scene1" format="reference"/>
<attr name="scene2" format="reference"/>
<attr name="scene3" format="reference"/>
<attr name="scene4" format="reference"/>
<attr name="scene5" format="reference"/>
</declare-styleable>
</resources>
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.smartmobilefactory.adviqo.adviqo_android.R;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
/**
* Created by Tim Freiheit
*/
public class SceneView extends ViewGroup {
private Set<OnSceneSpecsCreatedListener> listeners = new HashSet<>();
private boolean mHeightChangeEnabled = true;
private boolean mWidthChangeEnabled = true;
private boolean mOvershootEnabled = false;
private float mAnimationProgress = 0f;
private ViewSpecs mMyStartSpecs = null;
private ViewSpecs mMyEndSpecs = null;
private boolean initialed = false;
List<SparseArray<ViewSpecs>> sceneSpecs = new ArrayList<>();
private static final String TAG = SceneView.class.getSimpleName();
private List<Integer> sceneRes = new ArrayList<>();
public List<View> sceneViews = new ArrayList<>();
public SceneView(Context context) {
super(context);
}
public SceneView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttr(context, attrs, 0);
}
public SceneView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAttr(context, attrs, defStyle);
}
private void initAttr(Context context, AttributeSet attrs, int defStyle) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SceneView);
mAnimationProgress = a.getFloat(R.styleable.SceneView_progress, 0);
mOvershootEnabled = a.getBoolean(R.styleable.SceneView_overshootEnabled, false);
mHeightChangeEnabled = a.getBoolean(R.styleable.SceneView_heightChangeEnabled, true);
mWidthChangeEnabled = a.getBoolean(R.styleable.SceneView_widthChangeEnabled, true);
int from_res = a.getResourceId(R.styleable.SceneView_scene0, -1);
//setFromLayout(from_res);
addSceneLayout(from_res);
addSceneLayout(a.getResourceId(R.styleable.SceneView_scene1, -1));
addSceneLayout(a.getResourceId(R.styleable.SceneView_scene2, -1));
addSceneLayout(a.getResourceId(R.styleable.SceneView_scene3, -1));
addSceneLayout(a.getResourceId(R.styleable.SceneView_scene4, -1));
addSceneLayout(a.getResourceId(R.styleable.SceneView_scene5, -1));
a.recycle();
reAddChildren();
}
public void setHeightChangeEnabled(boolean b) {
mHeightChangeEnabled = b;
}
public void setWidthChangeEnabled(boolean b) {
mWidthChangeEnabled = b;
}
public void setOverShootEnabled(boolean enableOvershoot) {
mOvershootEnabled = enableOvershoot;
}
public void setProgress(float progress) {
if (progress > 1 && !mOvershootEnabled) {
progress = 1;
}
if (progress < 0 && !mOvershootEnabled) {
progress = 0;
}
if (mAnimationProgress == progress) {
return;
}
mAnimationProgress = progress;
requestLayout();
}
public void addSceneLayout(int res) {
if(res < 0){
return;
}
sceneRes.add(res);
//mToRes = res;
mMyEndSpecs = null;
LayoutInflater inflater = LayoutInflater.from(getContext());
View mLayoutTo = inflater.inflate(res, null, false);
sceneViews.add(mLayoutTo);
initialed = false;
}
private void reAddChildren() {
List<View> children = new ArrayList<>();
LayoutInflater inflater = LayoutInflater.from(getContext());
View v = inflater.inflate(sceneRes.get(0), null, false);
findAllAnimatedViews(v, children);
for (View child : children) {
((ViewGroup) child.getParent()).removeView(child);
if(findViewById(child.getId()) == null){
addView(child);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(!initialed){
onLayoutAndMeasure(changed,l,t,r,b);
}
}
protected void onLayoutAndMeasure(boolean changed, int left, int top, int right, int bottom) {
//init specs if not already done
initSpecs();
if(sceneSpecs.size() == 0){
return;
}
int min_x = 0;
int max_x = 0;
int min_y = 0;
int max_y = 0;
float progressPerPart = 0;
if(sceneSpecs.size() <= 1){
progressPerPart = 1;
}else{
progressPerPart = 1f / (sceneSpecs.size()-1);
}
int part = (int) (mAnimationProgress / progressPerPart) +1;
if(part >= sceneSpecs.size()){
part = sceneSpecs.size()-1;
}
int beforePart = part > 0 ? part-1: 0;
float partProgress = (mAnimationProgress - (beforePart * progressPerPart)) /progressPerPart;
SparseArray<ViewSpecs> toSpecsArray = sceneSpecs.get(part);
SparseArray<ViewSpecs> fromSpecsArray = sceneSpecs.get(beforePart);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
ViewSpecs fromSpecs = fromSpecsArray.get(child.getId());
ViewSpecs toSpecs = toSpecsArray.get(child.getId());
//calc current attributes
int x = left + (int) (fromSpecs.x + (toSpecs.x - fromSpecs.x) * partProgress);
int y = top + (int) (fromSpecs.y + (toSpecs.y - fromSpecs.y) * partProgress);
int width = (int) (fromSpecs.width + (toSpecs.width - fromSpecs.width) * partProgress);
int height = (int) (fromSpecs.height + (toSpecs.height - fromSpecs.height) * partProgress);
int paddingTop = (int) (fromSpecs.paddingTop + (toSpecs.paddingTop - fromSpecs.paddingTop) * partProgress);
int paddingLeft = (int) (fromSpecs.paddingLeft + (toSpecs.paddingLeft - fromSpecs.paddingLeft) * partProgress);
int paddingBottom = (int) (fromSpecs.paddingBottom + (toSpecs.paddingBottom - fromSpecs.paddingBottom) * partProgress);
int paddingRight = (int) (fromSpecs.paddingRight + (toSpecs.paddingRight - fromSpecs.paddingRight) * partProgress);
float alpha = fromSpecs.alpha + (toSpecs.alpha - fromSpecs.alpha) * partProgress;
float rotation = fromSpecs.rotation + (toSpecs.rotation - fromSpecs.rotation) * partProgress;
float rotationX = fromSpecs.rotationX + (toSpecs.rotationX - fromSpecs.rotationX) * partProgress;
float rotationY = fromSpecs.rotationY + (toSpecs.rotationY - fromSpecs.rotationY) * partProgress;
float scaleX = fromSpecs.scaleX + (toSpecs.scaleX - fromSpecs.scaleX) * partProgress;
float scaleY = fromSpecs.scaleY + (toSpecs.scaleY - fromSpecs.scaleY) * partProgress;
float translationY = fromSpecs.translationY + (toSpecs.translationY - fromSpecs.translationY) * partProgress;
float translationX = fromSpecs.translationX + (toSpecs.translationX - fromSpecs.translationX) * partProgress;
//TODO fix it
//child.setPadding(paddingLeft,paddingTop,paddingRight,paddingBottom);
child.setAlpha(alpha);
if(alpha < 0){
if(child.getVisibility() == View.VISIBLE) {
child.setVisibility(View.INVISIBLE);
}
}else if(child.getVisibility() == View.INVISIBLE){
child.setVisibility(View.VISIBLE);
}
child.setRotation(rotation);
child.setRotationX(rotationX);
child.setRotationY(rotationY);
child.setScaleX(scaleX);
child.setScaleY(scaleY);
child.setTranslationX(translationX);
child.setTranslationY(translationY);
/*if(fromSpecs.backgroundDrawable != null && toSpecs.backgroundDrawable != null){
Drawable[] drawables = {fromSpecs.backgroundDrawable,toSpecs.backgroundDrawable};
drawables[0].setAlpha((int)(255*(1-partProgress)));
drawables[1].setAlpha((int)(255*partProgress));
LayerDrawable layoutDrawbable = new LayerDrawable(drawables);
if(Build.VERSION.SDK_INT >= 16){
child.setBackground(layoutDrawbable);
}else{
child.setBackgroundDrawable(layoutDrawbable);
}
}*/
child.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
child.layout(x, y, x + width, y + height);
min_x = Math.min(min_x, x - left);
max_x = Math.max(max_x, x + width);
min_y = Math.min(min_y, y - top);
max_y = Math.max(max_y, y + height);
}
//enable support for overshoot
//all children need new positions on screen
if (min_x < 0 || min_y < 0) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
//calc current attributes
int x = (int)child.getX();
int y = (int)child.getY();
int width = child.getWidth();
int height = child.getHeight();
child.layout(-min_x + x, -min_y + y, -min_x + x + width, -min_y + y + height);
}
}
//set new dimensions if enabled
LayoutParams params = getLayoutParams();
int height = max_y - min_y;
int width = max_x - min_x;
/*
if (mHeightChangeEnabled) {
params.height = height + getPaddingBottom();
} else {
params.height = mMyStartSpecs.height;
}
if (mWidthChangeEnabled) {
params.width = width + getPaddingRight();
} else {
params.width = mMyStartSpecs.width;
}
setLayoutParams(params);*/
if (mHeightChangeEnabled) {
height = height + getPaddingBottom();
} else {
height = mMyStartSpecs.height;
}
if (mWidthChangeEnabled) {
width = width + getPaddingRight();
} else {
width = mMyStartSpecs.width;
}
setMeasuredDimension(width, height);
}
public ViewSpecs getViewSpecsOfView(int scene,int res){
if(sceneSpecs.size() <= scene){
return null;
}
return sceneSpecs.get(scene).get(res);
}
public void setViewSpecsOfView(int scene, int res, ViewSpecs specs){
sceneSpecs.get(scene).put(res,specs);
}
public int getStartHeight() {
if(mMyStartSpecs == null){
return -1;
}
return mMyStartSpecs.height;
}
public int getStartWidth() {
if(mMyStartSpecs == null){
return -1;
}
return mMyStartSpecs.width;
}
public int getEndHeight() {
if(mMyEndSpecs == null){
return -1;
}
return mMyEndSpecs.height;
}
public int getEndWidth() {
if(mMyEndSpecs == null){
return -1;
}
return mMyEndSpecs.width;
}
private void initSpecs() {
if (initialed) {
return;
}
initialed = true;
reAddChildren();
List<View> fromViews = new ArrayList<>();
findAllAnimatedViews(sceneViews.get(0), fromViews);
sceneSpecs.clear();
sceneSpecs.add(new SparseArray<ViewSpecs>());
SparseArray<ViewSpecs> fromSpecs = sceneSpecs.get(0);
for (View v : fromViews) {
int id = v.getId();
ViewSpecs from = getViewSpecsOfView(v);
fromSpecs.append(id, from);
for(int i=1;i<sceneViews.size();i++) {
View mLayoutTo = sceneViews.get(i);
if(sceneSpecs.size() <= i){
sceneSpecs.add(new SparseArray<ViewSpecs>());
}
SparseArray<ViewSpecs> mToSpecs = sceneSpecs.get(i);
View toV = mLayoutTo.findViewById(id);
if (toV == null || toV.getVisibility() == View.GONE) {
ViewSpecs to = from.clone();
if(to.alpha == 1) {
to.alpha = 0;
}
mToSpecs.append(id, to);
} else {
ViewSpecs to = getViewSpecsOfView(toV);
if (toV.getVisibility() == View.INVISIBLE) {
if(to.alpha == 1) {
to.alpha = 0;
}
}
mToSpecs.append(id, to);
}
}
}
for(OnSceneSpecsCreatedListener l: listeners){
l.onSceneSpecsCreated(this);
}
}
public void invalidateChildren() {
mMyStartSpecs = null;
mMyEndSpecs = null;
forceLayout();
requestLayout();
}
private void findAllAnimatedViews(View v, List<View> temp) {
if (v.getId() != View.NO_ID) {
temp.add(v);
}
if (v instanceof ViewGroup) {
ViewGroup g = (ViewGroup) v;
for (int i = 0; i < g.getChildCount(); i++) {
findAllAnimatedViews(g.getChildAt(i), temp);
}
}
}
/*
* creates the ViewSpecs for an specific view
* just use this for Views in mLayoutFrom & mLayoutTo
*/
private ViewSpecs getViewSpecsOfView(View target) {
View parent = (View) target.getParent();
int x = target.getLeft();
int y = target.getTop();
while (parent != null) {
x += parent.getLeft();
y += parent.getTop();
parent = (View) parent.getParent();
}
ViewSpecs s = new ViewSpecs();
s.x = x;
s.y = y;
s.height = target.getHeight();
s.width = target.getWidth();
s.alpha = target.getAlpha();
s.rotation = target.getRotation();
s.rotationX = target.getRotationX();
s.rotationY = target.getRotationY();
s.scaleX = target.getScaleX();
s.scaleY = target.getScaleY();
s.translationX = target.getTranslationX();
s.translationY = target.getTranslationY();
s.backgroundDrawable = target.getBackground();
s.paddingTop = target.getPaddingTop();
s.paddingLeft = target.getPaddingLeft();
s.paddingBottom = target.getPaddingBottom();
s.paddingRight = target.getPaddingRight();
if(s.backgroundDrawable != null){
s.backgroundDrawable = s.backgroundDrawable.mutate();
}
return s;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMyStartSpecs == null || mMyEndSpecs == null) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSize > 0 && widthSize > getPaddingLeft() + getPaddingRight()) {
widthSize -= getPaddingLeft() + getPaddingRight();
}
if (heightSize > 0 && heightSize > getPaddingTop() + getPaddingBottom()) {
heightSize -= getPaddingTop() + getPaddingBottom();
}
int widthSpec = makeMeasureSpec(widthSize, widthMode);
int heightSpec = makeMeasureSpec(heightSize, heightMode);
mMyStartSpecs = new ViewSpecs();
mMyEndSpecs = new ViewSpecs();
Log.d(TAG,"onMeasure: ");
initialed = false;
/*sceneViews.clear();
for(int i=0;i<sceneRes.size();i++) {
LayoutInflater inflater = LayoutInflater.from(getContext());
View mLayoutTo = inflater.inflate(sceneRes.get(i), null, false);
sceneViews.add(mLayoutTo);
}*/
for(View layoutTo : sceneViews){
for(int i=0;i<getChildCount();i++){
View child = getChildAt(i);
if(child instanceof TextView){
View eq = layoutTo.findViewById(child.getId());
cloneAttributes(child, eq);
}
}
layoutTo.measure(widthSpec, heightSpec);
layoutTo.layout(0, 0, layoutTo.getMeasuredWidth(), layoutTo.getMeasuredHeight());
}
View firstLayoutFrom = sceneViews.get(0);
mMyStartSpecs.height = getPaddingTop() + firstLayoutFrom.getMeasuredHeight() + getPaddingBottom();
mMyStartSpecs.width = getPaddingLeft() + firstLayoutFrom.getMeasuredWidth() + getPaddingRight();
View lastLayoutTo = sceneViews.get(sceneViews.size()-1);
mMyEndSpecs.height = getPaddingTop() + lastLayoutTo.getHeight() + getPaddingBottom();
mMyEndSpecs.width = getPaddingLeft() + lastLayoutTo.getWidth() + getPaddingRight();
}
onLayoutAndMeasure(true, getPaddingLeft(), getPaddingTop(), -1, -1);
//setMeasuredDimension(getLayoutParams().width, getLayoutParams().height);
}
public void addOnSceneSpecsCreatedListener(OnSceneSpecsCreatedListener listener){
this.listeners.add(listener);
}
public void removeOnSceneSpecsCreatedListener(OnSceneSpecsCreatedListener listener){
this.listeners.remove(listener);
}
private void cloneAttributes(View from, View to){
if(from == null || to == null){
return;
}
if(from instanceof TextView && to instanceof TextView){
Log.d(TAG,"copy Text: "+((TextView) from).getText());
((TextView)to).setText(((TextView) from).getText());
((TextView)to).setTypeface(((TextView) from).getTypeface());
}
if(from instanceof ImageView && to instanceof ImageView){
((ImageView) to).setImageDrawable(((ImageView) from).getDrawable());
}
}
public interface OnSceneSpecsCreatedListener{
/*
invoked when all view specs are created
you can now modify them
*/
void onSceneSpecsCreated(SceneView sceneView);
}
public static class ViewSpecs implements Cloneable {
public int x;
public int y;
public int height;
public int width;
public int paddingLeft;
public int paddingRight;
public int paddingTop;
public int paddingBottom;
public float alpha = 1;
public float rotation;
public float rotationX;
public float rotationY;
public float scaleX;
public float scaleY;
public float translationX;
public float translationY;
public Drawable backgroundDrawable;
public ViewSpecs() {
}
@Override
public ViewSpecs clone() {
try {
return (ViewSpecs) super.clone();
}catch (Exception e){
return null;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment