下拉重新整理,上拉載入更多的SwipeRefreshLayout(可自定義動畫)
阿新 • • 發佈:2019-01-03
為啥重複搞,搞得還沒人家好,因為除了需求,還有理解。我是這麼認為的。
因為懶所以寫出來留作自用,以後就是修修改改了。
打造自己的“下拉重新整理,上拉載入更多,自定義動畫及佈局”控制元件
(拷貝SwipeRefreshLayout原始碼進行修改)
不鬥圖的碼農你見過嗎?
醜醜爆了,原諒我的審美和能力。。。
新寫了一個自定義載入動畫
設計思路:
也就是一個大的佈局套著一個可滾動的子viewGroup
內外兩個都可以滑動的View,那麼主要問題就是解決滑動衝突了。
當然要重現父類View的攔截器部分:
onInterceptTouchEvent事件:
如果(子view可以向上滾動&&也可以向下滾動){ //TODO直接放行,觸控事件交給子view處理 }否則{ //TODO呼叫父類觸控事件OntouchEvent(由onTouchEvnent決定是否攔截) }
TouchEvent事件:
能觸發onTouchenvet事件有四種情況:
1.已經滑到頂部了,還要繼續下滑動(觸發頂部重新整理)
2.已經滑到頂部了,這時候上劃則滾動子view
3.已經滑到底部了,還要繼續滑動(觸發底部重新整理)
4.已經滑到底部了,這時候下滑則滾動子view
由上四種情況分別處理:
1,3情況 觸發父view的重新整理載入動畫
2,4情況 則不消費onTouch事件,並把結果返還給攔截器onInterceptTouchEvent處理把事件交給子view
上程式碼:
package com.huan.squirrel.pushcardlayout.pushcardlayout; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.NestedScrollingChild; import android.support.v4.view.NestedScrollingChildHelper; import android.support.v4.view.NestedScrollingParent; import android.support.v4.view.NestedScrollingParentHelper; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ListViewCompat; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.widget.AbsListView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; /** * @author:Squirrel桓 * @time:2018/8/28 */ public class PushCardLayout extends ViewGroup implements NestedScrollingParent, NestedScrollingChild { private int mTouchSlop; private final NestedScrollingParentHelper mNestedScrollingParentHelper; private final NestedScrollingChildHelper mNestedScrollingChildHelper; //滾動內容區 private View contentLayout; //底部佈局,頂部佈局 private LinearLayout bottomLayout, topLayout; private View topLayoutView; private View bottomLayoutView; //資料載入監聽 private PushCardDatalistener dataListener; //動畫載入監聽 private PushCardAnimationListener animationListener; public void setDataListener(PushCardDatalistener dataListener) { this.dataListener = dataListener; } public void setAnimationListener(PushCardAnimationListener animationListener) { this.animationListener = animationListener; } public View getTopLayoutView() { return topLayoutView; } /** * 設定頂部view * * @param topLayoutView */ public void setTopLayoutView(View topLayoutView) { topLayout.removeAllViews(); topLayout.addView(topLayoutView); this.topLayoutView = topLayoutView; } public View getBottomLayoutView() { return bottomLayoutView; } /** * 設定底部view * * @param bottomLayoutView */ public void setBottomLayoutView(View bottomLayoutView) { bottomLayout.removeAllViews(); bottomLayout.addView(bottomLayoutView); this.bottomLayoutView = bottomLayoutView; } public PushCardLayout(Context context, AttributeSet attrs) { super(context, attrs); mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); setNestedScrollingEnabled(true); initView(context, attrs); } private int mMediumAnimationDuration;//動畫時長 private final DecelerateInterpolator mDecelerateInterpolator; private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; private static final int[] LAYOUT_ATTRS = new int[]{ android.R.attr.enabled }; private void initView(Context context, AttributeSet attrs) { final DisplayMetrics metrics = getResources().getDisplayMetrics();//解析度 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();//距離,表示滑動的時候,手的移動要大於這個距離才開始移動控制元件。 mMediumAnimationDuration = getResources().getInteger( android.R.integer.config_mediumAnimTime);//動畫時長 setWillNotDraw(false);//ViewGroup預設情況下,出於效能考慮,會被設定成WILL_NOT_DROW,這樣,ondraw就不會被執行了。 //呼叫setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。就可以重寫ondraw() creatTopLayout(context); creatBottomLayout(context); /****** (START) 測試新增 可刪除 ***********/ /*ImageView imageView = new ImageView(getContext()); imageView.setImageResource(R.mipmap.ic_launcher_round); setTopLayoutView(imageView); ImageView imageView2 = new ImageView(getContext()); imageView2.setImageResource(R.mipmap.ic_launcher_round); setBottomLayoutView(imageView2);*/ /****** (END) 測試新增 可刪除 ***********/ setChildrenDrawingOrderEnabled(true);//設定子view按照順序繪製 mSpinnerOffsetEnd = bottomLayoutHeight / 2; mTotalDragDistance = mSpinnerOffsetEnd; final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); setEnabled(a.getBoolean(0, true)); a.recycle(); } /** * 初始化頂部佈局 * TODO 暫時用底部佈局高度作為預設高度,動態頂部,底部高度 ,用到時候在處理吧 * * @param context */ private int bottomLayoutHeight = (int)(100*getResources().getDisplayMetrics().density); public int getBottomLayoutHeight() { return bottomLayoutHeight; } private void creatBottomLayout(Context context) { bottomLayout = new LinearLayout(context); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, bottomLayoutHeight); layoutParams.gravity = Gravity.CENTER; bottomLayout.setLayoutParams(layoutParams); bottomLayout.setGravity(Gravity.CENTER); //bottomLayout.setGravity(Gravity.BOTTOM); addView(bottomLayout); } /** * 初始化底部佈局 * * @param context */ private int topLayoutHeight = (int)(100*getResources().getDisplayMetrics().density); private void creatTopLayout(Context context) { topLayout = new LinearLayout(context); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, topLayoutHeight); layoutParams.gravity = Gravity.CENTER; topLayout.setLayoutParams(layoutParams); topLayout.setGravity(Gravity.CENTER); addView(topLayout); } private int topLayoutOffsetTop = 0;//header距離頂部的距離 private int bottomLayoutOffsetTop = 0;//footer距離頂部的距離 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int width = getMeasuredWidth(); final int height = getMeasuredHeight(); if (getChildCount() == 0) { return; } if (contentLayout == null) { ensureTarget(); } if (contentLayout == null) { return; } final View child = contentLayout; final int childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); final int childWidth = width - getPaddingLeft() - getPaddingRight(); final int childHeight = height - getPaddingTop() - getPaddingBottom(); child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); final DisplayMetrics metrics = getResources().getDisplayMetrics();//解析度 int topLayoutWidth = topLayout.getMeasuredWidth(); topLayoutHeight = topLayout.getMeasuredHeight(); topLayoutOffsetTop = -topLayoutHeight; topLayout.layout((width / 2 - topLayoutWidth / 2), -topLayoutHeight, (width / 2 + topLayoutWidth / 2), 0); int bottomLayoutWidth = bottomLayout.getMeasuredWidth(); bottomLayoutHeight = bottomLayout.getMeasuredHeight(); bottomLayoutOffsetTop = height; bottomLayout.layout((width / 2 - bottomLayoutWidth / 2), height, (width / 2 + bottomLayoutWidth / 2), height + bottomLayoutHeight); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (contentLayout == null) { ensureTarget(); } if (contentLayout == null) { return; } contentLayout.measure(MeasureSpec.makeMeasureSpec( getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); topLayout.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) topLayoutHeight, MeasureSpec.EXACTLY)); bottomLayout.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) bottomLayoutHeight, MeasureSpec.EXACTLY)); } private void ensureTarget() { if (contentLayout == null) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (!child.equals(topLayout) && !child.equals(bottomLayout)) { contentLayout = child; break; } } } } @Override public void requestDisallowInterceptTouchEvent(boolean b) { // if this is a List < L or another view that doesn't support nested // scrolling, ignore this request so that the vertical scroll event // isn't stolen if ((android.os.Build.VERSION.SDK_INT < 21 && contentLayout instanceof AbsListView) || (contentLayout != null && !ViewCompat.isNestedScrollingEnabled(contentLayout))) { // Nope. } else { super.requestDisallowInterceptTouchEvent(b); } } public boolean onInterceptTouchEvent(MotionEvent ev) { ensureTarget(); boolean handled = false; if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) { Log.i("CGQ", "不攔截子view滾動"); mReturningToStart = false; if (isUped || isDowned) { finishSpinner(0); } } if (isEnabled() && !mReturningToStart && (!canChildScrollUp() || !canChildScrollDown()) && !mRefreshing && !mNestedScrollInProgress) { handled = onTouchEvent(ev); Log.i("CGQ", "事件分發"); } //Log.i("CGQ", "handled=" + handled); //父控制元件消費,子空間就不執行了。父控制元件不消費,再交給子空間處理 return !handled ? super.onInterceptTouchEvent(ev) : handled; } private float mDownY; private boolean mIsBeingDraggedTop;//頂部拖拽狀態 @Override public boolean onTouchEvent(MotionEvent event) { if (!canRefresh) { return false; } int action = event.getAction(); boolean handled = false; switch (action) { case MotionEvent.ACTION_DOWN: // 移動的起點 mDownY = event.getY(); mInitialDownY = event.getY(); break; case MotionEvent.ACTION_MOVE: if (Math.abs(event.getY() - mDownY) < mTouchSlop) { Log.i("CGQ", "未達到滾動最小值"); return false; } float offset_c = (event.getY() - mDownY);//當前滑動間距 if (offset_c > 0) {//下滑手勢 if (isDowned) {//已經是下滑到底狀態 finishSpinner(0); } else {//不是下滑到底狀態 if (canChildScrollUp()) { Log.i("CGQ", "下滑"); } else { Log.i("CGQ", "下滑並觸發"); handled = true; float y = event.getY(); startDragging(y); final float overscrollTop = offset_c * DRAG_RATE; //下滑並觸發 moveSpinner(overscrollTop); } } } else {//上拉手勢 if (isUped) {//已經是上拉到頂狀態 finishSpinner(0); } else { if (canChildScrollDown()) { Log.i("CGQ", "上滑"); } else { Log.i("CGQ", "上滑並觸發"); handled = true; float y = event.getY(); startDragging(y); final float overscrollTop = offset_c * DRAG_RATE; //下拉顯示頭部 moveSpinner(overscrollTop); } } } break; case MotionEvent.ACTION_UP: finishSpinner(0); break; case MotionEvent.ACTION_CANCEL: finishSpinner(0); break; } return handled; } /** * @param offset 偏移量 */ private void scrollTopLayout(int offset) { if (contentLayout != null) { ViewCompat.offsetTopAndBottom(contentLayout, offset);//正數向下移動,負數向上移動 //topLayout.bringToFront(); if (isUping) { ViewCompat.offsetTopAndBottom(topLayout, offset); } else { ViewCompat.offsetTopAndBottom(bottomLayout, offset); } mContenViewOffsetTop = contentLayout.getTop(); } } private float mInitialMotionY; private static final float DRAG_RATE = .5f; private float mInitialDownY; private void startDragging(float y) { final float yDiff = y - mInitialDownY; if (yDiff > mTouchSlop && !mIsBeingDraggedTop) { mInitialMotionY = mInitialDownY + mTouchSlop; mIsBeingDraggedTop = true; } } private float mTotalDragDistance = -1; int mSpinnerOffsetEnd; protected int mOriginalOffsetTop; int mContenViewOffsetTop; /** * @param overscrollTop * @param */ private void moveSpinner(float overscrollTop) { boolean isTop = false; int targetY2 = 0; if (overscrollTop < 0) { isTop = true; overscrollTop = -overscrollTop; //更改狀態 isDowning = true; } else { //更改狀態 isUping = true; } Log.i("CGQ", "moveSpinner = " + overscrollTop); float originalDragPercent = overscrollTop / mTotalDragDistance; float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); float extraOS = Math.abs(overscrollTop) - mTotalDragDistance; float slingshotDist = mUsingCustomStart ? mSpinnerOffsetEnd - mOriginalOffsetTop : mSpinnerOffsetEnd; float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) / slingshotDist); float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( (tensionSlingshotPercent / 4), 2)) * 2f; float extraMove = (slingshotDist) * tensionPercent * 2; targetY2 = (int) ((slingshotDist * dragPercent) + extraMove); int h = !isTop ? (targetY2 - mContenViewOffsetTop) : (-targetY2 - mContenViewOffsetTop); if (animationListener != null) { animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, (float) Math.abs(contentLayout.getTop()) / bottomLayoutHeight); } Log.i("CGQ", "slingshotDist=" + slingshotDist + ",h=" + h); scrollTopLayout(h); } private boolean isUping = false;//正在上拉重新整理 private boolean isDowning = false;//正在下拉 private boolean isUped = false;//正在上拉到指定位置(預設拉到三分之二就觸發載入) private boolean isDowned = false;//正在下拉到指定位置(預設拉到三分之二就觸發載入) /** * 恢復 */ public void setCancel() { finishSpinner(0); } //是否可以上拉下拉滑動 private boolean canRefresh = true; /** * 是否可以上拉下拉滑動 * * @param canRefresh */ public void setCanRefresh(boolean canRefresh) { this.canRefresh = canRefresh; } /** * 恢復動畫 */ private void finishSpinner(float overscrollTop) { if (contentLayout == null) { return; } //處理是否觸發重新整理或者載入更多 if (Math.abs(contentLayout.getTop()) > bottomLayoutHeight / 3 * 2 && (!isUped && !isDowned)) {//拉到2/3以上則觸發 //填充動畫(自動下拉到最大高度) float startValue = Math.abs(contentLayout.getTop())/(float)bottomLayoutHeight; ValueAnimator animator = ValueAnimator.ofFloat(startValue, 1); animator.setDuration((int)(200*(1-startValue))); animator.setInterpolator(mDecelerateInterpolator); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float scale = (float) animation.getAnimatedValue(); if(contentLayout.getTop()>0&& (topLayout.getTop() != topLayoutOffsetTop )){ ViewCompat.offsetTopAndBottom(contentLayout, (int) (( bottomLayoutHeight - contentLayout.getTop()) * scale));//正數向下移動,負數向上移動 ViewCompat.offsetTopAndBottom(topLayout, (int) ( (bottomLayoutHeight + (topLayoutOffsetTop - topLayout.getTop())) * scale)); }else if( (topLayout.getTop() != topLayoutOffsetTop )){ ViewCompat.offsetTopAndBottom(contentLayout, (int) ((- bottomLayoutHeight - contentLayout.getTop()) * scale));//正數向下移動,負數向上移動 ViewCompat.offsetTopAndBottom(bottomLayout, (int) ((-bottomLayoutHeight + (bottomLayoutOffsetTop - bottomLayout.getTop())) * scale)); } mContenViewOffsetTop = contentLayout.getTop(); if (scale == 0) {//動畫開始 if (animationListener != null) { animationListener.onStart(isUping ? topLayoutView : bottomLayoutView); } } else if (scale == 1) {//動畫結束 if (animationListener != null) { animationListener.onEnd(isUping ? topLayoutView : bottomLayoutView); } if (isUping) {//上拉處理 isUping = false; isUped = true; if (dataListener != null) { dataListener.onRefreshData(); } } if (isDowning) {//下拉處理 isDowning = false; isDowned = true; if (dataListener != null) { dataListener.onLoadMoreData(); } } } else {//動畫進行中 if (animationListener != null) { animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, scale); } } } }); animator.start(); } else { //回滾動畫 ValueAnimator animator = ValueAnimator.ofFloat(0, 1); animator.setDuration(200); animator.setInterpolator(mDecelerateInterpolator); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float scale = (float) animation.getAnimatedValue(); ViewCompat.offsetTopAndBottom(contentLayout, (int) (-contentLayout.getTop() * scale));//正數向下移動,負數向上移動 if (topLayout.getTop() != topLayoutOffsetTop) { ViewCompat.offsetTopAndBottom(topLayout, (int) ((topLayoutOffsetTop - topLayout.getTop()) * scale)); } if (bottomLayout.getTop() != bottomLayoutOffsetTop) { ViewCompat.offsetTopAndBottom(bottomLayout, (int) ((bottomLayoutOffsetTop - bottomLayout.getTop()) * scale)); } mContenViewOffsetTop = contentLayout.getTop(); if (scale == 0) {//動畫開始 if (animationListener != null) { animationListener.onStart(isUping ? topLayoutView : bottomLayoutView); } } else if (scale == 1) {//動畫結束 isUped = false; isDowned = false; isUping = false; isDowning = false; if (animationListener != null) { animationListener.onEnd(isUping ? topLayoutView : bottomLayoutView); } } else {//動畫進行中 if (animationListener != null) { animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, scale); } } } }); animator.start(); } } // NestedScrollingParent private float mTotalUnconsumed; private boolean mNestedScrollInProgress; private final int[] mParentScrollConsumed = new int[2]; private final int[] mParentOffsetInWindow = new int[2]; // Whether the client has set a custom starting position; boolean mUsingCustomStart; private boolean mReturningToStart; boolean mRefreshing = false; private OnChildScrollUpCallback mChildScrollUpCallback; @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return isEnabled() && !mReturningToStart && !mRefreshing && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; } @Override public void onNestedScrollAccepted(View child, View target, int axes) { // Reset the counter of how much leftover scroll needs to be consumed. mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); // Dispatch up to the nested parent startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL); mTotalUnconsumed = 0; mNestedScrollInProgress = true; } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { // If we are in the middle of consuming, a scroll, then we want to move the spinner back up // before allowing the list to scroll if (dy > 0 && mTotalUnconsumed > 0) { if (dy > mTotalUnconsumed) { consumed[1] = dy - (int) mTotalUnconsumed; mTotalUnconsumed = 0; } else { mTotalUnconsumed -= dy; consumed[1] = dy; } moveSpinner(mTotalUnconsumed); } // If a client layout is using a custom start position for the circle // view, they mean to hide it again before scrolling the child view // If we get back to mTotalUnconsumed == 0 and there is more to go, hide // the circle so it isn't exposed if its blocking content is moved if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0 && Math.abs(dy - consumed[1]) > 0) { //mCircleView.setVisibility(View.GONE); } // Now let our nested parent consume the leftovers final int[] parentConsumed = mParentScrollConsumed; if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) { consumed[0] += parentConsumed[0]; consumed[1] += parentConsumed[1]; } } @Override public int getNestedScrollAxes() { return mNestedScrollingParentHelper.getNestedScrollAxes(); } @Override public void onStopNestedScroll(View target) { mNestedScrollingParentHelper.onStopNestedScroll(target); mNestedScrollInProgress = false; // Finish the spinner for nested scrolling if we ever consumed any // unconsumed nested scroll if (mTotalUnconsumed > 0) { finishSpinner(mTotalUnconsumed); mTotalUnconsumed = 0; } // Dispatch up our nested parent stopNestedScroll(); } @Override public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed, final int dxUnconsumed, final int dyUnconsumed) { // Dispatch up to the nested parent first dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mParentOffsetInWindow); // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are // sometimes between two nested scrolling views, we need a way to be able to know when any // nested scrolling parent has stopped handling events. We do that by using the // 'offset in window 'functionality to see if we have been moved from the event. // This is a decent indication of whether we should take over the event stream or not. final int dy = dyUnconsumed + mParentOffsetInWindow[1]; if (dy < 0 && !canChildScrollUp()) { mTotalUnconsumed += Math.abs(dy); moveSpinner(mTotalUnconsumed); } } // NestedScrollingChild @Override public void setNestedScrollingEnabled(boolean enabled) { mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mNestedScrollingChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mNestedScrollingChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mNestedScrollingChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mNestedScrollingChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mNestedScrollingChildHelper.dispatchNestedPreScroll( dx, dy, consumed, offsetInWindow); } @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { return dispatchNestedPreFling(velocityX, velocityY); } @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); } //判斷是否可以下拉 public boolean canChildScrollUp() { if (mChildScrollUpCallback != null) { return mChildScrollUpCallback.canChildScrollUp(this, contentLayout); } if (contentLayout instanceof ListView) { return ListViewCompat.canScrollList((ListView) contentLayout, -1); } return contentLayout.canScrollVertically(-1); } //判斷是否可以上拉 public boolean canChildScrollDown() { if (android.os.Build.VERSION.SDK_INT < 14) { if (contentLayout instanceof AbsListView) { final AbsListView absListView = (AbsListView) contentLayout; // AppLog.e(absListView.getFirstVisiblePosition()+" : "+absListView.getChildAt(absListView.getChildCount()-1).getBottom()+" : "+absListView.getPaddingBottom()); return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) .getTop() < absListView.getPaddingTop()); } else { return contentLayout.getScrollY() > 0; } } else { return ViewCompat.canScrollVertically(contentLayout, 1); } } public void setOnChildScrollUpCallback(@Nullable OnChildScrollUpCallback callback) { mChildScrollUpCallback = callback; } /** * Classes that wish to override {@link PushCardLayout#canChildScrollUp()} method * behavior should implement this interface. */ public interface OnChildScrollUpCallback { /** * Callback that will be called when {@link PushCardLayout#canChildScrollUp()} method * is called to allow the implementer to override its behavior. * * @param parent SwipeRefreshLayout that this callback is overriding. * @param child The child view of SwipeRefreshLayout. * @return Whether it is possible for the child view of parent layout to scroll up. */ boolean canChildScrollUp(@NonNull PushCardLayout parent, @Nullable View child); /** * Callback that will be called when {@link PushCardLayout#canChildScrollDown()} method * is called to allow the implementer to override its behavior. * * @param parent SwipeRefreshLayout that this callback is overriding. * @param child The child view of SwipeRefreshLayout. * @return Whether it is possible for the child view of parent layout to scroll down. */ boolean canChildScrollDown(@NonNull PushCardLayout parent, @Nullable View child); } /** * 資料監聽器 */ public interface PushCardDatalistener { /** * 上拉載入更多 */ void onLoadMoreData(); /** * 下拉重新整理 */ void onRefreshData(); } /** * 動畫監聽器 */ public interface PushCardAnimationListener { /** * 開始初始化操作 */ void onStart(View targetView); /** * 0-1屬性動畫,下拉百分比動畫 * * @param targetView 目標view(頭部或者底部) * @param isUpper //判斷頭部動畫還是底部動畫 * @param value 動畫百分比 */ void onRuning(View targetView, boolean isUpper, float value); /** * 動畫結束 */ void onEnd(View targetView); } }
一個類完事了。要啥動畫自己寫去吧!
使用部分:
1.layout
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".activity.CustomerActivity"> <com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout android:id="@+id/pcl_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:overScrollMode="never"> <include layout="@layout/layout_main"/> </com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout> </android.support.constraint.ConstraintLayout>
2.activity
package com.huan.squirrel.pushcardlayout.activity;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.huan.squirrel.pushcardlayout.R;
import com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout;
public class CustomerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_customer);
initView();
}
private void initView() {
//初始化
PushCardLayout pcl_layout = findViewById(R.id.pcl_layout);
/************************* 自定義頭部和底部佈局 ************************************************/
TextView textView = new TextView(this);
textView.setText("下拉重新整理資料");
textView.setTextSize(20);
textView.setBackgroundColor(Color.RED);
TextView textView2 = new TextView(this);
textView2.setText("上拉載入更多資料");
textView2.setTextSize(20);
textView2.setBackgroundColor(Color.YELLOW);
//設定頂部佈局view
pcl_layout.setTopLayoutView(textView);
//設定底部佈局view
pcl_layout.setBottomLayoutView(textView2);
//禁用滑動 pcl_layout.setCanRefresh(false);
/************************* 設定資料監聽器,可觸發網路請求 資料載入完成請手動恢復 pcl_layout.setCancel(); ********************************/
pcl_layout.setDataListener(new PushCardLayout.PushCardDatalistener() {
@Override
public void onLoadMoreData() {
Toast.makeText(CustomerActivity.this, "載入更多。。。", Toast.LENGTH_SHORT).show();
}
@Override
public void onRefreshData() {
Toast.makeText(CustomerActivity.this, "重新整理資料。。。", Toast.LENGTH_SHORT).show();
}
});
/************************* 設定動畫監聽器,可自定義動畫 ************************************************/
pcl_layout.setAnimationListener(new PushCardLayout.PushCardAnimationListener() {
@Override
public void onStart(View targetView) {
Log.i("A", "Animation Start ...");
}
@Override
public void onRuning(View targetView,boolean isUpper, final float value) {
Log.i("A", "Animation onRuning:" + value);
((TextView) targetView).setRotation(value * 360);
//isUpper 可判斷是頭部動畫還是底部動畫
}
@Override
public void onEnd(View targetView) {
Log.i("A", "Animation End ...");
}
});
}
}