SizeAdaptiveLayoutpublic class SizeAdaptiveLayout extends android.view.ViewGroup A layout that switches between its children based on the requested layout height.
Each child specifies its minimum and maximum valid height. Results are undefined
if children specify overlapping ranges. A child may specify the maximum height
as 'unbounded' to indicate that it is willing to be displayed arbitrarily tall.
See {@link SizeAdaptiveLayout.LayoutParams} for a full description of the
layout parameters used by SizeAdaptiveLayout. |
Fields Summary |
---|
private static final String | TAG | private static final boolean | DEBUG | private static final boolean | REPORT_BAD_BOUNDS | private static final long | CROSSFADE_TIME | private static final int | MIN_VALID_HEIGHT | private static final int | MAX_VALID_HEIGHT | private android.view.View | mActiveChild | private android.view.View | mLastActive | private android.animation.AnimatorSet | mTransitionAnimation | private android.animation.Animator.AnimatorListener | mAnimatorListener | private android.animation.ObjectAnimator | mFadePanel | private android.animation.ObjectAnimator | mFadeView | private int | mCanceledAnimationCount | private android.view.View | mEnteringView | private android.view.View | mLeavingView | private android.view.View | mModestyPanel | private int | mModestyPanelTop |
Constructors Summary |
---|
public SizeAdaptiveLayout(android.content.Context context)
this(context, null);
| public SizeAdaptiveLayout(android.content.Context context, android.util.AttributeSet attrs)
this(context, attrs, 0);
| public SizeAdaptiveLayout(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr)
this(context, attrs, defStyleAttr, 0);
| public SizeAdaptiveLayout(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes)
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
|
Methods Summary |
---|
protected boolean | checkLayoutParams(ViewGroup.LayoutParams p)
return p instanceof SizeAdaptiveLayout.LayoutParams;
| private int | clampSizeToBounds(int measuredHeight, android.view.View child)
SizeAdaptiveLayout.LayoutParams lp =
(SizeAdaptiveLayout.LayoutParams) child.getLayoutParams();
int heightIn = View.MEASURED_SIZE_MASK & measuredHeight;
int height = Math.max(heightIn, lp.minHeight);
if (lp.maxHeight != SizeAdaptiveLayout.LayoutParams.UNBOUNDED) {
height = Math.min(height, lp.maxHeight);
}
if (REPORT_BAD_BOUNDS && heightIn != height) {
Log.d(TAG, this + "child view " + child + " " +
"measured out of bounds at " + heightIn +"px " +
"clamped to " + height + "px");
}
return height;
| protected com.android.internal.widget.SizeAdaptiveLayout$LayoutParams | generateDefaultLayoutParams()
if (DEBUG) Log.d(TAG, "generate default layout from null");
return new SizeAdaptiveLayout.LayoutParams();
| public com.android.internal.widget.SizeAdaptiveLayout$LayoutParams | generateLayoutParams(android.util.AttributeSet attrs)
if (DEBUG) Log.d(TAG, "generate layout from attrs");
return new SizeAdaptiveLayout.LayoutParams(getContext(), attrs);
| protected com.android.internal.widget.SizeAdaptiveLayout$LayoutParams | generateLayoutParams(ViewGroup.LayoutParams p)
if (DEBUG) Log.d(TAG, "generate default layout from viewgroup");
return new SizeAdaptiveLayout.LayoutParams(p);
| public android.view.View | getModestyPanel()Visible for testing
return mModestyPanel;
| public android.animation.Animator | getTransitionAnimation()Visible for testing
return mTransitionAnimation;
| private void | initialize()
mModestyPanel = new View(getContext());
// If the SizeAdaptiveLayout has a solid background, use it as a transition hint.
Drawable background = getBackground();
if (background instanceof StateListDrawable) {
StateListDrawable sld = (StateListDrawable) background;
sld.setState(StateSet.WILD_CARD);
background = sld.getCurrent();
}
if (background instanceof ColorDrawable) {
mModestyPanel.setBackgroundDrawable(background);
}
SizeAdaptiveLayout.LayoutParams layout =
new SizeAdaptiveLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mModestyPanel.setLayoutParams(layout);
addView(mModestyPanel);
mFadePanel = ObjectAnimator.ofFloat(mModestyPanel, "alpha", 0f);
mFadeView = ObjectAnimator.ofFloat(null, "alpha", 0f);
mAnimatorListener = new BringToFrontOnEnd();
mTransitionAnimation = new AnimatorSet();
mTransitionAnimation.play(mFadeView).with(mFadePanel);
mTransitionAnimation.setDuration(CROSSFADE_TIME);
mTransitionAnimation.addListener(mAnimatorListener);
| public void | onAttachedToWindow()
mLastActive = null;
// make sure all views start off invisible.
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).setVisibility(View.GONE);
}
| protected void | onLayout(boolean changed, int left, int top, int right, int bottom)
if (DEBUG) Log.d(TAG, this + " onlayout height: " + (bottom - top));
mLastActive = mActiveChild;
int measureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top,
View.MeasureSpec.EXACTLY);
mActiveChild = selectActiveChild(measureSpec);
if (mActiveChild == null) return;
mActiveChild.setVisibility(View.VISIBLE);
if (mLastActive != mActiveChild && mLastActive != null) {
if (DEBUG) Log.d(TAG, this + " changed children from: " + mLastActive +
" to: " + mActiveChild);
mEnteringView = mActiveChild;
mLeavingView = mLastActive;
mEnteringView.setAlpha(1f);
mModestyPanel.setAlpha(1f);
mModestyPanel.bringToFront();
mModestyPanelTop = mLeavingView.getHeight();
mModestyPanel.setVisibility(View.VISIBLE);
// TODO: mModestyPanel background should be compatible with mLeavingView
mLeavingView.bringToFront();
if (mTransitionAnimation.isRunning()) {
mTransitionAnimation.cancel();
}
mFadeView.setTarget(mLeavingView);
mFadeView.setFloatValues(0f);
mFadePanel.setFloatValues(0f);
mTransitionAnimation.setupStartValues();
mTransitionAnimation.start();
}
final int childWidth = mActiveChild.getMeasuredWidth();
final int childHeight = mActiveChild.getMeasuredHeight();
// TODO investigate setting LAYER_TYPE_HARDWARE on mLastActive
mActiveChild.layout(0, 0, childWidth, childHeight);
if (DEBUG) Log.d(TAG, "got modesty offset of " + mModestyPanelTop);
mModestyPanel.layout(0, mModestyPanelTop, childWidth, mModestyPanelTop + childHeight);
| protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
if (DEBUG) Log.d(TAG, this + " measure spec: " +
MeasureSpec.toString(heightMeasureSpec));
View model = selectActiveChild(heightMeasureSpec);
if (model == null) {
setMeasuredDimension(0, 0);
return;
}
SizeAdaptiveLayout.LayoutParams lp =
(SizeAdaptiveLayout.LayoutParams) model.getLayoutParams();
if (DEBUG) Log.d(TAG, "active min: " + lp.minHeight + " max: " + lp.maxHeight);
measureChild(model, widthMeasureSpec, heightMeasureSpec);
int childHeight = model.getMeasuredHeight();
int childWidth = model.getMeasuredHeight();
int childState = combineMeasuredStates(0, model.getMeasuredState());
if (DEBUG) Log.d(TAG, "measured child at: " + childHeight);
int resolvedWidth = resolveSizeAndState(childWidth, widthMeasureSpec, childState);
int resolvedHeight = resolveSizeAndState(childHeight, heightMeasureSpec, childState);
if (DEBUG) Log.d(TAG, "resolved to: " + resolvedHeight);
int boundedHeight = clampSizeToBounds(resolvedHeight, model);
if (DEBUG) Log.d(TAG, "bounded to: " + boundedHeight);
setMeasuredDimension(resolvedWidth, boundedHeight);
| private android.view.View | selectActiveChild(int heightMeasureSpec)
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
View unboundedView = null;
View tallestView = null;
int tallestViewSize = 0;
View smallestView = null;
int smallestViewSize = Integer.MAX_VALUE;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child != mModestyPanel) {
SizeAdaptiveLayout.LayoutParams lp =
(SizeAdaptiveLayout.LayoutParams) child.getLayoutParams();
if (DEBUG) Log.d(TAG, "looking at " + i +
" with min: " + lp.minHeight +
" max: " + lp.maxHeight);
if (lp.maxHeight == SizeAdaptiveLayout.LayoutParams.UNBOUNDED &&
unboundedView == null) {
unboundedView = child;
}
if (lp.maxHeight > tallestViewSize) {
tallestViewSize = lp.maxHeight;
tallestView = child;
}
if (lp.minHeight < smallestViewSize) {
smallestViewSize = lp.minHeight;
smallestView = child;
}
if (heightMode != MeasureSpec.UNSPECIFIED &&
heightSize >= lp.minHeight && heightSize <= lp.maxHeight) {
if (DEBUG) Log.d(TAG, " found exact match, finishing early");
return child;
}
}
}
if (unboundedView != null) {
tallestView = unboundedView;
}
if (heightMode == MeasureSpec.UNSPECIFIED || heightSize > tallestViewSize) {
return tallestView;
} else {
return smallestView;
}
|
|