/** Copyright (C) 2012 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/packagecom.example.asus.myapplication.utils;
importandroid.content.Context;
importandroid.content.res.TypedArray;
importandroid.graphics.Bitmap;
importandroid.graphics.Canvas;
importandroid.graphics.Paint;
importandroid.graphics.PixelFormat;
importandroid.graphics.PorterDuff;
importandroid.graphics.PorterDuffColorFilter;
importandroid.graphics.Rect;
importandroid.graphics.drawable.Drawable;
importandroid.os.Build;
importandroid.os.Parcel;
importandroid.os.Parcelable;
importandroid.support.annotation.NonNull;
importandroid.support.v4.view.MotionEventCompat;
importandroid.support.v4.view.ViewCompat;
importandroid.support.v4.view.ViewPager;
importandroid.support.v4.widget.ViewDragHelper;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewGroup;
importjava.lang.reflect.Field;
importjava.lang.reflect.Method;
importjava.util.ArrayList;
public classResideLayout extendsViewGroup {
private static finalString TAG= "ResideLayout";
/*** Default size of the overhang for DetailsBean pane in the open state.* At least this much of DetailsBean sliding pane will remain visible.* This indicates that there is more content available and provides* DetailsBean "physical" edge to grab to pull it closed.*/private static final intDEFAULT_OVERHANG_SIZE= 80; // dp;/*** The fade color used for the sliding panel. 0 = no fading.*/private intmSliderFadeColor= 0x99000000;
/*** Minimum velocity that will be detected as DetailsBean fling*/private static final intMIN_FLING_VELOCITY= 400; // dips per second/*** The fade color used for the panel covered by the slider. 0 = no fading.*/private intmCoveredFadeColor= 0xcc000000;
/*** The size of the overhang in pixels.* This is the minimum section of the sliding panel that will* be visible in the open state to allow for DetailsBean closing drag.*/private final intmOverhangSize;
/*** True if DetailsBean panel can slide with the current measurements*/private booleanmCanSlide;
/*** The child view that can slide, if any.*/privateView mSlideableView;
/*** How far the panel is offset from its closed position.* range [0, 1] where 0 = closed, 1 = open.*/private floatmSlideOffset;
/*** How far the non-sliding panel is parallaxed from its usual position when open.* range [0, 1]*/private floatmParallaxOffset;
/*** How far in pixels the slideable panel may move.*/private intmSlideRange;
/*** A panel view is locked into internal scrolling or another condition that* is preventing DetailsBean drag.*/private booleanmIsUnableToDrag;
/*** Distance in pixels to parallax the fixed pane by when fully closed*/private intmParallaxBy;
private floatmInitialMotionX;
private floatmInitialMotionY;
privatePanelSlideListener mPanelSlideListener;
private finalViewDragHelper mDragHelper;
/*** Stores whether or not the pane was open the last time it was slideable.* If open/close operations are invoked this state is modified. Used by* instance state save/restore.*/private booleanmPreservedOpenState;
private booleanmFirstLayout= true;
private finalArrayList mPostedRunnables=
newArrayList();
static finalSlidingPanelLayoutImpl IMPL;
static{
final intdeviceVersion = Build.VERSION.SDK_INT;
if(deviceVersion >= 17) {
IMPL= newSlidingPanelLayoutImplJBMR1();
} else if(deviceVersion >= 16) {
IMPL= newSlidingPanelLayoutImplJB();
} else{
IMPL= newSlidingPanelLayoutImplBase();
}
}
/*** Listener for monitoring events about sliding panes.*/public interfacePanelSlideListener {
/*** Called when DetailsBean sliding pane's position changes.*@parampanelThe child view that was moved*@paramslideOffsetThe new offset of this sliding pane within its range, from 0-1*/public voidonPanelSlide(View panel, floatslideOffset);
/*** Called when DetailsBean sliding pane becomes slid completely open. The pane may or may not* be interactive at this point depending on how much of the pane is visible.*@parampanelThe child view that was slid to an open position, revealing other panes*/public voidonPanelOpened(View panel);
/*** Called when DetailsBean sliding pane becomes slid completely closed. The pane is now guaranteed* to be interactive. It may now obscure other views in the layout.*@parampanelThe child view that was slid to DetailsBean closed position*/public voidonPanelClosed(View panel);
}
publicResideLayout(Context context) {
this(context, null);
}
publicResideLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
publicResideLayout(Context context, AttributeSet attrs, intdefStyle) {
super(context, attrs, defStyle);
final floatdensity = context.getResources().getDisplayMetrics().density;
mOverhangSize= (int) (DEFAULT_OVERHANG_SIZE* density + 0.5f);
setWillNotDraw(false);
mDragHelper= ViewDragHelper.create(this, 0.5f, newDragHelperCallback());
mDragHelper.setMinVelocity(MIN_FLING_VELOCITY* density);
}
/*** Set DetailsBean distance to parallax the lower pane by when the upper pane is in its* fully closed state. The lower pane will scroll between this position and* its fully open state.**@paramparallaxByDistance to parallax by in pixels*/public voidsetParallaxDistance(intparallaxBy) {
mParallaxBy= parallaxBy;
requestLayout();
}
/***@returnThe distance the lower pane will parallax by when the upper pane is fully closed.**@see#setParallaxDistance(int)*/public intgetParallaxDistance() {
returnmParallaxBy;
}
public voidsetPanelSlideListener(PanelSlideListener listener) {
mPanelSlideListener= listener;
}
voiddispatchOnPanelSlide(View panel) {
if(mPanelSlideListener!= null) {
mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
}
}
voiddispatchOnPanelOpened(View panel) {
if(mPanelSlideListener!= null) {
mPanelSlideListener.onPanelOpened(panel);
}
}
voiddispatchOnPanelClosed(View panel) {
if(mPanelSlideListener!= null) {
mPanelSlideListener.onPanelClosed(panel);
}
}
voidupdateObscuredViewsVisibility(View panel) {
final intstartBound = getPaddingLeft();
final intendBound = getWidth() - getPaddingRight();
final inttopBound = getPaddingTop();
final intbottomBound = getHeight() - getPaddingBottom();
final intleft;
final intright;
final inttop;
final intbottom;
if(panel != null&& viewIsOpaque(panel)) {
left = panel.getLeft();
right = panel.getRight();
top = panel.getTop();
bottom = panel.getBottom();
} else{
left = right = top = bottom = 0;
}
for(inti = 0, childCount = getChildCount(); i < childCount; i++) {
finalView child = getChildAt(i);
if(child == panel) {
// There are still more children above the panel but they won't be affected.break;
}
final intclampedChildLeft = Math.max(startBound, child.getLeft());
final intclampedChildTop = Math.max(topBound, child.getTop());
final intclampedChildRight = Math.min(endBound, child.getRight());
final intclampedChildBottom = Math.min(bottomBound, child.getBottom());
final intvis;
if(clampedChildLeft >= left && clampedChildTop >= top &&
clampedChildRight <= right && clampedChildBottom <= bottom) {
vis = INVISIBLE;
} else{
vis = VISIBLE;
}
child.setVisibility(vis);
}
}
voidsetAllChildrenVisible() {
for(inti = 0, childCount = getChildCount(); i < childCount; i++) {
finalView child = getChildAt(i);
if(child.getVisibility() == INVISIBLE) {
child.setVisibility(VISIBLE);
}
}
}
private static booleanviewIsOpaque(View v) {
if(ViewCompat.isOpaque(v)) return true;
// View#isOpaque didn't take all valid opaque scrollbar modes into account// before API 18 (JB-MR2). On newer devices rely solely on isOpaque above and return false// here. On older devices, check the view's background drawable directly as DetailsBean fallback.if(Build.VERSION.SDK_INT>= 18) return false;
finalDrawable bg = v.getBackground();
returnbg != null&& bg.getOpacity() == PixelFormat.OPAQUE;
}
@Overrideprotected voidonAttachedToWindow() {
super.onAttachedToWindow();
mFirstLayout= true;
}
@Overrideprotected voidonDetachedFromWindow() {
super.onDetachedFromWindow();
mFirstLayout= true;
for(finalDisableLayerRunnable dlr : mPostedRunnables) {
dlr.run();
}
mPostedRunnables.clear();
}
@Overrideprotected voidonMeasure(intwidthMeasureSpec, intheightMeasureSpec) {
intwidthMode = MeasureSpec.getMode(widthMeasureSpec);
intwidthSize = MeasureSpec.getSize(widthMeasureSpec);
intheightMode = MeasureSpec.getMode(heightMeasureSpec);
intheightSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthMode != MeasureSpec.EXACTLY) {
if(isInEditMode()) {
if(widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = 300;
}
} else{
throw newIllegalStateException("Width must have an exact value or MATCH_PARENT");
}
} else if(heightMode == MeasureSpec.UNSPECIFIED) {
if(isInEditMode()) {
if(heightMode == MeasureSpec.UNSPECIFIED) {
heightMode = MeasureSpec.AT_MOST;
heightSize = 300;
}
} else{
throw newIllegalStateException("Height must not be UNSPECIFIED");
}
}
intlayoutHeight = 0;
intmaxLayoutHeight = -1;
switch(heightMode) {
caseMeasureSpec.EXACTLY:
layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
break;
caseMeasureSpec.AT_MOST:
maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
break;
}
floatweightSum = 0;
booleancanSlide = false;
final intwidthAvailable = widthSize - getPaddingLeft() - getPaddingRight();
intwidthRemaining = widthAvailable;
final intchildCount = getChildCount();
if(childCount > 2) {
Log.e(TAG, "onMeasure: More than two child views are not supported.");
}
// We'll findfragment the current one below.mSlideableView= null;
// First pass. Measure based on child LayoutParams width/height.// Weight will incur DetailsBean second pass.for(inti = 0; i < childCount; i++) {
finalView child = getChildAt(i);
finalLayoutParams lp = (LayoutParams) child.getLayoutParams();
if(child.getVisibility() == GONE) {
lp.dimWhenOffset= false;
continue;
}
if(lp.weight> 0) {
weightSum += lp.weight;
// If we have no width, weight is the only contributor to the final size.// Measure this view on the weight pass only.if(lp.width== 0) continue;
}
intchildWidthSpec;
final inthorizontalMargin = lp.leftMargin+ lp.rightMargin;
if(lp.width== LayoutParams.WRAP_CONTENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(widthAvailable - horizontalMargin,
MeasureSpec.AT_MOST);
} else if(lp.width== LayoutParams.MATCH_PARENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(widthAvailable - horizontalMargin,
MeasureSpec.EXACTLY);
} else{
childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
}
intchildHeightSpec;
if(lp.height== LayoutParams.WRAP_CONTENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);
} else if(lp.height== LayoutParams.MATCH_PARENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);
} else{
childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
}
child.measure(childWidthSpec, childHeightSpec);
final intchildWidth = child.getMeasuredWidth();
final intchildHeight = child.getMeasuredHeight();
if(heightMode == MeasureSpec.AT_MOST&& childHeight > layoutHeight) {
layoutHeight = Math.min(childHeight, maxLayoutHeight);
}
widthRemaining -= childWidth;
canSlide |= lp.slideable= widthRemaining < 0;
if(lp.slideable) {
mSlideableView= child;
}
}
// Resolve weight and make sure non-sliding panels are smaller than the full screen.if(canSlide || weightSum > 0) {
// final int fixedPanelWidthLimit = widthAvailable - mOverhangSize;for(inti = 0; i < childCount; i++) {
finalView child = getChildAt(i);
if(child.getVisibility() == GONE) {
continue;
}
finalLayoutParams lp = (LayoutParams) child.getLayoutParams();
if(child.getVisibility() == GONE) {
continue;
}
final booleanskippedFirstPass = lp.width== 0&& lp.weight> 0;
final intmeasuredWidth = skippedFirstPass ? 0: child.getMeasuredWidth();
if(canSlide && child != mSlideableView) {
if(lp.width< 0&& (measuredWidth > widthAvailable || lp.weight> 0)) {
// Fixed panels in DetailsBean sliding configuration should// be clamped to the fixed panel limit.final intchildHeightSpec;
if(skippedFirstPass) {
// Do initial height measurement if we skipped measuring this view// the first time around.if(lp.height== LayoutParams.WRAP_CONTENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
MeasureSpec.AT_MOST);
} else if(lp.height== LayoutParams.MATCH_PARENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
MeasureSpec.EXACTLY);
} else{
childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
MeasureSpec.EXACTLY);
}
} else{
childHeightSpec = MeasureSpec.makeMeasureSpec(
child.getMeasuredHeight(), Measure