Java tutorial
/* * Copyright 2015 chenupt * * 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 * imitations under the License. */ package com.gu.baselibrary.view.dragtoplayout; import android.content.Context; import android.content.res.TypedArray; import android.os.Handler; import android.os.Parcelable; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import com.gu.baselibrary.R; import com.gu.baselibrary.utils.LogUtils; import com.gu.baselibrary.utils.ScreenUtils; /** * Created by chenupt@gmail.com on 2015/1/18. * Description : Drag down to show a menu panel on the top. */ public class DragTopLayout extends FrameLayout { private ViewDragHelper dragHelper; private int dragRange;// private View dragContentView;// private View topView;// private int contentTop;//top private int topViewHeight; private float ratio; private boolean isRefreshing; private boolean shouldIntercept = true; private int topDistance; private PanelListener panelListener; private float refreshRatio = 1.5f; private boolean overDrag = false;//?? private int collapseOffset;//??? private int topViewId = -1; private int dragContentViewId = -1; private boolean captureTop = false;//??viewdraghelper // Used for scrolling private boolean dispatchingChildrenDownFaked = false; private boolean dispatchingChildrenContentView = false; private float dispatchingChildrenStartedAtY = Float.MAX_VALUE; private PanelState panelState = PanelState.EXPANDED; public enum PanelState { COLLAPSED(0), // EXPANDED(1), // SLIDING(2);// private int asInt; PanelState(int i) { this.asInt = i; } static PanelState fromInt(int i) { switch (i) { case 0: return COLLAPSED; case 2: return SLIDING; default: case 1: return EXPANDED; } } public int toInt() { return asInt; } } public DragTopLayout(Context context) { this(context, null); } public DragTopLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragTopLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(attrs); } private void init(AttributeSet attrs) { dragHelper = ViewDragHelper.create(this, 1.0f, callback); // init from attrs TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DragTopLayout); //setCollapseOffset(a.getDimensionPixelSize(R.styleable.DragTopLayout_dtlCollapseOffset, collapseOffset)); overDrag = a.getBoolean(R.styleable.DragTopLayout_dtlOverDrag, overDrag); dragContentViewId = a.getResourceId(R.styleable.DragTopLayout_dtlDragContentView, -1); topViewId = a.getResourceId(R.styleable.DragTopLayout_dtlTopView, -1); initOpen(a.getBoolean(R.styleable.DragTopLayout_dtlOpen, true)); captureTop = a.getBoolean(R.styleable.DragTopLayout_dtlCaptureTop, false); topDistance = a.getInt(R.styleable.DragTopLayout_topDistance, 250); a.recycle(); } /** * ? * * @param initOpen */ private void initOpen(boolean initOpen) { if (initOpen) { panelState = PanelState.EXPANDED; } else { panelState = PanelState.COLLAPSED; } } @Override public void onFinishInflate() { super.onFinishInflate(); //?contenttop contentTop = ScreenUtils.dp2px(getContext(), topDistance); if (getChildCount() < 2) { throw new RuntimeException("Content view must contains two child views at least."); } if (topViewId != -1 && dragContentViewId == -1) { throw new IllegalArgumentException( "You have set \"dtlTopView\" but not \"dtlDragContentView\". Both are required!"); } if (dragContentViewId != -1 && topViewId == -1) { throw new IllegalArgumentException( "You have set \"dtlDragContentView\" but not \"dtlTopView\". Both are required!"); } if (dragContentViewId != -1 && topViewId != -1) { bindId(this); } else { topView = getChildAt(0); dragContentView = getChildAt(1); } } private void bindId(View view) { topView = view.findViewById(topViewId); dragContentView = view.findViewById(dragContentViewId); if (topView == null) { throw new IllegalArgumentException("\"dtlTopView\" with id = \"@id/" + getResources().getResourceEntryName(topViewId) + "\" has NOT been found. Is a child with that id in this " + getClass().getSimpleName() + "?"); } if (dragContentView == null) { throw new IllegalArgumentException("\"dtlDragContentView\" with id = \"@id/" + getResources().getResourceEntryName(dragContentViewId) + "\" has NOT been found. Is a child with that id in this " + getClass().getSimpleName() + "?"); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); dragRange = getHeight(); // In case of resetting the content top to target position before sliding. resetTopViewHeight(); resetContentHeight(); topView.layout(left, Math.min(topView.getPaddingTop(), contentTop - topViewHeight) >= 0 ? Math.min(topView.getPaddingTop(), contentTop - topViewHeight) : 0, right, contentTop + ScreenUtils.dp2px(getContext(), 50)); dragContentView.layout(left, contentTop, right, contentTop + dragContentView.getHeight()); } /** * ? */ private void resetTopViewHeight() { int newTopHeight = topView.getHeight(); // Top layout is changed if (topViewHeight != newTopHeight) { if (panelState == PanelState.EXPANDED) { //contenttop //contentTop = newTopHeight; handleSlide(contentTop); } else if (panelState == PanelState.COLLAPSED) { // update the drag content top when it is collapsed. contentTop = collapseOffset; } topViewHeight = newTopHeight; } } /** * ? */ private void resetContentHeight() { if (dragContentView != null && dragContentView.getHeight() != 0) { ViewGroup.LayoutParams layoutParams = dragContentView.getLayoutParams(); layoutParams.height = getHeight() - collapseOffset - ScreenUtils.dp2px(getContext(), 54); dragContentView.setLayoutParams(layoutParams); } } private void handleSlide(final int top) { new Handler().post(new Runnable() { @Override public void run() { dragHelper.smoothSlideViewTo(dragContentView, getPaddingLeft(), top); postInvalidate(); } }); } private void resetDragContent(boolean anim, int top) { contentTop = top; if (anim) { dragHelper.smoothSlideViewTo(dragContentView, getPaddingLeft(), contentTop); postInvalidate(); } else { requestLayout(); } } private void calculateRatio(float top) { ratio = (top - collapseOffset - ScreenUtils.dp2px(getContext(), 54)) / (ScreenUtils.dp2px(getContext(), 196) - collapseOffset); if (dispatchingChildrenContentView) { resetDispatchingContentView(); } if (panelListener != null) { // Calculate the ratio while dragging. panelListener.onSliding(ratio); if (ratio > refreshRatio && !isRefreshing) { isRefreshing = true; panelListener.onRefresh(); } } } /** * dragtoplayout??contenttop? */ private void updatePanelState() { if (contentTop <= getPaddingTop() + collapseOffset + ScreenUtils.dp2px(getContext(), 60)) { panelState = PanelState.COLLAPSED; } else if (contentTop >= ScreenUtils.dp2px(getContext(), topDistance)) { panelState = PanelState.EXPANDED; } else { panelState = PanelState.SLIDING; } if (panelListener != null) { panelListener.onPanelStateChanged(panelState); } } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState state = new SavedState(superState); state.panelState = panelState.toInt(); return state; } @Override protected void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { // FIX #10 super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE); return; } SavedState s = (SavedState) state; super.onRestoreInstanceState(s.getSuperState()); this.panelState = PanelState.fromInt(s.panelState); if (panelState == PanelState.COLLAPSED) { closeTopView(false); } else { openTopView(false); } } private boolean isDrag = true; public void setDrag(boolean isDrag) { this.isDrag = isDrag; if (isDrag) { //Bug,isDragfalse?truemDragHelpermCallBack //?????mDragHelper??? dragHelper.abort(); } else { contentTop = ScreenUtils.dp2px(getContext(), 54); } } private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { if (child == topView && captureTop) { dragHelper.captureChildView(dragContentView, pointerId); return false; } return child == dragContentView; } /** * ? * @param changedView * @param left * @param top * @param dx * @param dy */ @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { super.onViewPositionChanged(changedView, left, top, dx, dy); //?top54dp?? if (isDrag && top > ScreenUtils.dp2px(getContext(), 54)) { contentTop = top; requestLayout(); calculateRatio(contentTop); updatePanelState(); } } @Override public int getViewVerticalDragRange(View child) { return dragRange; } @Override public int clampViewPositionVertical(View child, int top, int dy) { //?????? if (isDrag) { if (top > ScreenUtils.dp2px(getContext(), 54)) { if (overDrag) { // Drag over the top view height. return Math.max(top, getPaddingTop() + collapseOffset); } else { // return Math.min(ScreenUtils.dp2px(getContext(), topDistance), Math.max(top, getPaddingTop() + collapseOffset)); } } else { return ScreenUtils.dp2px(getContext(), 54); } } else { contentTop = ScreenUtils.dp2px(getContext(), 54); return ScreenUtils.dp2px(getContext(), 54); } } /** * ? * @param releasedChild * @param xvel * @param yvel */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); if (isDrag && contentTop > ScreenUtils.dp2px(getContext(), 54)) { // yvel > 0 Fling down || yvel < 0 Fling up int top; if (yvel > 0 || contentTop > ScreenUtils.dp2px(getContext(), topDistance)) { if (panelListener != null) { panelListener.onPanelStateChanged(PanelState.EXPANDED); } top = ScreenUtils.dp2px(getContext(), topDistance) + getPaddingTop(); } else { if (panelListener != null) { panelListener.onPanelStateChanged(PanelState.COLLAPSED); } //??? top = getPaddingTop() + collapseOffset + ScreenUtils.dp2px(getContext(), 54); } dragHelper.settleCapturedViewAt(releasedChild.getLeft(), top); postInvalidate(); } } @Override public void onViewDragStateChanged(int state) { super.onViewDragStateChanged(state); } }; @Override public void computeScroll() { if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { try { boolean intercept = shouldIntercept && dragHelper.shouldInterceptTouchEvent(ev); return intercept; } catch (NullPointerException e) { e.printStackTrace(); } return false; } @Override public boolean onTouchEvent(MotionEvent event) { final int action = MotionEventCompat.getActionMasked(event); if (!dispatchingChildrenContentView) { try { // There seems to be a bug on certain devices: "pointerindex out of range" in viewdraghelper // https://github.com/umano/AndroidSlidingUpPanel/issues/351 dragHelper.processTouchEvent(event); } catch (Exception e) { e.printStackTrace(); } } if (action == MotionEvent.ACTION_MOVE && ratio == 0.0f) { dispatchingChildrenContentView = true; if (!dispatchingChildrenDownFaked) { dispatchingChildrenStartedAtY = event.getY(); event.setAction(MotionEvent.ACTION_DOWN); dispatchingChildrenDownFaked = true; } dragContentView.dispatchTouchEvent(event); } if (dispatchingChildrenContentView && dispatchingChildrenStartedAtY < event.getY()) { resetDispatchingContentView(); } if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { resetDispatchingContentView(); dragContentView.dispatchTouchEvent(event); } return true; } private void resetDispatchingContentView() { dispatchingChildrenDownFaked = false; dispatchingChildrenContentView = false; dispatchingChildrenStartedAtY = Float.MAX_VALUE; } public void openTopView(boolean anim) { // Before created if (dragContentView.getHeight() == 0) { panelState = PanelState.EXPANDED; if (panelListener != null) { panelListener.onSliding(1.0f); } } else { resetDragContent(anim, topViewHeight); } } public void closeTopView(boolean anim) { if (dragContentView.getHeight() == 0) { panelState = PanelState.COLLAPSED; if (panelListener != null) { panelListener.onSliding(0.0f); } } else { resetDragContent(anim, getPaddingTop() + collapseOffset); } } public interface PanelListener { /** * Called while the panel state is changed. */ void onPanelStateChanged(PanelState panelState); /** * Called while dragging. * ratio >= 0. */ void onSliding(float ratio); /** * Called while the ratio over refreshRatio. */ void onRefresh(); } public void setPanelListener(PanelListener listener) { this.panelListener = listener; } /** * Save the instance state */ private static class SavedState extends BaseSavedState { int panelState; SavedState(Parcelable superState) { super(superState); } } }