Created
December 7, 2015 15:03
-
-
Save timfreiheit/0aec9a25e5bf5c854b5d to your computer and use it in GitHub Desktop.
SceneView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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