package com.sothree.slidinguppanel;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import cn.mutils.app.R;
public class SlidingUpPanelLayout extends ViewGroup {
/**
* Default peeking out panel height
*/
private static final int DEFAULT_PANEL_HEIGHT = 48; // dp;
/**
* Default anchor point height
*/
private static final float DEFAULT_ANCHOR_POINT = 1.0f; // In relative %
/**
* Default initial state for the component
*/
private static PanelState DEFAULT_SLIDE_STATE = PanelState.COLLAPSED;
/**
* Default Minimum velocity that will be detected as a fling
*/
private static final int DEFAULT_MIN_FLING_VELOCITY = 600; // dips per second
/**
* Minimum velocity that will be detected as a fling
*/
private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
/**
* The size of the overhang in pixels.
*/
private int mPanelHeight = -1;
/**
* If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
* used for dragging.
*/
private View mDragView;
/**
* If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
* used for dragging.
*/
private int mDragViewResId = -1;
/**
* The child view that can slide, if any.
*/
private View mSlideableView;
/**
* The main view
*/
private View mMainView;
/**
* Current state of the slideable view.
*/
public enum PanelState {
EXPANDED,
COLLAPSED,
ANCHORED,
HIDDEN,
DRAGGING
}
private PanelState mSlideState = DEFAULT_SLIDE_STATE;
private PanelState mLastSlideState = DEFAULT_SLIDE_STATE;
/**
* If the current slide state is DRAGGING, this will store the last non dragging state
*/
private PanelState mLastNotDraggingSlideState = DEFAULT_SLIDE_STATE;
/**
* 上一次不包含影藏的状态
*/
private PanelState mLastNotHiddenSlideState = DEFAULT_SLIDE_STATE;
/**
* How far the panel is offset from its expanded position. range [0, 1] where 0 = collapsed, 1 =
* expanded.
*/
private float mSlideOffset;
/**
* How far in pixels the slideable panel may move.
*/
private int mSlideRange;
/**
* An anchor point where the panel can stop during sliding
*/
private float mAnchorPoint = 1.f;
/**
* Flag indicating that sliding feature is enabled\disabled
*/
private boolean mDragEnabled = true;
private float mInitialMotionX;
private float mInitialMotionY;
private boolean mActionDownOnSlideableView;
private List<PanelSlideListener> mPanelSlideListeners = new ArrayList<>();
private final ViewDragHelper mDragHelper;
/**
* Stores whether or not the pane was expanded the last time it was slideable. If
* expand/collapse operations are invoked this state is modified. Used by instance state
* save/restore.
*/
private boolean mFirstLayout = true;
/**
* 当前的滑动状态是否是没有被布局的情况下设置的
*/
private boolean mSlideStateWithoutLayout = true;
/**
* 可滑动区域补白,可以为负数
*/
private int mSlideRangePadding;
/**
* 悬挂高度,该高度相对于控件本身
*/
private int mAnchorHeight = -1;
/**
* 悬挂位置,相对于父控件的百分比
*/
private float mAnchorPointInParent = 1.0f;
/**
* 展开点高度
*/
private int mExpandHeight = -1;
/**
* 展开位置,滑动到相对于父控件本身的该比例值时自动执行EXPANDED状态切换
*/
private float mExpandPointInParent = 1.0f;
/**
* 展开位置,滑动到相对于SlideRange区域的该百分比自动执行EXPANDED状态切换
*
* @see #mAnchorPoint
*/
private float mExpandPoint = 1.0f;
/**
* 拦截横向拖拽事件
*
* 设置为true即可拦截SlideableView中的HorizontalScrollView、ViewPager等组件的横向滑动事件
*/
private boolean mDragHorizontalIntercept;
/**
* 拦截拖拽事件的滑动阀值
*/
private int mInterceptTouchSlop;
/**
* 设置是否在DRAGGING状态下捕捉UI事件
*
* 是否在代码调用设置状态之后响应触摸事件
*/
private boolean mCaptureViewOnDraggingState = true;
/**
* SlideOffset误差
*
* 由于SlideOffset转换成Top值存在float与int换算的强制转换存在误差
*/
private float mSlideOffsetSlop;
/**
* 额外的悬挂高度
*/
private ArrayList<Integer> mAnchorHeightExtras = new ArrayList<>();
/**
* 正在作用的额外悬挂高度索引
*/
private int mAnchorHeightExtraIndex = -1;
/**
* 额外的悬挂点
*/
private ArrayList<Float> mAnchorPointExtras = new ArrayList<>();
/**
* ScrollView滚动到顶部
*/
private boolean mScrollAtTop;
/**
* ScrollView引用
*/
private IScrollableView mScrollableView;
/**
* Hook的滚动视图
*/
private IScrollableView mHookScrollableView;
/**
* Hook事件的Y坐标
*/
private float mHookMotionY;
/**
* Hook事件工作状态
*/
private boolean mHookMotionRunning;
/**
* Hook事件上滑状态标记,解决重复调用Cancel事件
*/
private boolean mHookMotionMoveUpHandled;
/**
* Panel顶部的透明区域
*/
private int mPanelTransparentTop;
/**
* 悬浮视图,用于支持在透明区域内的事件拖拽
*/
private View mPanelHoverView;
/**
* 手指拖动的联动杠杆倍数
*/
private float mDragLeverage;
/**
* Listener for monitoring events about sliding panes.
*/
public interface PanelSlideListener {
/**
* Called when a sliding pane's position changes.
*
* @param panel The child view that was moved
* @param slideOffset The new offset of this sliding pane within its range, from 0-1
*/
public void onPanelSlide(View panel, float slideOffset);
/**
* Called when a sliding panel state changes
*
* @param panel The child view that was slid to an collapsed position
*/
public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState);
}
/**
* No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset of the
* listener methods you can extend this instead of implement the full interface.
*/
public static class SimplePanelSlideListener implements PanelSlideListener {
@Override
public void onPanelSlide(View panel, float slideOffset) {
}
@Override
public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState) {
switch (newState) {
case COLLAPSED:
onPanelCollapsed(panel);
break;
case ANCHORED:
onPanelAnchored(panel);
break;
case EXPANDED:
onPanelExpanded(panel);
break;
case HIDDEN:
onPanelHidden(panel);
break;
}
}
public void onPanelCollapsed(View panel) {
}
public void onPanelAnchored(View panel) {
}
public void onPanelExpanded(View panel) {
}
public void onPanelHidden(View panel) {
}
}
public SlidingUpPanelLayout(Context context) {
this(context, null);
}
public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode()) {
mDragHelper = null;
return;
}
float sensitivity = 5f;
Interpolator scrollerInterpolator = null;
if (attrs != null) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);
if (ta != null) {
mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_panelHeight, -1);
mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_flingVelocity, DEFAULT_MIN_FLING_VELOCITY);
mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_dragView, -1);
mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_anchorPoint, DEFAULT_ANCHOR_POINT);
mSlideState = PanelState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_initialState, DEFAULT_SLIDE_STATE.ordinal())];
int interpolatorResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_scrollInterpolator, -1);
if (interpolatorResId != -1) {
scrollerInterpolator = AnimationUtils.loadInterpolator(context, interpolatorResId);
}
mSlideRangePadding = ta.getDimensionPixelOffset(R.styleable.SlidingUpPanelLayout_slideRangePadding, 0);
mAnchorHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_anchorHeight, -1);
mExpandHeight = ta.getDimensionPixelOffset(R.styleable.SlidingUpPanelLayout_expandHeight, -1);
mExpandPointInParent = ta.getFloat(R.styleable.SlidingUpPanelLayout_expandPointInParent, 1.0f);
mExpandPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_expandPoint, 1.0f);
mAnchorPointInParent = ta.getFloat(R.styleable.SlidingUpPanelLayout_anchorPointInParent, 1.0f);
sensitivity = ta.getFloat(R.styleable.SlidingUpPanelLayout_sensitivity, sensitivity);
}
ta.recycle();
}
final float density = context.getResources().getDisplayMetrics().density;
if (mPanelHeight == -1) {
mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
}
mDragHelper = ViewDragHelper.create(this, sensitivity, scrollerInterpolator, new DragHelperCallback());
mDragHelper.setMinVelocity(mMinFlingVelocity * density);
if (mAnchorPoint != 1.0f) {
mAnchorPointInParent = 1.0f;
}
if (mAnchorHeight != -1 && mAnchorHeight > mPanelHeight) {
mAnchorPoint = mAnchorPointInParent = 1;
}
if (mExpandPoint != 1.0f) {
mExpandPointInParent = 1.0f;
}
if (mExpandHeight != -1 && mExpandHeight > mPanelHeight) {
mExpandPoint = mExpandPointInParent = 1;
}
mInterceptTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
/**
* Set the Drag View after the view is inflated
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (mDragViewResId != -1) {
setDragView(findViewById(mDragViewResId));
}
mSlideableView = getChildCount() == 2 ? getChildAt(1) : null;
}
public boolean isScrollAtTop() {
return mScrollAtTop;
}
public void setScrollAtTop(boolean scrollAtTop) {
mScrollAtTop = scrollAtTop;
mScrollableView = null;
}
public void setScrollAtTop(boolean scrollAtTop, View scrollableView) {
mScrollAtTop = scrollAtTop;
mScrollableView = scrollableView != null ? new ScrollableVew(scrollableView) : null;
}
public void setScrollAtTop(IScrollableView scrollableView, boolean scrollAtTop) {
mScrollAtTop = scrollAtTop;
mScrollableView = scrollableView;
}
public void setHookScrollableView(IScrollableView scrollableView) {
mHookScrollableView = scrollableView;
}
public void setPanelTransparentTop(int panelTransparentTop) {
mPanelTransparentTop = panelTransparentTop;
}
public void setPanelHoverView(View hoverView) {
mPanelHoverView = hoverView;
}
public float getDragLeverage() {
return mDragLeverage;
}
public void setDragLeverage(float dragLeverage) {
mDragLeverage = dragLeverage;
}
public boolean isCaptureViewOnDraggingState() {
return mCaptureViewOnDraggingState;
}
public void setCaptureViewOnDraggingState(boolean captureViewOnDraggingState) {
mCaptureViewOnDraggingState = captureViewOnDraggingState;
}
public boolean isDragHorizontalIntercept() {
return mDragHorizontalIntercept;
}
public void setDragHorizontalIntercept(boolean intercept) {
mDragHorizontalIntercept = intercept;
}
/**
* 获取滑动区域补白
*/
public int getSlideRangePadding() {
return mSlideRangePadding;
}
/**
* 设置滑动区域补白
*/
public void setSlideRangePadding(int value) {
if (mSlideRangePadding == value) {
return;
}
mSlideRangePadding = value;
requestLayout();
}
/**
* 获取当前滑动高度,即SlideableView露出的高度
*/
public int getSlideOffsetHeight() {
return (int) (mSlideOffset * mSlideRange + mPanelHeight);
}
/**
* Set sliding enabled flag
*
* @param enabled flag value
*/
public void setDragEnabled(boolean enabled) {
mDragEnabled = enabled;
}
public boolean isDragEnabled() {
return mDragEnabled && mSlideableView != null;
}
/**
* Set the collapsed panel height in pixels
*
* @param val A height in pixels
*/
public void setPanelHeight(int val) {
if (getPanelHeight() == val) {
return;
}
mPanelHeight = val;
requestLayout();
}
/**
* @return The current collapsed panel height
*/
public int getPanelHeight() {
return mPanelHeight;
}
/**
* @return The current minimin fling velocity
*/
public int getMinFlingVelocity() {
return mMinFlingVelocity;
}
/**
* Sets the minimum fling velocity for the panel
*
* @param val the new value
*/
public void setMinFlingVelocity(int val) {
mMinFlingVelocity = val;
}
/**
* Adds a panel slide listener
*/
public void addPanelSlideListener(PanelSlideListener listener) {
mPanelSlideListeners.add(listener);
}
/**
* Removes a panel slide listener
*/
public void removePanelSlideListener(PanelSlideListener listener) {
mPanelSlideListeners.remove(listener);
}
public int getViewDragState() {
return mDragHelper.getViewDragState();
}
public View getSlideableView() {
return mSlideableView;
}
public View getDragView() {
return mDragView;
}
/**
* Set the draggable view portion. Use to null, to allow the whole panel to be draggable
*
* @param dragView A view that will be used to drag the panel.
*/
public void setDragView(View dragView) {
mDragView = dragView;
}
/**
* Set the draggable view portion. Use to null, to allow the whole panel to be draggable
*
* @param dragViewResId The resource ID of the new drag view
*/
public void setDragView(int dragViewResId) {
mDragViewResId = dragViewResId;
setDragView(findViewById(dragViewResId));
}
public List<Integer> getAnchorHeightExtras() {
return new ArrayList<>(mAnchorHeightExtras);
}
public void setAnchorHeightExtras(List<Integer> anchorHeightExtras) {
if (anchorHeightExtras == null || anchorHeightExtras.size() == 0) {
if (mAnchorHeightExtras.size() == 0) {
return;
}
}
//将传入的悬挂高度配置信息进行排序
ArrayList<Integer> sortedExtras = new ArrayList<>(anchorHeightExtras);
if (sortedExtras.size() != 0) {
for (Integer extra : sortedExtras) {
if (extra == null) {
throw new IllegalArgumentException("element can not be null");
}
}
Collections.sort(sortedExtras, new Comparator<Integer>() {
@Override
public int compare(Integer lhs, Integer rhs) {
return lhs - rhs;
}
});
mAnchorHeightExtras.clear();
mAnchorHeightExtras.addAll(sortedExtras);
} else {
mAnchorHeightExtras.clear();
}
requestLayout();
return;
}
public float getExpandPoint() {
return mExpandPoint;
}
public void setExpandPoint(float expandPoint) {
setExpandPoint(expandPoint, true);
}
private void setExpandPoint(float expandPoint, boolean resetMode) {
if (expandPoint > 0 && expandPoint <= 1) {
if (resetMode) {
mExpandHeight = -1;
mExpandPointInParent = 1;
}
mExpandPoint = expandPoint;
}
}
public int getExpandHeight() {
if (mExpandHeight == -1) {
return (int) (mExpandPoint * mSlideRange + mPanelHeight + getPaddingBottom());
}
return mExpandHeight;
}
public void setExpandHeight(int expandHeight) {
if (mExpandHeight == expandHeight) {
return;
}
mExpandHeight = expandHeight;
mExpandPoint = mExpandPointInParent = 1;
requestLayout();
}
/**
* 获取当前正在作用的额外悬挂高度
*/
public int getAnchorHeightExtraNow() {
if (mSlideState == PanelState.ANCHORED) {
if (mAnchorHeightExtraIndex >= 0 && mAnchorHeightExtraIndex < mAnchorHeightExtras.size()) {
return mAnchorHeightExtras.get(mAnchorHeightExtraIndex);
}
}
return -1;
}
public int getAnchorHeight() {
if (mAnchorHeight == -1) {
return (int) (mAnchorPoint * mSlideRange + mPanelHeight + getPaddingBottom());
}
return mAnchorHeight;
}
public void setAnchorHeight(int anchorHeight) {
if (mAnchorHeight == anchorHeight) {
return;
}
mAnchorHeight = anchorHeight;
mAnchorPoint = mAnchorPointInParent = 1;
requestLayout();
}
private void setAnchorPoint(float anchorPoint, boolean resetMode) {
if (anchorPoint > 0 && anchorPoint <= 1) {
if (resetMode) {
mAnchorHeight = -1;
mAnchorPointInParent = 1;
}
mAnchorPoint = anchorPoint;
if (resetMode) {
requestLayout();
}
}
}
/**
* Set an anchor point where the panel can stop during sliding
*
* @param anchorPoint A value between 0 and 1, determining the position of the anchor point
* starting from the top of the layout.
*/
public void setAnchorPoint(float anchorPoint) {
setAnchorPoint(anchorPoint, true);
}
/**
* Gets the currently set anchor point
*
* @return the currently set anchor point
*/
public float getAnchorPoint() {
return mAnchorPoint;
}
void dispatchOnPanelSlide(View panel) {
for (PanelSlideListener l : mPanelSlideListeners) {
l.onPanelSlide(panel, mSlideOffset);
}
}
void dispatchOnPanelStateChanged(View panel, PanelState previousState, PanelState newState) {
for (PanelSlideListener l : mPanelSlideListeners) {
l.onPanelStateChanged(panel, previousState, newState);
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
void updateObscuredViewVisibility() {
if (getChildCount() == 0) {
return;
}
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - getPaddingRight();
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - getPaddingBottom();
final int left;
final int right;
final int top;
final int bottom;
if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
left = mSlideableView.getLeft();
right = mSlideableView.getRight();
top = mSlideableView.getTop();
bottom = mSlideableView.getBottom();
} else {
left = right = top = bottom = 0;
}
View child = getChildAt(0);
final int clampedChildLeft = Math.max(leftBound, child.getLeft());
final int clampedChildTop = Math.max(topBound, child.getTop());
final int clampedChildRight = Math.min(rightBound, child.getRight());
final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
final int vis;
if (clampedChildLeft >= left && clampedChildTop >= top &&
clampedChildRight <= right && clampedChildBottom <= bottom) {
vis = INVISIBLE;
} else {
vis = VISIBLE;
}
child.setVisibility(vis);
}
void setAllChildrenVisible() {
for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == INVISIBLE) {
child.setVisibility(VISIBLE);
}
}
}
private static boolean hasOpaqueBackground(View v) {
final Drawable bg = v.getBackground();
return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFirstLayout = true;
mSlideStateWithoutLayout = true;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mFirstLayout = true;
mSlideStateWithoutLayout = true;
}
/**
* 计算额外悬挂点百分比,相对于SlideRange区域
*
* @param heightSize 测量高度
*/
private void computeAnchorPointExtras(int heightSize) {
int sizeOfHeights = mAnchorHeightExtras.size();
if (mAnchorPointExtras.size() != sizeOfHeights) {
mAnchorPointExtras.clear();
}
float slideRange = heightSize - mPanelHeight - getPaddingTop() - getPaddingBottom() - mSlideRangePadding;
for (int i = 0; i < sizeOfHeights; i++) {
float point = 1.0f;
if (slideRange != 0) {
point = (mAnchorHeightExtras.get(i) - mPanelHeight) / slideRange;
point = (point > 0 && point <= 1) ? point : 1.0f;
}
if (i == mAnchorPointExtras.size()) {
mAnchorPointExtras.add(point);
} else {
mAnchorPointExtras.set(i, point);
}
}
}
/**
* 计算悬挂点百分比,相对于SlideRange区域
*
* @param heightSize 测量高度
*/
private void computeAnchorPoint(int heightSize) {
float slideRange = heightSize - mPanelHeight - getPaddingTop() - getPaddingBottom() - mSlideRangePadding;
if (slideRange != 0) {
float anchorPoint = (mAnchorHeight - mPanelHeight) / slideRange;
if (anchorPoint != mAnchorPoint) {
setAnchorPoint(anchorPoint, false);
}
}
}
/**
* 根据父控件悬挂百分比计算当前的悬挂百分比
*
* @param anchorPointInParent 该百分比包含panelHeight大小
*/
private void computeAnchorPoint(float anchorPointInParent) {
if (mSlideRange == 0) {
return;
}
int heightSize = getMeasuredHeight();
if (heightSize == 0) {
return;
}
float anchorPoint = pointInParentToSlideRange(anchorPointInParent, heightSize);
if (anchorPoint != mAnchorPoint) {
setAnchorPoint(anchorPoint, false);
}
}
private void computeExpandPoint(int heightSize) {
float slideRange = heightSize - mPanelHeight - getPaddingTop() - getPaddingBottom() - mSlideRangePadding;
if (slideRange != 0) {
float expandPoint = (mExpandHeight - mPanelHeight) / slideRange;
if (expandPoint != mExpandPoint) {
setExpandPoint(expandPoint, false);
}
}
}
/**
* 计算展开点百分比
*/
private void computeExpandPoint(float expandPointInParent) {
if (mSlideRange == 0) {
return;
}
int heightSize = getMeasuredHeight();
if (heightSize == 0) {
return;
}
float expandPoint = pointInParentToSlideRange(expandPointInParent, heightSize);
if (expandPoint != mExpandPoint) {
mExpandPoint = expandPoint;
}
}
/**
* 将父组件中Point转换成滑动区域中的Point
*
* @param pointInParent 父组件中的Point
* @param heightSize 当前组件的测量高度
*/
private float pointInParentToSlideRange(float pointInParent, int heightSize) {
int topMarginInParent = 0;
int bottomMarginInParent = 0;
ViewGroup.LayoutParams params = getLayoutParams();
if (params != null && params.height == ViewGroup.LayoutParams.MATCH_PARENT) {
if (params instanceof MarginLayoutParams) {
MarginLayoutParams marginParams = (MarginLayoutParams) params;
topMarginInParent = marginParams.topMargin;
bottomMarginInParent = marginParams.bottomMargin;
}
}
float anchorPointWidthPanel = (pointInParent * (heightSize + topMarginInParent + bottomMarginInParent)
- bottomMarginInParent) / heightSize; //加上PanelHeight的悬挂点
float point = (anchorPointWidthPanel * heightSize - mPanelHeight - getPaddingTop() - getPaddingBottom() - mSlideRangePadding) / mSlideRange;
return point;
}
/**
* 计算滑动区域
*/
private void computeSlideRange() {
mSlideRange = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - mSlideRangePadding;
}
private void onMeasureComplete(int heightSize) {
if (mAnchorPointInParent != 1) {
computeAnchorPoint(mAnchorPointInParent);
} else {
if (mAnchorHeight != -1) {
computeAnchorPoint(heightSize);
}
}
if (mExpandPointInParent != 1) {
computeExpandPoint(mExpandPointInParent);
} else {
if (mExpandHeight != -1) {
computeExpandPoint(heightSize);
}
}
mSlideOffsetSlop = mSlideRange != 0 ? 1.0f / mSlideRange : 0.001f;
if (mAnchorHeightExtras.size() != 0) {
computeAnchorPointExtras(heightSize);
}
}
@SuppressWarnings("Range")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
} else if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
}
final int childCount = getChildCount();
if (childCount != 2) {
throw new IllegalStateException("Sliding up panel layout must have exactly 2 children!");
}
mMainView = getChildAt(0);
mSlideableView = getChildAt(1);
if (mDragView == null) {
setDragView(mSlideableView);
}
// If the sliding panel is not visible, then put the whole view in the hidden state
if (mSlideableView.getVisibility() != VISIBLE) {
mSlideState = PanelState.HIDDEN;
}
int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
// First pass. Measure based on child LayoutParams width/height.
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// We always measure the sliding panel in order to know it's height (needed for show panel)
if (child.getVisibility() == GONE && i == 0) {
continue;
}
int height = layoutHeight;
int width = layoutWidth;
if (child == mMainView) {
if (mSlideState != PanelState.HIDDEN) {
height -= mPanelHeight;
}
width -= lp.leftMargin + lp.rightMargin;
} else if (child == mSlideableView) {
// The slideable view should be aware of its top margin.
// See https://github.com/umano/AndroidSlidingUpPanel/issues/412.
height -= lp.topMargin;
}
int childWidthSpec;
if (lp.width == LayoutParams.WRAP_CONTENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
} else if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
} else {
childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
}
int childHeightSpec;
if (lp.height == LayoutParams.WRAP_CONTENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
} else {
// Modify the height based on the weight.
if (lp.weight > 0 && lp.weight < 1) {
height = (int) (height * lp.weight);
} else if (lp.height != LayoutParams.MATCH_PARENT) {
height = lp.height;
}
childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
child.measure(childWidthSpec, childHeightSpec);
if (child == mSlideableView) {
mSlideRange = mSlideableView.getMeasuredHeight() - mPanelHeight - mSlideRangePadding;
mSlideRange = mSlideRange > 0 ? mSlideRange : 1;
}
}
setMeasuredDimension(widthSize, heightSize);
onMeasureComplete(heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int childCount = getChildCount();
if (mFirstLayout) {
switch (mSlideState) {
case EXPANDED:
mSlideOffset = 1.0f;
break;
case ANCHORED:
if (mAnchorHeightExtraIndex >= 0 && mAnchorHeightExtraIndex < mAnchorPointExtras.size()) {
mSlideOffset = mAnchorPointExtras.get(mAnchorHeightExtraIndex);
} else {
mSlideOffset = mAnchorPoint;
}
break;
case HIDDEN:
int newTop = computePanelTopPosition(0.0f) + mPanelHeight;
mSlideOffset = computeSlideOffset(newTop);
break;
default:
mSlideOffset = 0.f;
break;
}
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Always layout the sliding view on the first layout
if (child.getVisibility() == GONE && (i == 0 || mFirstLayout)) {
continue;
}
final int childHeight = child.getMeasuredHeight();
int childTop = paddingTop;
if (child == mSlideableView) {
childTop = computePanelTopPosition(mSlideOffset);
}
final int childBottom = childTop + childHeight;
final int childLeft = paddingLeft + lp.leftMargin;
final int childRight = childLeft + child.getMeasuredWidth();
child.layout(childLeft, childTop, childRight, childBottom);
}
if (mFirstLayout) {
updateObscuredViewVisibility();
}
mFirstLayout = false;
if (mSlideStateWithoutLayout && mSlideState != PanelState.DRAGGING) {
mSlideStateWithoutLayout = false;
dispatchOnPanelStateChanged(mSlideableView, mLastSlideState, mSlideState);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Recalculate sliding panes and their details
if (h != oldh) {
mFirstLayout = true;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!isEnabled() || !isDragEnabled()) {
mDragHelper.cancel();
return onInterceptTouchEventHorizontal(ev);
}
if (mHookMotionRunning) {
return onInterceptTouchEventHorizontal(ev);
}
if (!mCaptureViewOnDraggingState) {
if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
return onInterceptTouchEventHorizontal(ev);
}
}
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
final float x = ev.getX();
final float y = ev.getY();
final int dragSlop = mDragHelper.getTouchSlop();
switch (action) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = x;
mInitialMotionY = y;
mActionDownOnSlideableView = y > mSlideableView.getTop();
break;
}
case MotionEvent.ACTION_MOVE: {
// Intercept Scrollable view events
if (mScrollAtTop) {
float dy = y - mInitialMotionY;
if (dy >= dragSlop) {
shouldInterceptTouchEvent(ev);
return true;
} else if (dy < -dragSlop) {
if (!isViewUnder(mDragView, (int) mInitialMotionX, (int) mInitialMotionY)) {
if (mScrollableView == null || (mScrollableView != null && mScrollableView.getScrollY() != 0)) {
mScrollAtTop = false;
return onInterceptTouchEventHorizontal(ev);
}
}
}
}
if (!isViewUnder(mDragView, (int) mInitialMotionX, (int) mInitialMotionY)) {
mDragHelper.cancel();
return onInterceptTouchEventHorizontal(ev);
}
break;
}
}
return shouldInterceptTouchEvent(ev) || onInterceptTouchEventHorizontal(ev);
}
private boolean shouldInterceptTouchEvent(MotionEvent ev) {
try {
return mDragHelper.shouldInterceptTouchEvent(ev);
} catch (Exception e) {
return false;
}
}
/**
* 拦截横向滑动
*/
private boolean onInterceptTouchEventHorizontal(MotionEvent ev) {
if (mActionDownOnSlideableView && mDragHorizontalIntercept) {
if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
return Math.abs(ev.getX() - mInitialMotionX) > mInterceptTouchSlop;
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!isEnabled() || !isDragEnabled()) {
return super.onTouchEvent(ev);
}
try {
mDragHelper.processTouchEvent(ev);
return mActionDownOnSlideableView;
} catch (Exception ex) {
return mActionDownOnSlideableView;
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
float y = ev.getY();
if (action == MotionEvent.ACTION_DOWN) {
mHookMotionY = y;
mHookMotionRunning = false;
if (isPointOnPanelTransparentArea((int) ev.getX(), (int) ev.getY())) {
return false; //透明区域屏蔽事件,下透到其他组件
}
} else if (action == MotionEvent.ACTION_MOVE) {
float dy = y - mHookMotionY;
if (dy > 0) {
if (mHookScrollableView != null && mSlideOffset >= 1.0f) { //滚动视图向下拉动
if (mHookScrollableView.getScrollY() > 0) {
mHookMotionRunning = true;
return super.dispatchTouchEvent(ev);
}
}
if (mHookMotionRunning) { //滚动视图向下拉动到顶部
this.dispatchTouchEventCancel(ev);
mHookMotionMoveUpHandled = false;
mHookMotionRunning = false;
return super.dispatchTouchEvent(ev);
}
if (mSlideOffset < 1.0f) {
mHookMotionMoveUpHandled = false;
}
} else if (dy < 0) {
if (mHookScrollableView != null && mSlideOffset >= 1.0f) { //滚动视图向上拉动
if (mHookScrollableView.getScrollY() >= 0) {
if (!mHookMotionRunning) { //第一次捕捉到
if (!mHookMotionMoveUpHandled) {
this.dispatchTouchEventCancel(ev);
mHookMotionMoveUpHandled = true;
}
}
mHookMotionRunning = true;
return super.dispatchTouchEvent(ev);
}
}
if (mSlideOffset < 1.0f) {
mHookMotionMoveUpHandled = false;
}
}
mHookMotionY = y;
} else if (action == MotionEvent.ACTION_UP) {
if (mHookMotionRunning) {
mDragHelper.setDragState(ViewDragHelper.STATE_IDLE);
}
}
return super.dispatchTouchEvent(ev);
}
/**
* 中止事件,新派发DOWN事件
*/
protected void dispatchTouchEventCancel(MotionEvent ev) {
MotionEvent cancel = MotionEvent.obtain(ev);
cancel.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(cancel);
cancel.recycle();
ev.setAction(MotionEvent.ACTION_DOWN);
}
private boolean isViewUnder(View view, int x, int y) {
if (view == null) return false;
int[] viewLocation = new int[2];
view.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
}
/**
* 判断点是否在Panel的透明区域里
*/
private boolean isPointOnPanelTransparentArea(int x, int y) {
if (mPanelTransparentTop == 0) {
return false;
}
if (mSlideableView == null) {
return false;
}
if (isViewUnder(mPanelHoverView, x, y)) {
return false;
}
int[] viewLocation = new int[2];
mSlideableView.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] && screenX < viewLocation[0] + mSlideableView.getWidth() &&
screenY >= viewLocation[1] && screenY < viewLocation[1] + mPanelTransparentTop;
}
/*
* Computes the top position of the panel based on the slide offset.
*/
private int computePanelTopPosition(float slideOffset) {
int slidingViewHeight = mSlideableView != null ? mSlideableView.getMeasuredHeight() : 0;
int slidePixelOffset = (int) (slideOffset * mSlideRange);
// Compute the top of the panel if its collapsed
return getMeasuredHeight() - getPaddingBottom() - mPanelHeight - slidePixelOffset;
}
/*
* Computes the slide offset based on the top position of the panel
*/
private float computeSlideOffset(int topPosition) {
// Compute the panel top position if the panel is collapsed (offset 0)
final int topBoundCollapsed = computePanelTopPosition(0);
// Determine the new slide offset based on the collapsed top position and the new required
// top position
return (float) (topBoundCollapsed - topPosition) / mSlideRange;
}
public PanelState getLastPanelState() {
return mLastNotDraggingSlideState;
}
/**
* Returns the current state of the panel as an enum.
*
* @return the current panel state
*/
public PanelState getPanelState() {
return mSlideState;
}
/**
* Change panel state to the given state with
*
* @param state - new panel state
*/
public void setPanelState(PanelState state) {
setPanelState(state, -1);
}
private void setPanelStateInternal(PanelState state) {
if (mSlideState == state && mSlideState != PanelState.ANCHORED) return;
mLastSlideState = mSlideState;
PanelState oldState = mSlideState;
mSlideState = state;
beforePanelStateChangedDispatched();
if (mSlideStateWithoutLayout) {
return;
}
dispatchOnPanelStateChanged(this, oldState, state);
}
private void beforePanelStateChangedDispatched() {
mLastNotHiddenSlideState = mSlideState != PanelState.HIDDEN ? mSlideState : mLastNotHiddenSlideState;
if (mSlideState != PanelState.ANCHORED) {
return;
}
if (mAnchorHeightExtraIndex >= 0 && mAnchorHeightExtraIndex < mAnchorHeightExtras.size()) {
if (mAnchorPointExtras.get(mAnchorHeightExtraIndex) != getSlideOffsetHeight()) {
mAnchorHeightExtraIndex = -1;
}
} else {
mAnchorHeightExtraIndex = -1;
}
}
/**
* 扩展设置状态
*
* @param state 状态
* @param anchorHeightExtra 默认值为-1,该值需要提前预设
*/
public void setPanelState(PanelState state, int anchorHeightExtra) {
if (state == null || state == PanelState.DRAGGING) {
return;
}
if (!isEnabled() || (!mFirstLayout && mSlideableView == null)) {
return;
}
if (state == mSlideState && state != PanelState.ANCHORED && anchorHeightExtra != -1) {
return;
}
if (mSlideState == PanelState.DRAGGING && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
return;
}
if (state == PanelState.ANCHORED) {
if (mSlideState == PanelState.ANCHORED) {
if (anchorHeightExtra != -1 && anchorHeightExtra == getAnchorHeightExtraNow()) {
return;
}
}
mAnchorHeightExtraIndex = mAnchorHeightExtras.indexOf(anchorHeightExtra);
}
if (mFirstLayout) {
setPanelStateInternal(state);
} else {
if (mSlideState == PanelState.HIDDEN) {
mSlideableView.setVisibility(View.VISIBLE);
requestLayout();
}
switch (state) {
case ANCHORED:
float point = 1.0f;
if (mAnchorHeightExtraIndex >= 0 && mAnchorHeightExtraIndex < mAnchorPointExtras.size()) {
point = mAnchorPointExtras.get(mAnchorHeightExtraIndex);
} else {
point = mAnchorPoint;
}
smoothSlideTo(point, 0);
break;
case COLLAPSED:
smoothSlideTo(0, 0);
break;
case EXPANDED:
smoothSlideTo(1.0f, 0);
break;
case HIDDEN:
int newTop = computePanelTopPosition(0.0f) + mPanelHeight;
smoothSlideTo(computeSlideOffset(newTop), 0);
break;
}
}
}
public void setPanelState(PanelState state, int anchorHeightExtra, boolean animate) {
if (animate) {
setPanelState(state, anchorHeightExtra);
return;
}
mFirstLayout = true;
mSlideStateWithoutLayout = true;
if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
mDragHelper.abort();
ViewCompat.postInvalidateOnAnimation(this);
}
setPanelState(state, anchorHeightExtra);
requestLayout();
}
public void setPanelState(PanelState state, boolean animate) {
setPanelState(state, -1, animate);
}
public void showPanel() {
setPanelState(mLastNotHiddenSlideState);
}
public void hidePanel() {
setPanelState(PanelState.HIDDEN);
}
private void onPanelDragged(int newTop) {
if (mSlideState != PanelState.DRAGGING) {
mLastNotDraggingSlideState = mSlideState;
}
setPanelStateInternal(PanelState.DRAGGING);
// Recompute the slide offset based on the new top position
mSlideOffset = computeSlideOffset(newTop);
// Dispatch the slide event
dispatchOnPanelSlide(mSlideableView);
}
/**
* Smoothly animate mDraggingPane to the target X position within its range.
*
* @param slideOffset position to animate to
* @param velocity initial velocity in case of fling, or 0.
*/
boolean smoothSlideTo(float slideOffset, int velocity) {
if (!isEnabled() || mSlideableView == null) {
// Nothing to do.
return false;
}
int panelTop = computePanelTopPosition(slideOffset);
if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) {
setAllChildrenVisible();
ViewCompat.postInvalidateOnAnimation(this);
return true;
}
return false;
}
@Override
public void computeScroll() {
if (mDragHelper != null && mDragHelper.continueSettling(true)) {
if (!isEnabled()) {
mDragHelper.abort();
return;
}
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams
? new LayoutParams((MarginLayoutParams) p)
: new LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams && super.checkLayoutParams(p);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
if (mSlideState != PanelState.DRAGGING) {
ss.mSlideState = mSlideState;
} else {
ss.mSlideState = mLastNotDraggingSlideState;
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mSlideState = ss.mSlideState != null ? ss.mSlideState : DEFAULT_SLIDE_STATE;
}
private class DragHelperCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
if (!mCaptureViewOnDraggingState && mSlideState == PanelState.DRAGGING) {
return false;
}
return child == mSlideableView;
}
@Override
public void onViewDragStateChanged(int state) {
if (mSlideStateWithoutLayout) {
return;
}
if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
mSlideOffset = computeSlideOffset(mSlideableView.getTop());
if (mSlideOffset == 1) {
updateObscuredViewVisibility();
setPanelStateInternal(PanelState.EXPANDED);
} else if (mSlideOffset == 0) {
setPanelStateInternal(PanelState.COLLAPSED);
} else if (mSlideOffset < 0) {
mSlideableView.setVisibility(View.GONE);
setPanelStateInternal(PanelState.HIDDEN);
} else {
if (mAnchorPoint != 1 && Math.abs(mSlideOffset - mAnchorPoint) <= mSlideOffsetSlop) {
mAnchorHeightExtraIndex = -1;
mSlideOffset = mAnchorPoint;
updateObscuredViewVisibility();
setPanelStateInternal(PanelState.ANCHORED);
} else {
for (int i = 0, size = mAnchorPointExtras.size(); i < size; i++) {
float anchorPointExtra = mAnchorPointExtras.get(i);
if (Math.abs(anchorPointExtra - mSlideOffset) <= mSlideOffsetSlop) {
mAnchorHeightExtraIndex = i;
mSlideOffset = anchorPointExtra;
updateObscuredViewVisibility();
setPanelStateInternal(PanelState.ANCHORED);
break;
}
}
}
}
if (mSlideState == PanelState.DRAGGING) { //没有完成滑动判断,出错处理
float anchorPointExtraClosest = 1.0f;
if ((anchorPointExtraClosest = computeAnchorPointExtraClosest(mSlideOffset, 0)) != 1.0f) {
smoothSlideTo(anchorPointExtraClosest, 0);
} else {
float deltaExpand = Math.abs(mSlideOffset - 1); //距离EXPAND状态的增量值
float deltaAnchor = mAnchorPoint != 1 ? Math.abs(mSlideOffset - mAnchorPoint) : 1; //距离ANCHOR状态的增量值
float deltaCollapse = Math.abs(mSlideOffset); //距离COLLAPSED状态的增量值
if (deltaExpand < deltaAnchor && deltaExpand < deltaCollapse) {
smoothSlideTo(1, 0);
} else if (mAnchorPoint != 1 && deltaAnchor < deltaExpand && deltaAnchor < deltaCollapse) {
smoothSlideTo(mAnchorPoint, 0);
} else if (deltaCollapse < deltaExpand && deltaCollapse < deltaAnchor) {
smoothSlideTo(0, 0);
} else {
smoothSlideTo(0, 0);
}
}
}
}
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
setAllChildrenVisible();
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
onPanelDragged(top);
invalidate();
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int target = 0;
// direction is always positive if we are sliding in the expanded direction
float direction = -yvel;
float anchorPointExtraClosest = computeAnchorPointExtraClosest(mSlideOffset, direction);
if (anchorPointExtraClosest != 1.0f) {
target = computePanelTopPosition(anchorPointExtraClosest);
} else if (direction > 0 && mSlideOffset <= mAnchorPoint) {
// swipe up -> expand and stop at anchor point
target = computePanelTopPosition(mAnchorPoint);
} else if (direction > 0 && mSlideOffset > mAnchorPoint) {
// swipe up past anchor -> expand
target = computePanelTopPosition(1.0f);
} else if (direction < 0 && mSlideOffset >= mAnchorPoint) {
// swipe down -> collapse and stop at anchor point
target = computePanelTopPosition(mAnchorPoint);
} else if (direction < 0 && mSlideOffset < mAnchorPoint) {
// swipe down past anchor -> collapse
target = computePanelTopPosition(0.0f);
} else if (mSlideOffset >= mExpandPoint) {
target = computePanelTopPosition(1.0f);
} else if (mExpandPoint == 1 && mSlideOffset >= (1.f + mAnchorPoint) / 2) {
// zero velocity, and far enough from anchor point => expand to the top
target = computePanelTopPosition(1.0f);
} else if (mSlideOffset >= mAnchorPoint / 2) {
// zero velocity, and close enough to anchor point => go to anchor
target = computePanelTopPosition(mAnchorPoint);
} else {
// settle at the bottom
target = computePanelTopPosition(0.0f);
}
mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), target);
invalidate();
}
private float computeAnchorPointExtraClosest(float slideOffset, float direction) {
float anchorPointExtra = 1.0f;
int pointExtraSize = mAnchorPointExtras.size();
if (pointExtraSize == 0) {
return anchorPointExtra;
}
if (direction > 0) { //swipe up
for (int i = 0; i < pointExtraSize; i++) {
float anchorPoint = mAnchorPointExtras.get(i);
if (anchorPoint > slideOffset) {
anchorPointExtra = anchorPoint;
break;
}
}
} else if (direction < 0) { //swipe down
for (int i = 0; i < pointExtraSize; i++) {
float anchorPoint = mAnchorPointExtras.get(i);
if (anchorPoint < slideOffset) {
anchorPointExtra = anchorPoint;
break;
}
}
} else {
ArrayList<Float> deltaExtras = new ArrayList<>();
for (int i = 0; i < pointExtraSize; i++) {
deltaExtras.add(Math.abs(mAnchorPointExtras.get(i) - slideOffset));
}
deltaExtras.add(Math.abs(slideOffset)); //COLLAPSED
deltaExtras.add(Math.abs(1 - slideOffset)); //EXPANDED
deltaExtras.add(Math.abs(mExpandPoint - slideOffset)); //展开点排除
deltaExtras.add(Math.abs(mAnchorPoint - slideOffset)); //ANCHORED 没有设置则该点默认为1
int minIndex = -1;
float minValue = 1.0f;
for (int i = 0, size = deltaExtras.size(); i < size; i++) {
float delta = deltaExtras.get(i);
if (minValue > delta) {
minValue = delta;
minIndex = i;
}
}
if (minIndex >= 0 && minIndex < pointExtraSize) {
anchorPointExtra = mAnchorPointExtras.get(minIndex);
}
}
return anchorPointExtra;
}
@Override
public int getViewVerticalDragRange(View child) {
return mSlideRange;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
top += mDragLeverage == 0 ? 0 : Math.round(mDragLeverage * dy);
final int collapsedTop = computePanelTopPosition(0.f);
final int expandedTop = computePanelTopPosition(1.0f);
return Math.min(Math.max(top, expandedTop), collapsedTop);
}
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
private static final int[] ATTRS = new int[]{
android.R.attr.layout_weight
};
public float weight = 0;
public LayoutParams() {
super(MATCH_PARENT, MATCH_PARENT);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(int width, int height, float weight) {
super(width, height);
this.weight = weight;
}
public LayoutParams(android.view.ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(LayoutParams source) {
super(source);
}
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
final TypedArray ta = c.obtainStyledAttributes(attrs, ATTRS);
if (ta != null) {
this.weight = ta.getFloat(0, 0);
}
ta.recycle();
}
}
static class SavedState extends BaseSavedState {
PanelState mSlideState;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
String panelStateString = in.readString();
try {
mSlideState = panelStateString != null ? Enum.valueOf(PanelState.class, panelStateString)
: PanelState.COLLAPSED;
} catch (IllegalArgumentException e) {
mSlideState = PanelState.COLLAPSED;
}
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(mSlideState == null ? null : mSlideState.toString());
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
static class ScrollableVew implements IScrollableView {
private View mView;
public ScrollableVew(View view) {
mView = view;
}
@Override
public int getScrollX() {
return mView.getScrollX();
}
@Override
public int getScrollY() {
return mView.getScrollY();
}
}
}