From 5447a042944d768cf9b68162f192f129ecdf30d4 Mon Sep 17 00:00:00 2001 From: wxj <986798656@qq.com> Date: Sun, 22 Jan 2017 15:28:49 +0800 Subject: [PATCH 1/6] add horizontally layout --- library/build.gradle | 1 + .../HorSwipeToLoadLayout.java | 1583 +++++++++++++++++ library/src/main/res/values/attrs.xml | 3 +- 3 files changed, 1586 insertions(+), 1 deletion(-) create mode 100644 library/src/main/java/com/aspsine/swipetoloadlayout/HorSwipeToLoadLayout.java diff --git a/library/build.gradle b/library/build.gradle index 62bd761..8055fb2 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -22,4 +22,5 @@ dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile 'com.android.support:support-compat:25.1.0' + compile 'com.android.support:appcompat-v7:25.1.0' } diff --git a/library/src/main/java/com/aspsine/swipetoloadlayout/HorSwipeToLoadLayout.java b/library/src/main/java/com/aspsine/swipetoloadlayout/HorSwipeToLoadLayout.java new file mode 100644 index 0000000..3233a20 --- /dev/null +++ b/library/src/main/java/com/aspsine/swipetoloadlayout/HorSwipeToLoadLayout.java @@ -0,0 +1,1583 @@ +package com.aspsine.swipetoloadlayout; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.Scroller; + +public class HorSwipeToLoadLayout extends ViewGroup { + private static final String TAG = HorSwipeToLoadLayout.class.getSimpleName(); + private static final int DEFAULT_SWIPING_TO_REFRESH_TO_DEFAULT_SCROLLING_DURATION = 200; + private static final int DEFAULT_RELEASE_TO_REFRESHING_SCROLLING_DURATION = 200; + private static final int DEFAULT_REFRESH_COMPLETE_DELAY_DURATION = 300; + private static final int DEFAULT_REFRESH_COMPLETE_TO_DEFAULT_SCROLLING_DURATION = 500; + private static final int DEFAULT_DEFAULT_TO_REFRESHING_SCROLLING_DURATION = 500; + private static final int DEFAULT_SWIPING_TO_LOAD_MORE_TO_DEFAULT_SCROLLING_DURATION = 200; + private static final int DEFAULT_RELEASE_TO_LOADING_MORE_SCROLLING_DURATION = 200; + private static final int DEFAULT_LOAD_MORE_COMPLETE_DELAY_DURATION = 300; + private static final int DEFAULT_LOAD_MORE_COMPLETE_TO_DEFAULT_SCROLLING_DURATION = 300; + private static final int DEFAULT_DEFAULT_TO_LOADING_MORE_SCROLLING_DURATION = 300; + private static final float DEFAULT_DRAG_RATIO = 0.5F; + private static final int INVALID_POINTER = -1; + private static final int INVALID_COORDINATE = -1; + private HorSwipeToLoadLayout.AutoScroller mAutoScroller; + private OnRefreshListener mRefreshListener; + private OnLoadMoreListener mLoadMoreListener; + private View mHeaderView; + private View mTargetView; + private View mFooterView; + private int mHeaderWidth; + private int mFooterWidth; + private boolean mHasHeaderView; + private boolean mHasFooterView; + private boolean mDebug; + private float mDragRatio = DEFAULT_DRAG_RATIO; + private boolean mAutoLoading; + private final int mTouchSlop; + private int mStatus = STATUS.STATUS_DEFAULT; + private int mHeaderOffset; + private int mTargetOffset; + private int mFooterOffset; + private float mInitDownY; + private float mInitDownX; + private float mLastY; + private float mLastX; + private int mActivePointerId; + private boolean mRefreshEnabled = true; + private boolean mLoadMoreEnabled = true; + private int mStyle = STYLE.CLASSIC; + private float mRefreshTriggerOffset; + private float mLoadMoreTriggerOffset; + private float mRefreshFinalDragOffset; + private float mLoadMoreFinalDragOffset; + /** + * ATTRIBUTE: + * Scrolling duration swiping to refresh -> default + */ + private int mSwipingToRefreshToDefaultScrollingDuration = DEFAULT_SWIPING_TO_REFRESH_TO_DEFAULT_SCROLLING_DURATION; + + /** + * ATTRIBUTE: + * Scrolling duration status release to refresh -> refreshing + */ + private int mReleaseToRefreshToRefreshingScrollingDuration = DEFAULT_RELEASE_TO_REFRESHING_SCROLLING_DURATION; + + /** + * ATTRIBUTE: + * Refresh complete delay duration + */ + private int mRefreshCompleteDelayDuration = DEFAULT_REFRESH_COMPLETE_DELAY_DURATION; + + /** + * ATTRIBUTE: + * Scrolling duration status refresh complete -> default + * {@link #setRefreshing(boolean)} false + */ + private int mRefreshCompleteToDefaultScrollingDuration = DEFAULT_REFRESH_COMPLETE_TO_DEFAULT_SCROLLING_DURATION; + + /** + * ATTRIBUTE: + * Scrolling duration status default -> refreshing, mainly for auto refresh + * {@link #setRefreshing(boolean)} true + */ + private int mDefaultToRefreshingScrollingDuration = DEFAULT_DEFAULT_TO_REFRESHING_SCROLLING_DURATION; + + /** + * ATTRIBUTE: + * Scrolling duration status release to loading more -> loading more + */ + private int mReleaseToLoadMoreToLoadingMoreScrollingDuration = DEFAULT_RELEASE_TO_LOADING_MORE_SCROLLING_DURATION; + + + /** + * ATTRIBUTE: + * Load more complete delay duration + */ + private int mLoadMoreCompleteDelayDuration = DEFAULT_LOAD_MORE_COMPLETE_DELAY_DURATION; + + /** + * ATTRIBUTE: + * Scrolling duration status load more complete -> default + * {@link #setLoadingMore(boolean)} false + */ + private int mLoadMoreCompleteToDefaultScrollingDuration = DEFAULT_LOAD_MORE_COMPLETE_TO_DEFAULT_SCROLLING_DURATION; + + /** + * ATTRIBUTE: + * Scrolling duration swiping to load more -> default + */ + private int mSwipingToLoadMoreToDefaultScrollingDuration = DEFAULT_SWIPING_TO_LOAD_MORE_TO_DEFAULT_SCROLLING_DURATION; + + /** + * ATTRIBUTE: + * Scrolling duration status default -> loading more, mainly for auto load more + * {@link #setLoadingMore(boolean)} true + */ + private int mDefaultToLoadingMoreScrollingDuration = DEFAULT_DEFAULT_TO_LOADING_MORE_SCROLLING_DURATION; + + /** + * the style enum + */ + public static final class STYLE { + public static final int CLASSIC = 0; + public static final int ABOVE = 1; + public static final int BLEW = 2; + public static final int SCALE = 3; + } + + + public HorSwipeToLoadLayout(Context context) { + this(context, (AttributeSet) null); + } + + public HorSwipeToLoadLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public HorSwipeToLoadLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorSwipeToLoadLayout, defStyleAttr, 0); + + try { + int N = a.getIndexCount(); + + for (int i = 0; i < N; ++i) { + int attr = a.getIndex(i); + if (attr == R.styleable.HorSwipeToLoadLayout_refresh_enabled) { + this.setRefreshEnabled(a.getBoolean(attr, mRefreshEnabled)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_enabled) { + this.setLoadMoreEnabled(a.getBoolean(attr, mLoadMoreEnabled)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_swipe_style) { + this.setSwipeStyle(a.getInt(attr, mStyle)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_drag_ratio) { + this.setDragRatio(a.getFloat(attr, mDragRatio)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_refresh_final_drag_offset) { + this.setRefreshFinalDragOffset(a.getDimensionPixelOffset(attr, 0)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_final_drag_offset) { + this.setLoadMoreFinalDragOffset(a.getDimensionPixelOffset(attr, 0)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_refresh_trigger_offset) { + this.setRefreshTriggerOffset(a.getDimensionPixelOffset(attr, 0)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_trigger_offset) { + this.setLoadMoreTriggerOffset(a.getDimensionPixelOffset(attr, 0)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_swiping_to_refresh_to_default_scrolling_duration) { + this.setSwipingToRefreshToDefaultScrollingDuration(a.getInt(attr, mSwipingToRefreshToDefaultScrollingDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_release_to_refreshing_scrolling_duration) { + this.setReleaseToRefreshingScrollingDuration(a.getInt(attr, mReleaseToRefreshToRefreshingScrollingDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_refresh_complete_delay_duration) { + this.setRefreshCompleteDelayDuration(a.getInt(attr, mRefreshCompleteDelayDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_refresh_complete_to_default_scrolling_duration) { + this.setRefreshCompleteToDefaultScrollingDuration(a.getInt(attr, mRefreshCompleteToDefaultScrollingDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_default_to_refreshing_scrolling_duration) { + this.setDefaultToRefreshingScrollingDuration(a.getInt(attr, mDefaultToRefreshingScrollingDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_swiping_to_load_more_to_default_scrolling_duration) { + this.setSwipingToLoadMoreToDefaultScrollingDuration(a.getInt(attr, mSwipingToLoadMoreToDefaultScrollingDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_release_to_loading_more_scrolling_duration) { + this.setReleaseToLoadingMoreScrollingDuration(a.getInt(attr, mReleaseToLoadMoreToLoadingMoreScrollingDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_complete_delay_duration) { + this.setLoadMoreCompleteDelayDuration(a.getInt(attr, mLoadMoreCompleteDelayDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_complete_to_default_scrolling_duration) { + this.setLoadMoreCompleteToDefaultScrollingDuration(a.getInt(attr, mLoadMoreCompleteToDefaultScrollingDuration)); + } else if (attr == R.styleable.HorSwipeToLoadLayout_default_to_loading_more_scrolling_duration) { + this.setDefaultToLoadingMoreScrollingDuration(a.getInt(attr, mDefaultToLoadingMoreScrollingDuration)); + } + } + } finally { + a.recycle(); + } + + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mAutoScroller = new HorSwipeToLoadLayout.AutoScroller(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + final int childNum = getChildCount(); + if (childNum == 0) { + // no child return + return; + } else if (0 < childNum && childNum < 4) { + mHeaderView = findViewById(R.id.swipe_refresh_header); + mTargetView = findViewById(R.id.swipe_target); + mFooterView = findViewById(R.id.swipe_load_more_footer); + } else { + // more than three children: unsupported! + throw new IllegalStateException("Children num must equal or less than 3"); + } + if (mTargetView == null) { + return; + } + if (mHeaderView != null && mHeaderView instanceof SwipeTrigger) { + mHeaderView.setVisibility(GONE); + } + if (mFooterView != null && mFooterView instanceof SwipeTrigger) { + mFooterView.setVisibility(GONE); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // header + if (mHeaderView != null) { + final View headerView = mHeaderView; + measureChildWithMargins(headerView, widthMeasureSpec, 0, heightMeasureSpec, 0); + MarginLayoutParams lp = ((MarginLayoutParams) headerView.getLayoutParams()); + mHeaderWidth = headerView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; + if (mRefreshTriggerOffset < mHeaderWidth) { + mRefreshTriggerOffset = mHeaderWidth; + } + } + // target + if (mTargetView != null) { + final View targetView = mTargetView; + measureChildWithMargins(targetView, widthMeasureSpec, 0, heightMeasureSpec, 0); + } + // footer + if (mFooterView != null) { + final View footerView = mFooterView; + measureChildWithMargins(footerView, widthMeasureSpec, 0, heightMeasureSpec, 0); + MarginLayoutParams lp = ((MarginLayoutParams) footerView.getLayoutParams()); + mFooterWidth = footerView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; + if (mLoadMoreTriggerOffset < mFooterWidth) { + mLoadMoreTriggerOffset = mFooterWidth; + } + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + layoutChildren(); + + mHasHeaderView = (mHeaderView != null); + mHasFooterView = (mFooterView != null); + } + + /** + * LayoutParams of RefreshLoadMoreLayout + */ + public static class LayoutParams extends MarginLayoutParams { + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(int width, int height) { + super(width, height); + } + + public LayoutParams(MarginLayoutParams source) { + super(source); + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new HorSwipeToLoadLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + } + + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new HorSwipeToLoadLayout.LayoutParams(p); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new HorSwipeToLoadLayout.LayoutParams(getContext(), attrs); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final int action = MotionEventCompat.getActionMasked(ev); + switch (action) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + // swipeToRefresh -> finger up -> finger down if the status is still swipeToRefresh + // in onInterceptTouchEvent ACTION_DOWN event will stop the scroller + // if the event pass to the child view while ACTION_MOVE(condition is false) + // in onInterceptTouchEvent ACTION_MOVE the ACTION_UP or ACTION_CANCEL will not be + // passed to onInterceptTouchEvent and onTouchEvent. Instead It will be passed to + // child view's onTouchEvent. So we must deal this situation in dispatchTouchEvent + onActivePointerUp(); + break; + } + return super.dispatchTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + final int action = MotionEventCompat.getActionMasked(event); + switch (action) { + case MotionEvent.ACTION_DOWN: + + mActivePointerId = event.getPointerId(0); + mInitDownY = mLastY = getMotionEventY(event, mActivePointerId); + mInitDownX = mLastX = getMotionEventX(event, mActivePointerId); + + // if it isn't an ing status or default status + if (STATUS.isSwipingToRefresh(mStatus) || STATUS.isSwipingToLoadMore(mStatus) || + STATUS.isReleaseToRefresh(mStatus) || STATUS.isReleaseToLoadMore(mStatus)) { + // abort autoScrolling, not trigger the method #autoScrollFinished() + mAutoScroller.abortIfRunning(); + if (mDebug) { + Log.i(TAG, "Another finger down, abort auto scrolling, let the new finger handle"); + } + } + + if (STATUS.isSwipingToRefresh(mStatus) || STATUS.isReleaseToRefresh(mStatus) + || STATUS.isSwipingToLoadMore(mStatus) || STATUS.isReleaseToLoadMore(mStatus)) { + return true; + } + + // let children view handle the ACTION_DOWN; + + // 1. children consumed: + // if at least one of children onTouchEvent() ACTION_DOWN return true. + // ACTION_DOWN event will not return to SwipeToLoadLayout#onTouchEvent(). + // but the others action can be handled by SwipeToLoadLayout#onInterceptTouchEvent() + + // 2. children not consumed: + // if children onTouchEvent() ACTION_DOWN return false. + // ACTION_DOWN event will return to SwipeToLoadLayout's onTouchEvent(). + // SwipeToLoadLayout#onTouchEvent() ACTION_DOWN return true to consume the ACTION_DOWN event. + + // anyway: handle action down in onInterceptTouchEvent() to init is an good option + break; + case MotionEvent.ACTION_MOVE: + if (mActivePointerId == INVALID_POINTER) { + return false; + } + float y = getMotionEventY(event, mActivePointerId); + float x = getMotionEventX(event, mActivePointerId); + final float yInitDiff = y - mInitDownY; + final float xInitDiff = x - mInitDownX; + mLastY = y; + mLastX = x; + boolean moved = Math.abs(xInitDiff) > Math.abs(yInitDiff) + && Math.abs(xInitDiff) > mTouchSlop; + boolean triggerCondition = + // refresh trigger condition + (xInitDiff > 0 && moved && onCheckCanRefresh()) || + //load more trigger condition + (xInitDiff < 0 && moved && onCheckCanLoadMore()); + if (triggerCondition) { + // if the refresh's or load more's trigger condition is true, + // intercept the move action event and pass it to SwipeToLoadLayout#onTouchEvent() + return true; + } + break; + case MotionEvent.ACTION_POINTER_UP: { + onSecondaryPointerUp(event); + mInitDownY = mLastY = getMotionEventY(event, mActivePointerId); + mInitDownX = mLastX = getMotionEventX(event, mActivePointerId); + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mActivePointerId = INVALID_POINTER; + break; + } + return super.onInterceptTouchEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int action = MotionEventCompat.getActionMasked(event); + + switch (action) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = event.getPointerId(0); + return true; + + case MotionEvent.ACTION_MOVE: + // take over the ACTION_MOVE event from SwipeToLoadLayout#onInterceptTouchEvent() + // if condition is true + final float y = getMotionEventY(event, mActivePointerId); + final float x = getMotionEventX(event, mActivePointerId); + + final float yDiff = y - mLastY; + final float xDiff = x - mLastX; + mLastY = y; + mLastX = x; + + if (Math.abs(yDiff) > Math.abs(xDiff) && Math.abs(yDiff) > mTouchSlop) { + return false; + } + + if (STATUS.isStatusDefault(mStatus)) { + if (xDiff > 0 && onCheckCanRefresh()) { + mRefreshCallback.onPrepare(); + setStatus(STATUS.STATUS_SWIPING_TO_REFRESH); + } else if (xDiff < 0 && onCheckCanLoadMore()) { + mLoadMoreCallback.onPrepare(); + setStatus(STATUS.STATUS_SWIPING_TO_LOAD_MORE); + } + } else if (STATUS.isRefreshStatus(mStatus)) { + if (mTargetOffset <= 0) { + setStatus(STATUS.STATUS_DEFAULT); + fixCurrentStatusLayout(); + return false; + } + } else if (STATUS.isLoadMoreStatus(mStatus)) { + if (mTargetOffset >= 0) { + setStatus(STATUS.STATUS_DEFAULT); + fixCurrentStatusLayout(); + return false; + } + } + + if (STATUS.isRefreshStatus(mStatus)) { + if (STATUS.isSwipingToRefresh(mStatus) || STATUS.isReleaseToRefresh(mStatus)) { + if (mTargetOffset >= mRefreshTriggerOffset) { + setStatus(STATUS.STATUS_RELEASE_TO_REFRESH); + } else { + setStatus(STATUS.STATUS_SWIPING_TO_REFRESH); + } + fingerScroll(xDiff); + } + } else if (STATUS.isLoadMoreStatus(mStatus)) { + if (STATUS.isSwipingToLoadMore(mStatus) || STATUS.isReleaseToLoadMore(mStatus)) { + if (-mTargetOffset >= mLoadMoreTriggerOffset) { + setStatus(STATUS.STATUS_RELEASE_TO_LOAD_MORE); + } else { + setStatus(STATUS.STATUS_SWIPING_TO_LOAD_MORE); + } + fingerScroll(xDiff); + } + } + return true; + + case MotionEvent.ACTION_POINTER_DOWN: { + final int pointerIndex = MotionEventCompat.getActionIndex(event); + final int pointerId = event.getPointerId(pointerIndex); + if (pointerId != INVALID_POINTER) { + mActivePointerId = pointerId; + } + mInitDownY = mLastY = getMotionEventY(event, mActivePointerId); + mInitDownX = mLastX = getMotionEventX(event, mActivePointerId); + break; + } + case MotionEvent.ACTION_POINTER_UP: { + onSecondaryPointerUp(event); + mInitDownY = mLastY = getMotionEventY(event, mActivePointerId); + mInitDownX = mLastX = getMotionEventX(event, mActivePointerId); + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mActivePointerId == INVALID_POINTER) { + return false; + } + mActivePointerId = INVALID_POINTER; + break; + default: + break; + } + return super.onTouchEvent(event); + } + + /** + * set debug mode(default value false) + * + * @param debug if true log on, false log off + */ + public void setDebug(boolean debug) { + this.mDebug = debug; + } + + /** + * is refresh function is enabled + * + * @return + */ + public boolean isRefreshEnabled() { + return mRefreshEnabled; + } + + /** + * switch refresh function on or off + * + * @param enable + */ + public void setRefreshEnabled(boolean enable) { + this.mRefreshEnabled = enable; + } + + /** + * is load more function is enabled + * + * @return + */ + public boolean isLoadMoreEnabled() { + return mLoadMoreEnabled; + } + + /** + * switch load more function on or off + * + * @param enable + */ + public void setLoadMoreEnabled(boolean enable) { + this.mLoadMoreEnabled = enable; + } + + /** + * is current status refreshing + * + * @return + */ + public boolean isRefreshing() { + return STATUS.isRefreshing(mStatus); + } + + /** + * is current status loading more + * + * @return + */ + public boolean isLoadingMore() { + return STATUS.isLoadingMore(mStatus); + } + + /** + * set refresh header view, the view must at lease be an implement of {@code SwipeRefreshTrigger}. + * the view can also implement {@code SwipeTrigger} for more extension functions + * + * @param view + */ + public void setRefreshHeaderView(View view) { + if (view instanceof SwipeRefreshTrigger) { + if (mHeaderView != null && mHeaderView != view) { + removeView(mHeaderView); + } + if (mHeaderView != view) { + this.mHeaderView = view; + addView(view); + } + } else { + Log.e(TAG, "Refresh header view must be an implement of SwipeRefreshTrigger"); + } + } + + /** + * set load more footer view, the view must at least be an implement of SwipeLoadTrigger + * the view can also implement {@code SwipeTrigger} for more extension functions + * + * @param view + */ + public void setLoadMoreFooterView(View view) { + if (view instanceof SwipeLoadMoreTrigger) { + if (mFooterView != null && mFooterView != view) { + removeView(mFooterView); + } + if (mFooterView != view) { + this.mFooterView = view; + addView(mFooterView); + } + } else { + Log.e(TAG, "Load more footer view must be an implement of SwipeLoadTrigger"); + } + } + + /** + * set the style of the refresh header + * + * @param style + */ + public void setSwipeStyle(int style) { + this.mStyle = style; + requestLayout(); + } + + /** + * set how hard to drag. bigger easier, smaller harder; + * + * @param dragRatio default value is {@link #DEFAULT_DRAG_RATIO} + */ + public void setDragRatio(float dragRatio) { + this.mDragRatio = dragRatio; + } + + /** + * set the value of {@link #mRefreshTriggerOffset}. + * Default value is the refresh header view height {@link #mHeaderWidth}

+ * If the offset you set is smaller than {@link #mHeaderWidth} or not set, + * using {@link #mHeaderWidth} as default value + * + * @param offset + */ + public void setRefreshTriggerOffset(int offset) { + mRefreshTriggerOffset = offset; + } + + /** + * set the value of {@link #mLoadMoreTriggerOffset}. + * Default value is the load more footer view height {@link #mFooterWidth}

+ * If the offset you set is smaller than {@link #mFooterWidth} or not set, + * using {@link #mFooterWidth} as default value + * + * @param offset + */ + public void setLoadMoreTriggerOffset(int offset) { + mLoadMoreTriggerOffset = offset; + } + + /** + * Set the final offset you can swipe to refresh.
+ * If the offset you set is 0(default value) or smaller than {@link #mRefreshTriggerOffset} + * there no final offset + * + * @param offset + */ + public void setRefreshFinalDragOffset(int offset) { + mRefreshFinalDragOffset = offset; + } + + /** + * Set the final offset you can swipe to load more.
+ * If the offset you set is 0(default value) or smaller than {@link #mLoadMoreTriggerOffset}, + * there no final offset + * + * @param offset + */ + public void setLoadMoreFinalDragOffset(int offset) { + mLoadMoreFinalDragOffset = offset; + } + + /** + * set {@link #mSwipingToRefreshToDefaultScrollingDuration} in milliseconds + * + * @param duration + */ + public void setSwipingToRefreshToDefaultScrollingDuration(int duration) { + this.mSwipingToRefreshToDefaultScrollingDuration = duration; + } + + /** + * set {@link #mReleaseToRefreshToRefreshingScrollingDuration} in milliseconds + * + * @param duration + */ + public void setReleaseToRefreshingScrollingDuration(int duration) { + this.mReleaseToRefreshToRefreshingScrollingDuration = duration; + } + + /** + * set {@link #mRefreshCompleteDelayDuration} in milliseconds + * + * @param duration + */ + public void setRefreshCompleteDelayDuration(int duration) { + this.mRefreshCompleteDelayDuration = duration; + } + + /** + * set {@link #mRefreshCompleteToDefaultScrollingDuration} in milliseconds + * + * @param duration + */ + public void setRefreshCompleteToDefaultScrollingDuration(int duration) { + this.mRefreshCompleteToDefaultScrollingDuration = duration; + } + + /** + * set {@link #mDefaultToRefreshingScrollingDuration} in milliseconds + * + * @param duration + */ + public void setDefaultToRefreshingScrollingDuration(int duration) { + this.mDefaultToRefreshingScrollingDuration = duration; + } + + /** + * set {@link @mSwipingToLoadMoreToDefaultScrollingDuration} in milliseconds + * + * @param duration + */ + public void setSwipingToLoadMoreToDefaultScrollingDuration(int duration) { + this.mSwipingToLoadMoreToDefaultScrollingDuration = duration; + } + + /** + * set {@link #mReleaseToLoadMoreToLoadingMoreScrollingDuration} in milliseconds + * + * @param duration + */ + public void setReleaseToLoadingMoreScrollingDuration(int duration) { + this.mReleaseToLoadMoreToLoadingMoreScrollingDuration = duration; + } + + /** + * set {@link #mLoadMoreCompleteDelayDuration} in milliseconds + * + * @param duration + */ + public void setLoadMoreCompleteDelayDuration(int duration) { + this.mLoadMoreCompleteDelayDuration = duration; + } + + /** + * set {@link #mLoadMoreCompleteToDefaultScrollingDuration} in milliseconds + * + * @param duration + */ + public void setLoadMoreCompleteToDefaultScrollingDuration(int duration) { + this.mLoadMoreCompleteToDefaultScrollingDuration = duration; + } + + /** + * set {@link #mDefaultToLoadingMoreScrollingDuration} in milliseconds + * + * @param duration + */ + public void setDefaultToLoadingMoreScrollingDuration(int duration) { + this.mDefaultToLoadingMoreScrollingDuration = duration; + } + + /** + * set an {@link OnRefreshListener} to listening refresh event + * + * @param listener + */ + public void setOnRefreshListener(OnRefreshListener listener) { + this.mRefreshListener = listener; + } + + /** + * set an {@link OnLoadMoreListener} to listening load more event + * + * @param listener + */ + public void setOnLoadMoreListener(OnLoadMoreListener listener) { + this.mLoadMoreListener = listener; + } + + /** + * auto refresh or cancel + * + * @param refreshing + */ + public void setRefreshing(boolean refreshing) { + if (!isRefreshEnabled() || mHeaderView == null) { + return; + } + this.mAutoLoading = refreshing; + if (refreshing) { + if (STATUS.isStatusDefault(mStatus)) { + setStatus(STATUS.STATUS_SWIPING_TO_REFRESH); + scrollDefaultToRefreshing(); + } + } else { + if (STATUS.isRefreshing(mStatus)) { + mRefreshCallback.onComplete(); + postDelayed(new Runnable() { + @Override + public void run() { + scrollRefreshingToDefault(); + } + }, mRefreshCompleteDelayDuration); + } + } + } + + /** + * auto loading more or cancel + * + * @param loadingMore + */ + public void setLoadingMore(boolean loadingMore) { + if (!isLoadMoreEnabled() || mFooterView == null) { + return; + } + this.mAutoLoading = loadingMore; + if (loadingMore) { + if (STATUS.isStatusDefault(mStatus)) { + setStatus(STATUS.STATUS_SWIPING_TO_LOAD_MORE); + scrollDefaultToLoadingMore(); + } + } else { + if (STATUS.isLoadingMore(mStatus)) { + mLoadMoreCallback.onComplete(); + postDelayed(new Runnable() { + @Override + public void run() { + scrollLoadingMoreToDefault(); + } + }, mLoadMoreCompleteDelayDuration); + } + } + } + + /** + * copy from {@link android.support.v4.widget.SwipeRefreshLayout#canChildScrollUp()} + * + * @return Whether it is possible for the child view of this layout to + * scroll left. Override this if the child view is a custom view. + */ + protected boolean canChildScrollLeft() { + if (android.os.Build.VERSION.SDK_INT < 14) { + if (mTargetView instanceof AbsListView) { + final AbsListView absListView = (AbsListView) mTargetView; + return absListView.getChildCount() > 0 + && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) + .getLeft() < absListView.getPaddingLeft()); + } else if (mTargetView instanceof ViewPager) { + ViewPager pager = (ViewPager) mTargetView; + return pager.getCurrentItem() > 0; + } else { + return ViewCompat.canScrollHorizontally(mTargetView, -1) || mTargetView.getScrollX() > 0; + } + } else { + if (mTargetView instanceof ViewPager) { + ViewPager pager = (ViewPager) mTargetView; + return pager.getCurrentItem() > 0; + } + return ViewCompat.canScrollHorizontally(mTargetView, -1); + } + } + + /** + * Whether it is possible for the child view of this layout to + * scroll right. Override this if the child view is a custom view. + * + * @return + */ + protected boolean canChildScrollRight() { + if (android.os.Build.VERSION.SDK_INT < 14) { + if (mTargetView instanceof AbsListView) { + final AbsListView absListView = (AbsListView) mTargetView; + return absListView.getChildCount() > 0 + && (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1 + || absListView.getChildAt(absListView.getChildCount() - 1).getRight() > absListView.getPaddingRight()); + } else if (mTargetView instanceof ViewPager) { + ViewPager pager = (ViewPager) mTargetView; + return pager.getCurrentItem() < pager.getAdapter().getCount() - 1; + } else { + return ViewCompat.canScrollHorizontally(mTargetView, 1) || mTargetView.getScrollX() < 0; + } + } else { + if (mTargetView instanceof ViewPager) { + ViewPager pager = (ViewPager) mTargetView; + return pager.getCurrentItem() < pager.getAdapter().getCount() - 1; + } + return ViewCompat.canScrollHorizontally(mTargetView, 1); + } + } + + /** + * @see #onLayout(boolean, int, int, int, int) + */ + private void layoutChildren() { + final int width = getMeasuredWidth(); + final int height = getMeasuredHeight(); + + final int paddingLeft = getPaddingLeft(); + final int paddingTop = getPaddingTop(); + final int paddingRight = getPaddingRight(); + final int paddingBottom = getPaddingBottom(); + + if (mTargetView == null) { + return; + } + + // layout header + if (mHeaderView != null) { + final View headerView = mHeaderView; + MarginLayoutParams lp = (MarginLayoutParams) headerView.getLayoutParams(); + final int headerLeft; + final int headerTop = paddingTop + lp.topMargin; + switch (mStyle) { + case STYLE.CLASSIC: + // classic + headerLeft = paddingLeft + lp.leftMargin - mHeaderWidth + mHeaderOffset; + break; + case STYLE.ABOVE: + // classic + headerLeft = paddingLeft + lp.leftMargin - mHeaderWidth + mHeaderOffset; + break; + case STYLE.BLEW: + // blew + headerLeft = paddingLeft + lp.leftMargin; + break; + case STYLE.SCALE: + // scale + headerLeft = paddingLeft + lp.leftMargin - mHeaderWidth / 2 + mHeaderOffset / 2; + break; + default: + // classic + headerLeft = paddingLeft + lp.leftMargin - mHeaderWidth + mHeaderOffset; + break; + } + final int headerRight = headerLeft + headerView.getMeasuredWidth(); + final int headerBottom = headerTop + headerView.getMeasuredHeight(); + headerView.layout(headerLeft, headerTop, headerRight, headerBottom); + } + + + // layout target + if (mTargetView != null) { + final View targetView = mTargetView; + MarginLayoutParams lp = (MarginLayoutParams) targetView.getLayoutParams(); + final int targetLeft; + final int targetTop = paddingTop + lp.topMargin; + + switch (mStyle) { + case STYLE.CLASSIC: + // classic + targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; + break; + case STYLE.ABOVE: + // above + targetLeft = paddingLeft + lp.leftMargin; + break; + case STYLE.BLEW: + // classic + targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; + break; + case STYLE.SCALE: + // classic + targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; + break; + default: + // classic + targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; + break; + } + final int targetRight = targetLeft + targetView.getMeasuredWidth(); + final int targetBottom = targetTop + targetView.getMeasuredHeight(); + targetView.layout(targetLeft, targetTop, targetRight, targetBottom); + } + + // layout footer + if (mFooterView != null) { + final View footerView = mFooterView; + MarginLayoutParams lp = (MarginLayoutParams) footerView.getLayoutParams(); + final int footerRight; + final int footerTop = paddingTop + lp.topMargin; + switch (mStyle) { + case STYLE.CLASSIC: + // classic + footerRight = width - paddingRight - lp.rightMargin + mFooterWidth + mFooterOffset; + break; + case STYLE.ABOVE: + // classic + footerRight = width - paddingRight - lp.rightMargin + mFooterWidth + mFooterOffset; + break; + case STYLE.BLEW: + // blew + footerRight = width - paddingRight - lp.rightMargin; + break; + case STYLE.SCALE: + // scale + footerRight = width - paddingRight - lp.rightMargin + mFooterWidth / 2 + mFooterOffset / 2; + break; + default: + // classic + footerRight = width - paddingRight - lp.rightMargin + mFooterWidth + mFooterOffset; + break; + } + final int footerLeft = footerRight - footerView.getMeasuredWidth(); + final int footerBottom = footerTop + footerView.getMeasuredHeight(); + + footerView.layout(footerLeft, footerTop, footerRight, footerBottom); + } + + if (mStyle == STYLE.CLASSIC + || mStyle == STYLE.ABOVE) { + if (mHeaderView != null) { + mHeaderView.bringToFront(); + } + if (mFooterView != null) { + mFooterView.bringToFront(); + } + } else if (mStyle == STYLE.BLEW || mStyle == STYLE.SCALE) { + if (mTargetView != null) { + mTargetView.bringToFront(); + } + } + } + + private void fixCurrentStatusLayout() { + if (STATUS.isRefreshing(mStatus)) { + mTargetOffset = (int) (mRefreshTriggerOffset + 0.5f); + mHeaderOffset = mTargetOffset; + mFooterOffset = 0; + layoutChildren(); + invalidate(); + } else if (STATUS.isStatusDefault(mStatus)) { + mTargetOffset = 0; + mHeaderOffset = 0; + mFooterOffset = 0; + layoutChildren(); + invalidate(); + } else if (STATUS.isLoadingMore(mStatus)) { + mTargetOffset = -(int) (mLoadMoreTriggerOffset + 0.5f); + mHeaderOffset = 0; + mFooterOffset = mTargetOffset; + layoutChildren(); + invalidate(); + } + } + + /** + * scrolling by physical touch with your fingers + * + * @param xDiff + */ + private void fingerScroll(final float xDiff) { + float ratio = mDragRatio; + float xScrolled = xDiff * ratio; + + // make sure (targetOffset>0 -> targetOffset=0 -> default status) + // or (targetOffset<0 -> targetOffset=0 -> default status) + // forbidden fling (targetOffset>0 -> targetOffset=0 ->targetOffset<0 -> default status) + // or (targetOffset<0 -> targetOffset=0 ->targetOffset>0 -> default status) + // I am so smart :) + + float tmpTargetOffset = xScrolled + mTargetOffset; + if ((tmpTargetOffset > 0 && mTargetOffset < 0) + || (tmpTargetOffset < 0 && mTargetOffset > 0)) { + xScrolled = -mTargetOffset; + } + + + if (mRefreshFinalDragOffset >= mRefreshTriggerOffset && tmpTargetOffset > mRefreshFinalDragOffset) { + xScrolled = mRefreshFinalDragOffset - mTargetOffset; + } else if (mLoadMoreFinalDragOffset >= mLoadMoreTriggerOffset && -tmpTargetOffset > mLoadMoreFinalDragOffset) { + xScrolled = -mLoadMoreFinalDragOffset - mTargetOffset; + } + + if (STATUS.isRefreshStatus(mStatus)) { + mRefreshCallback.onMove(mTargetOffset, false, false); + } else if (STATUS.isLoadMoreStatus(mStatus)) { + mLoadMoreCallback.onMove(mTargetOffset, false, false); + } + updateScroll(xScrolled); + } + + private void autoScroll(final float xScrolled) { + + if (STATUS.isSwipingToRefresh(mStatus)) { + mRefreshCallback.onMove(mTargetOffset, false, true); + } else if (STATUS.isReleaseToRefresh(mStatus)) { + mRefreshCallback.onMove(mTargetOffset, false, true); + } else if (STATUS.isRefreshing(mStatus)) { + mRefreshCallback.onMove(mTargetOffset, true, true); + } else if (STATUS.isSwipingToLoadMore(mStatus)) { + mLoadMoreCallback.onMove(mTargetOffset, false, true); + } else if (STATUS.isReleaseToLoadMore(mStatus)) { + mLoadMoreCallback.onMove(mTargetOffset, false, true); + } else if (STATUS.isLoadingMore(mStatus)) { + mLoadMoreCallback.onMove(mTargetOffset, true, true); + } + updateScroll(xScrolled); + } + + /** + * Process the scrolling(auto or physical) and append the diff values to mTargetOffset + * I think it's the most busy and core method. :) a ha ha ha ha... + * + * @param xScrolled + */ + private void updateScroll(final float xScrolled) { + if (xScrolled == 0) { + return; + } + mTargetOffset += xScrolled; + + if (STATUS.isRefreshStatus(mStatus)) { + mHeaderOffset = mTargetOffset; + mFooterOffset = 0; + } else if (STATUS.isLoadMoreStatus(mStatus)) { + mFooterOffset = mTargetOffset; + mHeaderOffset = 0; + } + + if (mDebug) { + Log.i(TAG, "mTargetOffset = " + mTargetOffset); + } + layoutChildren(); + invalidate(); + } + + /** + * on active finger up + */ + private void onActivePointerUp() { + if (STATUS.isSwipingToRefresh(mStatus)) { + // simply return + scrollSwipingToRefreshToDefault(); + + } else if (STATUS.isSwipingToLoadMore(mStatus)) { + // simply return + scrollSwipingToLoadMoreToDefault(); + + } else if (STATUS.isReleaseToRefresh(mStatus)) { + // return to header height and perform refresh + mRefreshCallback.onRelease(); + scrollReleaseToRefreshToRefreshing(); + + } else if (STATUS.isReleaseToLoadMore(mStatus)) { + // return to footer height and perform loadMore + mLoadMoreCallback.onRelease(); + scrollReleaseToLoadMoreToLoadingMore(); + + } + } + + /** + * on not active finger up + * + * @param ev + */ + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = MotionEventCompat.getActionIndex(ev); + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + } + } + + private void scrollDefaultToRefreshing() { + mAutoScroller.autoScroll((int) (mRefreshTriggerOffset + 0.5f), mDefaultToRefreshingScrollingDuration); + } + + private void scrollDefaultToLoadingMore() { + mAutoScroller.autoScroll(-(int) (mLoadMoreTriggerOffset + 0.5f), mDefaultToLoadingMoreScrollingDuration); + } + + private void scrollSwipingToRefreshToDefault() { + mAutoScroller.autoScroll(-mHeaderOffset, mSwipingToRefreshToDefaultScrollingDuration); + } + + private void scrollSwipingToLoadMoreToDefault() { + mAutoScroller.autoScroll(-mFooterOffset, mSwipingToLoadMoreToDefaultScrollingDuration); + } + + private void scrollReleaseToRefreshToRefreshing() { + mAutoScroller.autoScroll(mHeaderWidth - mHeaderOffset, mReleaseToRefreshToRefreshingScrollingDuration); + } + + private void scrollReleaseToLoadMoreToLoadingMore() { + mAutoScroller.autoScroll(-mFooterOffset - mFooterWidth, mReleaseToLoadMoreToLoadingMoreScrollingDuration); + } + + private void scrollRefreshingToDefault() { + mAutoScroller.autoScroll(-mHeaderOffset, mRefreshCompleteToDefaultScrollingDuration); + } + + private void scrollLoadingMoreToDefault() { + mAutoScroller.autoScroll(-mFooterOffset, mLoadMoreCompleteToDefaultScrollingDuration); + } + + /** + * invoke when {@link AutoScroller#finish()} is automatic + */ + private void autoScrollFinished() { + int mLastStatus = mStatus; + + if (STATUS.isReleaseToRefresh(mStatus)) { + setStatus(STATUS.STATUS_REFRESHING); + fixCurrentStatusLayout(); + mRefreshCallback.onRefresh(); + + } else if (STATUS.isRefreshing(mStatus)) { + setStatus(STATUS.STATUS_DEFAULT); + fixCurrentStatusLayout(); + mRefreshCallback.onReset(); + + } else if (STATUS.isSwipingToRefresh(mStatus)) { + if (mAutoLoading) { + mAutoLoading = false; + setStatus(STATUS.STATUS_REFRESHING); + fixCurrentStatusLayout(); + mRefreshCallback.onRefresh(); + } else { + setStatus(STATUS.STATUS_DEFAULT); + fixCurrentStatusLayout(); + mRefreshCallback.onReset(); + } + } else if (STATUS.isStatusDefault(mStatus)) { + + } else if (STATUS.isSwipingToLoadMore(mStatus)) { + if (mAutoLoading) { + mAutoLoading = false; + setStatus(STATUS.STATUS_LOADING_MORE); + fixCurrentStatusLayout(); + mLoadMoreCallback.onLoadMore(); + } else { + setStatus(STATUS.STATUS_DEFAULT); + fixCurrentStatusLayout(); + mLoadMoreCallback.onReset(); + } + } else if (STATUS.isLoadingMore(mStatus)) { + setStatus(STATUS.STATUS_DEFAULT); + fixCurrentStatusLayout(); + mLoadMoreCallback.onReset(); + } else if (STATUS.isReleaseToLoadMore(mStatus)) { + setStatus(STATUS.STATUS_LOADING_MORE); + fixCurrentStatusLayout(); + mLoadMoreCallback.onLoadMore(); + } else { + throw new IllegalStateException("illegal state: " + STATUS.getStatus(mStatus)); + } + + if (mDebug) { + Log.i(TAG, STATUS.getStatus(mLastStatus) + " -> " + STATUS.getStatus(mStatus)); + } + } + + /** + * check if it can refresh + * + * @return + */ + private boolean onCheckCanRefresh() { + + return mRefreshEnabled && !canChildScrollLeft() && mHasHeaderView && mRefreshTriggerOffset > 0; + } + + /** + * check if it can load more + * + * @return + */ + private boolean onCheckCanLoadMore() { + + return mLoadMoreEnabled && !canChildScrollRight() && mHasFooterView && mLoadMoreTriggerOffset > 0; + } + + private float getMotionEventY(MotionEvent event, int activePointerId) { + final int index = event.findPointerIndex(activePointerId); + if (index < 0) { + return INVALID_COORDINATE; + } + return event.getY(index); + } + + private float getMotionEventX(MotionEvent event, int activePointId) { + final int index = event.findPointerIndex(activePointId); + if (index < 0) { + return INVALID_COORDINATE; + } + return event.getX(index); + } + + RefreshCallback mRefreshCallback = new RefreshCallback() { + @Override + public void onPrepare() { + if (mHeaderView != null && mHeaderView instanceof SwipeTrigger && STATUS.isStatusDefault(mStatus)) { + mHeaderView.setVisibility(VISIBLE); + ((SwipeTrigger) mHeaderView).onPrepare(); + } + } + + @Override + public void onMove(int x, boolean isComplete, boolean automatic) { + if (mHeaderView != null && mHeaderView instanceof SwipeTrigger && STATUS.isRefreshStatus(mStatus)) { + if (mHeaderView.getVisibility() != VISIBLE) { + mHeaderView.setVisibility(VISIBLE); + } + ((SwipeTrigger) mHeaderView).onMove(x, isComplete, automatic); + } + } + + @Override + public void onRelease() { + if (mHeaderView != null && mHeaderView instanceof SwipeTrigger && STATUS.isReleaseToRefresh(mStatus)) { + ((SwipeTrigger) mHeaderView).onRelease(); + } + } + + @Override + public void onRefresh() { + if (mHeaderView != null && STATUS.isRefreshing(mStatus)) { + if (mHeaderView instanceof SwipeRefreshTrigger) { + ((SwipeRefreshTrigger) mHeaderView).onRefresh(); + } + if (mRefreshListener != null) { + mRefreshListener.onRefresh(); + } + } + } + + @Override + public void onComplete() { + if (mHeaderView != null && mHeaderView instanceof SwipeTrigger) { + ((SwipeTrigger) mHeaderView).onComplete(); + } + } + + @Override + public void onReset() { + if (mHeaderView != null && mHeaderView instanceof SwipeTrigger && STATUS.isStatusDefault(mStatus)) { + ((SwipeTrigger) mHeaderView).onReset(); + mHeaderView.setVisibility(GONE); + } + } + }; + + LoadMoreCallback mLoadMoreCallback = new LoadMoreCallback() { + + @Override + public void onPrepare() { + if (mFooterView != null && mFooterView instanceof SwipeTrigger && STATUS.isStatusDefault(mStatus)) { + mFooterView.setVisibility(VISIBLE); + ((SwipeTrigger) mFooterView).onPrepare(); + } + } + + @Override + public void onMove(int x, boolean isComplete, boolean automatic) { + if (mFooterView != null && mFooterView instanceof SwipeTrigger && STATUS.isLoadMoreStatus(mStatus)) { + if (mFooterView.getVisibility() != VISIBLE) { + mFooterView.setVisibility(VISIBLE); + } + ((SwipeTrigger) mFooterView).onMove(x, isComplete, automatic); + } + } + + @Override + public void onRelease() { + if (mFooterView != null && mFooterView instanceof SwipeTrigger && STATUS.isReleaseToLoadMore(mStatus)) { + ((SwipeTrigger) mFooterView).onRelease(); + } + } + + @Override + public void onLoadMore() { + if (mFooterView != null && STATUS.isLoadingMore(mStatus)) { + if (mFooterView instanceof SwipeLoadMoreTrigger) { + ((SwipeLoadMoreTrigger) mFooterView).onLoadMore(); + } + if (mLoadMoreListener != null) { + mLoadMoreListener.onLoadMore(); + } + } + } + + @Override + public void onComplete() { + if (mFooterView != null && mFooterView instanceof SwipeTrigger) { + ((SwipeTrigger) mFooterView).onComplete(); + } + } + + @Override + public void onReset() { + if (mFooterView != null && mFooterView instanceof SwipeTrigger && STATUS.isStatusDefault(mStatus)) { + ((SwipeTrigger) mFooterView).onReset(); + mFooterView.setVisibility(GONE); + } + } + }; + + /** + * refresh event callback + */ + abstract class RefreshCallback implements SwipeTrigger, SwipeRefreshTrigger { + } + + /** + * load more event callback + */ + abstract class LoadMoreCallback implements SwipeTrigger, SwipeLoadMoreTrigger { + } + + private class AutoScroller implements Runnable { + + private Scroller mScroller; + + private int mmLastX; + + private boolean mRunning = false; + + private boolean mAbort = false; + + public AutoScroller() { + mScroller = new Scroller(getContext()); + } + + @Override + public void run() { + boolean finish = !mScroller.computeScrollOffset() || mScroller.isFinished(); + int currx = mScroller.getCurrX(); + int xDiff = currx - mmLastX; + if (finish) { + finish(); + } else { + mmLastX = currx; + HorSwipeToLoadLayout.this.autoScroll(xDiff); + post(this); + } + } + + /** + * remove the post callbacks and reset default values + */ + private void finish() { + mmLastX = 0; + mRunning = false; + removeCallbacks(this); + // if abort by user, don't call + if (!mAbort) { + autoScrollFinished(); + } + } + + /** + * abort scroll if it is scrolling + */ + public void abortIfRunning() { + if (mRunning) { + if (!mScroller.isFinished()) { + mAbort = true; + mScroller.forceFinished(true); + } + finish(); + mAbort = false; + } + } + + /** + * The param xScrolled here isn't final pos of y. + * It's just like the yScrolled param in the + * {@link #updateScroll(float yScrolled)} + * + * @param xScrolled + * @param duration + */ + private void autoScroll(int xScrolled, int duration) { + removeCallbacks(this); + mmLastX = 0; + if (!mScroller.isFinished()) { + mScroller.forceFinished(true); + } + mScroller.startScroll(0, 0, xScrolled, 0, duration); + post(this); + mRunning = true; + } + } + + /** + * Set the current status for better control + * + * @param status + */ + private void setStatus(int status) { + mStatus = status; + if (mDebug) { + STATUS.printStatus(status); + } + } + + /** + * an inner util class. + * enum of status + */ + private final static class STATUS { + private static final int STATUS_REFRESH_RETURNING = -4; + private static final int STATUS_REFRESHING = -3; + private static final int STATUS_RELEASE_TO_REFRESH = -2; + private static final int STATUS_SWIPING_TO_REFRESH = -1; + private static final int STATUS_DEFAULT = 0; + private static final int STATUS_SWIPING_TO_LOAD_MORE = 1; + private static final int STATUS_RELEASE_TO_LOAD_MORE = 2; + private static final int STATUS_LOADING_MORE = 3; + private static final int STATUS_LOAD_MORE_RETURNING = 4; + + private static boolean isRefreshing(final int status) { + return status == STATUS.STATUS_REFRESHING; + } + + private static boolean isLoadingMore(final int status) { + return status == STATUS.STATUS_LOADING_MORE; + } + + private static boolean isReleaseToRefresh(final int status) { + return status == STATUS.STATUS_RELEASE_TO_REFRESH; + } + + private static boolean isReleaseToLoadMore(final int status) { + return status == STATUS.STATUS_RELEASE_TO_LOAD_MORE; + } + + private static boolean isSwipingToRefresh(final int status) { + return status == STATUS.STATUS_SWIPING_TO_REFRESH; + } + + private static boolean isSwipingToLoadMore(final int status) { + return status == STATUS.STATUS_SWIPING_TO_LOAD_MORE; + } + + private static boolean isRefreshStatus(final int status) { + return status < STATUS.STATUS_DEFAULT; + } + + public static boolean isLoadMoreStatus(final int status) { + return status > STATUS.STATUS_DEFAULT; + } + + private static boolean isStatusDefault(final int status) { + return status == STATUS.STATUS_DEFAULT; + } + + private static String getStatus(int status) { + final String statusInfo; + switch (status) { + case STATUS_REFRESH_RETURNING: + statusInfo = "status_refresh_returning"; + break; + case STATUS_REFRESHING: + statusInfo = "status_refreshing"; + break; + case STATUS_RELEASE_TO_REFRESH: + statusInfo = "status_release_to_refresh"; + break; + case STATUS_SWIPING_TO_REFRESH: + statusInfo = "status_swiping_to_refresh"; + break; + case STATUS_DEFAULT: + statusInfo = "status_default"; + break; + case STATUS_SWIPING_TO_LOAD_MORE: + statusInfo = "status_swiping_to_load_more"; + break; + case STATUS_RELEASE_TO_LOAD_MORE: + statusInfo = "status_release_to_load_more"; + break; + case STATUS_LOADING_MORE: + statusInfo = "status_loading_more"; + break; + case STATUS_LOAD_MORE_RETURNING: + statusInfo = "status_load_more_returning"; + break; + default: + statusInfo = "status_illegal!"; + break; + } + return statusInfo; + } + + private static void printStatus(int status) { + Log.i(TAG, "printStatus:" + getStatus(status)); + } + } +} diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index bf0a634..3ce545f 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -28,4 +28,5 @@ - \ No newline at end of file + + \ No newline at end of file From dcfc74430eb3a36059067dbeefdc8e61943ba3fb Mon Sep 17 00:00:00 2001 From: wxj <986798656@qq.com> Date: Sun, 22 Jan 2017 17:53:23 +0800 Subject: [PATCH 2/6] loading --- .../com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java | 5 +++++ library/src/main/res/values/attrs.xml | 1 + 2 files changed, 6 insertions(+) diff --git a/library/src/main/java/com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java b/library/src/main/java/com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java index 6802d40..6bbd878 100644 --- a/library/src/main/java/com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java +++ b/library/src/main/java/com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java @@ -235,6 +235,11 @@ public class SwipeToLoadLayout extends ViewGroup { */ private int mDefaultToLoadingMoreScrollingDuration = DEFAULT_DEFAULT_TO_LOADING_MORE_SCROLLING_DURATION; + /** + *true : the target is vertically scroll + */ + private boolean mIsVertically = true; + /** * the style enum */ diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 3ce545f..ec603d5 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -27,6 +27,7 @@ + \ No newline at end of file From 837a5a12e444ddbe26b447948f0eb233158ce4a1 Mon Sep 17 00:00:00 2001 From: wxj <986798656@qq.com> Date: Tue, 7 Feb 2017 14:32:42 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8F=B3=E6=8B=89?= =?UTF-8?q?=E5=88=B7=E6=96=B0=EF=BC=8C=20=E5=B7=A6=E6=8B=89=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=9B=B4=E5=A4=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HorSwipeToLoadLayout.java | 1583 ----------------- .../swipetoloadlayout/SwipeToLoadLayout.java | 328 +++- library/src/main/res/values/attrs.xml | 2 +- 3 files changed, 271 insertions(+), 1642 deletions(-) delete mode 100644 library/src/main/java/com/aspsine/swipetoloadlayout/HorSwipeToLoadLayout.java diff --git a/library/src/main/java/com/aspsine/swipetoloadlayout/HorSwipeToLoadLayout.java b/library/src/main/java/com/aspsine/swipetoloadlayout/HorSwipeToLoadLayout.java deleted file mode 100644 index 3233a20..0000000 --- a/library/src/main/java/com/aspsine/swipetoloadlayout/HorSwipeToLoadLayout.java +++ /dev/null @@ -1,1583 +0,0 @@ -package com.aspsine.swipetoloadlayout; - -import android.content.Context; -import android.content.res.TypedArray; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.Scroller; - -public class HorSwipeToLoadLayout extends ViewGroup { - private static final String TAG = HorSwipeToLoadLayout.class.getSimpleName(); - private static final int DEFAULT_SWIPING_TO_REFRESH_TO_DEFAULT_SCROLLING_DURATION = 200; - private static final int DEFAULT_RELEASE_TO_REFRESHING_SCROLLING_DURATION = 200; - private static final int DEFAULT_REFRESH_COMPLETE_DELAY_DURATION = 300; - private static final int DEFAULT_REFRESH_COMPLETE_TO_DEFAULT_SCROLLING_DURATION = 500; - private static final int DEFAULT_DEFAULT_TO_REFRESHING_SCROLLING_DURATION = 500; - private static final int DEFAULT_SWIPING_TO_LOAD_MORE_TO_DEFAULT_SCROLLING_DURATION = 200; - private static final int DEFAULT_RELEASE_TO_LOADING_MORE_SCROLLING_DURATION = 200; - private static final int DEFAULT_LOAD_MORE_COMPLETE_DELAY_DURATION = 300; - private static final int DEFAULT_LOAD_MORE_COMPLETE_TO_DEFAULT_SCROLLING_DURATION = 300; - private static final int DEFAULT_DEFAULT_TO_LOADING_MORE_SCROLLING_DURATION = 300; - private static final float DEFAULT_DRAG_RATIO = 0.5F; - private static final int INVALID_POINTER = -1; - private static final int INVALID_COORDINATE = -1; - private HorSwipeToLoadLayout.AutoScroller mAutoScroller; - private OnRefreshListener mRefreshListener; - private OnLoadMoreListener mLoadMoreListener; - private View mHeaderView; - private View mTargetView; - private View mFooterView; - private int mHeaderWidth; - private int mFooterWidth; - private boolean mHasHeaderView; - private boolean mHasFooterView; - private boolean mDebug; - private float mDragRatio = DEFAULT_DRAG_RATIO; - private boolean mAutoLoading; - private final int mTouchSlop; - private int mStatus = STATUS.STATUS_DEFAULT; - private int mHeaderOffset; - private int mTargetOffset; - private int mFooterOffset; - private float mInitDownY; - private float mInitDownX; - private float mLastY; - private float mLastX; - private int mActivePointerId; - private boolean mRefreshEnabled = true; - private boolean mLoadMoreEnabled = true; - private int mStyle = STYLE.CLASSIC; - private float mRefreshTriggerOffset; - private float mLoadMoreTriggerOffset; - private float mRefreshFinalDragOffset; - private float mLoadMoreFinalDragOffset; - /** - * ATTRIBUTE: - * Scrolling duration swiping to refresh -> default - */ - private int mSwipingToRefreshToDefaultScrollingDuration = DEFAULT_SWIPING_TO_REFRESH_TO_DEFAULT_SCROLLING_DURATION; - - /** - * ATTRIBUTE: - * Scrolling duration status release to refresh -> refreshing - */ - private int mReleaseToRefreshToRefreshingScrollingDuration = DEFAULT_RELEASE_TO_REFRESHING_SCROLLING_DURATION; - - /** - * ATTRIBUTE: - * Refresh complete delay duration - */ - private int mRefreshCompleteDelayDuration = DEFAULT_REFRESH_COMPLETE_DELAY_DURATION; - - /** - * ATTRIBUTE: - * Scrolling duration status refresh complete -> default - * {@link #setRefreshing(boolean)} false - */ - private int mRefreshCompleteToDefaultScrollingDuration = DEFAULT_REFRESH_COMPLETE_TO_DEFAULT_SCROLLING_DURATION; - - /** - * ATTRIBUTE: - * Scrolling duration status default -> refreshing, mainly for auto refresh - * {@link #setRefreshing(boolean)} true - */ - private int mDefaultToRefreshingScrollingDuration = DEFAULT_DEFAULT_TO_REFRESHING_SCROLLING_DURATION; - - /** - * ATTRIBUTE: - * Scrolling duration status release to loading more -> loading more - */ - private int mReleaseToLoadMoreToLoadingMoreScrollingDuration = DEFAULT_RELEASE_TO_LOADING_MORE_SCROLLING_DURATION; - - - /** - * ATTRIBUTE: - * Load more complete delay duration - */ - private int mLoadMoreCompleteDelayDuration = DEFAULT_LOAD_MORE_COMPLETE_DELAY_DURATION; - - /** - * ATTRIBUTE: - * Scrolling duration status load more complete -> default - * {@link #setLoadingMore(boolean)} false - */ - private int mLoadMoreCompleteToDefaultScrollingDuration = DEFAULT_LOAD_MORE_COMPLETE_TO_DEFAULT_SCROLLING_DURATION; - - /** - * ATTRIBUTE: - * Scrolling duration swiping to load more -> default - */ - private int mSwipingToLoadMoreToDefaultScrollingDuration = DEFAULT_SWIPING_TO_LOAD_MORE_TO_DEFAULT_SCROLLING_DURATION; - - /** - * ATTRIBUTE: - * Scrolling duration status default -> loading more, mainly for auto load more - * {@link #setLoadingMore(boolean)} true - */ - private int mDefaultToLoadingMoreScrollingDuration = DEFAULT_DEFAULT_TO_LOADING_MORE_SCROLLING_DURATION; - - /** - * the style enum - */ - public static final class STYLE { - public static final int CLASSIC = 0; - public static final int ABOVE = 1; - public static final int BLEW = 2; - public static final int SCALE = 3; - } - - - public HorSwipeToLoadLayout(Context context) { - this(context, (AttributeSet) null); - } - - public HorSwipeToLoadLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public HorSwipeToLoadLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorSwipeToLoadLayout, defStyleAttr, 0); - - try { - int N = a.getIndexCount(); - - for (int i = 0; i < N; ++i) { - int attr = a.getIndex(i); - if (attr == R.styleable.HorSwipeToLoadLayout_refresh_enabled) { - this.setRefreshEnabled(a.getBoolean(attr, mRefreshEnabled)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_enabled) { - this.setLoadMoreEnabled(a.getBoolean(attr, mLoadMoreEnabled)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_swipe_style) { - this.setSwipeStyle(a.getInt(attr, mStyle)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_drag_ratio) { - this.setDragRatio(a.getFloat(attr, mDragRatio)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_refresh_final_drag_offset) { - this.setRefreshFinalDragOffset(a.getDimensionPixelOffset(attr, 0)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_final_drag_offset) { - this.setLoadMoreFinalDragOffset(a.getDimensionPixelOffset(attr, 0)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_refresh_trigger_offset) { - this.setRefreshTriggerOffset(a.getDimensionPixelOffset(attr, 0)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_trigger_offset) { - this.setLoadMoreTriggerOffset(a.getDimensionPixelOffset(attr, 0)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_swiping_to_refresh_to_default_scrolling_duration) { - this.setSwipingToRefreshToDefaultScrollingDuration(a.getInt(attr, mSwipingToRefreshToDefaultScrollingDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_release_to_refreshing_scrolling_duration) { - this.setReleaseToRefreshingScrollingDuration(a.getInt(attr, mReleaseToRefreshToRefreshingScrollingDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_refresh_complete_delay_duration) { - this.setRefreshCompleteDelayDuration(a.getInt(attr, mRefreshCompleteDelayDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_refresh_complete_to_default_scrolling_duration) { - this.setRefreshCompleteToDefaultScrollingDuration(a.getInt(attr, mRefreshCompleteToDefaultScrollingDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_default_to_refreshing_scrolling_duration) { - this.setDefaultToRefreshingScrollingDuration(a.getInt(attr, mDefaultToRefreshingScrollingDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_swiping_to_load_more_to_default_scrolling_duration) { - this.setSwipingToLoadMoreToDefaultScrollingDuration(a.getInt(attr, mSwipingToLoadMoreToDefaultScrollingDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_release_to_loading_more_scrolling_duration) { - this.setReleaseToLoadingMoreScrollingDuration(a.getInt(attr, mReleaseToLoadMoreToLoadingMoreScrollingDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_complete_delay_duration) { - this.setLoadMoreCompleteDelayDuration(a.getInt(attr, mLoadMoreCompleteDelayDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_load_more_complete_to_default_scrolling_duration) { - this.setLoadMoreCompleteToDefaultScrollingDuration(a.getInt(attr, mLoadMoreCompleteToDefaultScrollingDuration)); - } else if (attr == R.styleable.HorSwipeToLoadLayout_default_to_loading_more_scrolling_duration) { - this.setDefaultToLoadingMoreScrollingDuration(a.getInt(attr, mDefaultToLoadingMoreScrollingDuration)); - } - } - } finally { - a.recycle(); - } - - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - mAutoScroller = new HorSwipeToLoadLayout.AutoScroller(); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - final int childNum = getChildCount(); - if (childNum == 0) { - // no child return - return; - } else if (0 < childNum && childNum < 4) { - mHeaderView = findViewById(R.id.swipe_refresh_header); - mTargetView = findViewById(R.id.swipe_target); - mFooterView = findViewById(R.id.swipe_load_more_footer); - } else { - // more than three children: unsupported! - throw new IllegalStateException("Children num must equal or less than 3"); - } - if (mTargetView == null) { - return; - } - if (mHeaderView != null && mHeaderView instanceof SwipeTrigger) { - mHeaderView.setVisibility(GONE); - } - if (mFooterView != null && mFooterView instanceof SwipeTrigger) { - mFooterView.setVisibility(GONE); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - // header - if (mHeaderView != null) { - final View headerView = mHeaderView; - measureChildWithMargins(headerView, widthMeasureSpec, 0, heightMeasureSpec, 0); - MarginLayoutParams lp = ((MarginLayoutParams) headerView.getLayoutParams()); - mHeaderWidth = headerView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; - if (mRefreshTriggerOffset < mHeaderWidth) { - mRefreshTriggerOffset = mHeaderWidth; - } - } - // target - if (mTargetView != null) { - final View targetView = mTargetView; - measureChildWithMargins(targetView, widthMeasureSpec, 0, heightMeasureSpec, 0); - } - // footer - if (mFooterView != null) { - final View footerView = mFooterView; - measureChildWithMargins(footerView, widthMeasureSpec, 0, heightMeasureSpec, 0); - MarginLayoutParams lp = ((MarginLayoutParams) footerView.getLayoutParams()); - mFooterWidth = footerView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; - if (mLoadMoreTriggerOffset < mFooterWidth) { - mLoadMoreTriggerOffset = mFooterWidth; - } - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - layoutChildren(); - - mHasHeaderView = (mHeaderView != null); - mHasFooterView = (mFooterView != null); - } - - /** - * LayoutParams of RefreshLoadMoreLayout - */ - public static class LayoutParams extends MarginLayoutParams { - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(MarginLayoutParams source) { - super(source); - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - } - - @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - return new HorSwipeToLoadLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - } - - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return new HorSwipeToLoadLayout.LayoutParams(p); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new HorSwipeToLoadLayout.LayoutParams(getContext(), attrs); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - switch (action) { - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - // swipeToRefresh -> finger up -> finger down if the status is still swipeToRefresh - // in onInterceptTouchEvent ACTION_DOWN event will stop the scroller - // if the event pass to the child view while ACTION_MOVE(condition is false) - // in onInterceptTouchEvent ACTION_MOVE the ACTION_UP or ACTION_CANCEL will not be - // passed to onInterceptTouchEvent and onTouchEvent. Instead It will be passed to - // child view's onTouchEvent. So we must deal this situation in dispatchTouchEvent - onActivePointerUp(); - break; - } - return super.dispatchTouchEvent(ev); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - final int action = MotionEventCompat.getActionMasked(event); - switch (action) { - case MotionEvent.ACTION_DOWN: - - mActivePointerId = event.getPointerId(0); - mInitDownY = mLastY = getMotionEventY(event, mActivePointerId); - mInitDownX = mLastX = getMotionEventX(event, mActivePointerId); - - // if it isn't an ing status or default status - if (STATUS.isSwipingToRefresh(mStatus) || STATUS.isSwipingToLoadMore(mStatus) || - STATUS.isReleaseToRefresh(mStatus) || STATUS.isReleaseToLoadMore(mStatus)) { - // abort autoScrolling, not trigger the method #autoScrollFinished() - mAutoScroller.abortIfRunning(); - if (mDebug) { - Log.i(TAG, "Another finger down, abort auto scrolling, let the new finger handle"); - } - } - - if (STATUS.isSwipingToRefresh(mStatus) || STATUS.isReleaseToRefresh(mStatus) - || STATUS.isSwipingToLoadMore(mStatus) || STATUS.isReleaseToLoadMore(mStatus)) { - return true; - } - - // let children view handle the ACTION_DOWN; - - // 1. children consumed: - // if at least one of children onTouchEvent() ACTION_DOWN return true. - // ACTION_DOWN event will not return to SwipeToLoadLayout#onTouchEvent(). - // but the others action can be handled by SwipeToLoadLayout#onInterceptTouchEvent() - - // 2. children not consumed: - // if children onTouchEvent() ACTION_DOWN return false. - // ACTION_DOWN event will return to SwipeToLoadLayout's onTouchEvent(). - // SwipeToLoadLayout#onTouchEvent() ACTION_DOWN return true to consume the ACTION_DOWN event. - - // anyway: handle action down in onInterceptTouchEvent() to init is an good option - break; - case MotionEvent.ACTION_MOVE: - if (mActivePointerId == INVALID_POINTER) { - return false; - } - float y = getMotionEventY(event, mActivePointerId); - float x = getMotionEventX(event, mActivePointerId); - final float yInitDiff = y - mInitDownY; - final float xInitDiff = x - mInitDownX; - mLastY = y; - mLastX = x; - boolean moved = Math.abs(xInitDiff) > Math.abs(yInitDiff) - && Math.abs(xInitDiff) > mTouchSlop; - boolean triggerCondition = - // refresh trigger condition - (xInitDiff > 0 && moved && onCheckCanRefresh()) || - //load more trigger condition - (xInitDiff < 0 && moved && onCheckCanLoadMore()); - if (triggerCondition) { - // if the refresh's or load more's trigger condition is true, - // intercept the move action event and pass it to SwipeToLoadLayout#onTouchEvent() - return true; - } - break; - case MotionEvent.ACTION_POINTER_UP: { - onSecondaryPointerUp(event); - mInitDownY = mLastY = getMotionEventY(event, mActivePointerId); - mInitDownX = mLastX = getMotionEventX(event, mActivePointerId); - break; - } - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mActivePointerId = INVALID_POINTER; - break; - } - return super.onInterceptTouchEvent(event); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - final int action = MotionEventCompat.getActionMasked(event); - - switch (action) { - case MotionEvent.ACTION_DOWN: - mActivePointerId = event.getPointerId(0); - return true; - - case MotionEvent.ACTION_MOVE: - // take over the ACTION_MOVE event from SwipeToLoadLayout#onInterceptTouchEvent() - // if condition is true - final float y = getMotionEventY(event, mActivePointerId); - final float x = getMotionEventX(event, mActivePointerId); - - final float yDiff = y - mLastY; - final float xDiff = x - mLastX; - mLastY = y; - mLastX = x; - - if (Math.abs(yDiff) > Math.abs(xDiff) && Math.abs(yDiff) > mTouchSlop) { - return false; - } - - if (STATUS.isStatusDefault(mStatus)) { - if (xDiff > 0 && onCheckCanRefresh()) { - mRefreshCallback.onPrepare(); - setStatus(STATUS.STATUS_SWIPING_TO_REFRESH); - } else if (xDiff < 0 && onCheckCanLoadMore()) { - mLoadMoreCallback.onPrepare(); - setStatus(STATUS.STATUS_SWIPING_TO_LOAD_MORE); - } - } else if (STATUS.isRefreshStatus(mStatus)) { - if (mTargetOffset <= 0) { - setStatus(STATUS.STATUS_DEFAULT); - fixCurrentStatusLayout(); - return false; - } - } else if (STATUS.isLoadMoreStatus(mStatus)) { - if (mTargetOffset >= 0) { - setStatus(STATUS.STATUS_DEFAULT); - fixCurrentStatusLayout(); - return false; - } - } - - if (STATUS.isRefreshStatus(mStatus)) { - if (STATUS.isSwipingToRefresh(mStatus) || STATUS.isReleaseToRefresh(mStatus)) { - if (mTargetOffset >= mRefreshTriggerOffset) { - setStatus(STATUS.STATUS_RELEASE_TO_REFRESH); - } else { - setStatus(STATUS.STATUS_SWIPING_TO_REFRESH); - } - fingerScroll(xDiff); - } - } else if (STATUS.isLoadMoreStatus(mStatus)) { - if (STATUS.isSwipingToLoadMore(mStatus) || STATUS.isReleaseToLoadMore(mStatus)) { - if (-mTargetOffset >= mLoadMoreTriggerOffset) { - setStatus(STATUS.STATUS_RELEASE_TO_LOAD_MORE); - } else { - setStatus(STATUS.STATUS_SWIPING_TO_LOAD_MORE); - } - fingerScroll(xDiff); - } - } - return true; - - case MotionEvent.ACTION_POINTER_DOWN: { - final int pointerIndex = MotionEventCompat.getActionIndex(event); - final int pointerId = event.getPointerId(pointerIndex); - if (pointerId != INVALID_POINTER) { - mActivePointerId = pointerId; - } - mInitDownY = mLastY = getMotionEventY(event, mActivePointerId); - mInitDownX = mLastX = getMotionEventX(event, mActivePointerId); - break; - } - case MotionEvent.ACTION_POINTER_UP: { - onSecondaryPointerUp(event); - mInitDownY = mLastY = getMotionEventY(event, mActivePointerId); - mInitDownX = mLastX = getMotionEventX(event, mActivePointerId); - break; - } - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (mActivePointerId == INVALID_POINTER) { - return false; - } - mActivePointerId = INVALID_POINTER; - break; - default: - break; - } - return super.onTouchEvent(event); - } - - /** - * set debug mode(default value false) - * - * @param debug if true log on, false log off - */ - public void setDebug(boolean debug) { - this.mDebug = debug; - } - - /** - * is refresh function is enabled - * - * @return - */ - public boolean isRefreshEnabled() { - return mRefreshEnabled; - } - - /** - * switch refresh function on or off - * - * @param enable - */ - public void setRefreshEnabled(boolean enable) { - this.mRefreshEnabled = enable; - } - - /** - * is load more function is enabled - * - * @return - */ - public boolean isLoadMoreEnabled() { - return mLoadMoreEnabled; - } - - /** - * switch load more function on or off - * - * @param enable - */ - public void setLoadMoreEnabled(boolean enable) { - this.mLoadMoreEnabled = enable; - } - - /** - * is current status refreshing - * - * @return - */ - public boolean isRefreshing() { - return STATUS.isRefreshing(mStatus); - } - - /** - * is current status loading more - * - * @return - */ - public boolean isLoadingMore() { - return STATUS.isLoadingMore(mStatus); - } - - /** - * set refresh header view, the view must at lease be an implement of {@code SwipeRefreshTrigger}. - * the view can also implement {@code SwipeTrigger} for more extension functions - * - * @param view - */ - public void setRefreshHeaderView(View view) { - if (view instanceof SwipeRefreshTrigger) { - if (mHeaderView != null && mHeaderView != view) { - removeView(mHeaderView); - } - if (mHeaderView != view) { - this.mHeaderView = view; - addView(view); - } - } else { - Log.e(TAG, "Refresh header view must be an implement of SwipeRefreshTrigger"); - } - } - - /** - * set load more footer view, the view must at least be an implement of SwipeLoadTrigger - * the view can also implement {@code SwipeTrigger} for more extension functions - * - * @param view - */ - public void setLoadMoreFooterView(View view) { - if (view instanceof SwipeLoadMoreTrigger) { - if (mFooterView != null && mFooterView != view) { - removeView(mFooterView); - } - if (mFooterView != view) { - this.mFooterView = view; - addView(mFooterView); - } - } else { - Log.e(TAG, "Load more footer view must be an implement of SwipeLoadTrigger"); - } - } - - /** - * set the style of the refresh header - * - * @param style - */ - public void setSwipeStyle(int style) { - this.mStyle = style; - requestLayout(); - } - - /** - * set how hard to drag. bigger easier, smaller harder; - * - * @param dragRatio default value is {@link #DEFAULT_DRAG_RATIO} - */ - public void setDragRatio(float dragRatio) { - this.mDragRatio = dragRatio; - } - - /** - * set the value of {@link #mRefreshTriggerOffset}. - * Default value is the refresh header view height {@link #mHeaderWidth}

- * If the offset you set is smaller than {@link #mHeaderWidth} or not set, - * using {@link #mHeaderWidth} as default value - * - * @param offset - */ - public void setRefreshTriggerOffset(int offset) { - mRefreshTriggerOffset = offset; - } - - /** - * set the value of {@link #mLoadMoreTriggerOffset}. - * Default value is the load more footer view height {@link #mFooterWidth}

- * If the offset you set is smaller than {@link #mFooterWidth} or not set, - * using {@link #mFooterWidth} as default value - * - * @param offset - */ - public void setLoadMoreTriggerOffset(int offset) { - mLoadMoreTriggerOffset = offset; - } - - /** - * Set the final offset you can swipe to refresh.
- * If the offset you set is 0(default value) or smaller than {@link #mRefreshTriggerOffset} - * there no final offset - * - * @param offset - */ - public void setRefreshFinalDragOffset(int offset) { - mRefreshFinalDragOffset = offset; - } - - /** - * Set the final offset you can swipe to load more.
- * If the offset you set is 0(default value) or smaller than {@link #mLoadMoreTriggerOffset}, - * there no final offset - * - * @param offset - */ - public void setLoadMoreFinalDragOffset(int offset) { - mLoadMoreFinalDragOffset = offset; - } - - /** - * set {@link #mSwipingToRefreshToDefaultScrollingDuration} in milliseconds - * - * @param duration - */ - public void setSwipingToRefreshToDefaultScrollingDuration(int duration) { - this.mSwipingToRefreshToDefaultScrollingDuration = duration; - } - - /** - * set {@link #mReleaseToRefreshToRefreshingScrollingDuration} in milliseconds - * - * @param duration - */ - public void setReleaseToRefreshingScrollingDuration(int duration) { - this.mReleaseToRefreshToRefreshingScrollingDuration = duration; - } - - /** - * set {@link #mRefreshCompleteDelayDuration} in milliseconds - * - * @param duration - */ - public void setRefreshCompleteDelayDuration(int duration) { - this.mRefreshCompleteDelayDuration = duration; - } - - /** - * set {@link #mRefreshCompleteToDefaultScrollingDuration} in milliseconds - * - * @param duration - */ - public void setRefreshCompleteToDefaultScrollingDuration(int duration) { - this.mRefreshCompleteToDefaultScrollingDuration = duration; - } - - /** - * set {@link #mDefaultToRefreshingScrollingDuration} in milliseconds - * - * @param duration - */ - public void setDefaultToRefreshingScrollingDuration(int duration) { - this.mDefaultToRefreshingScrollingDuration = duration; - } - - /** - * set {@link @mSwipingToLoadMoreToDefaultScrollingDuration} in milliseconds - * - * @param duration - */ - public void setSwipingToLoadMoreToDefaultScrollingDuration(int duration) { - this.mSwipingToLoadMoreToDefaultScrollingDuration = duration; - } - - /** - * set {@link #mReleaseToLoadMoreToLoadingMoreScrollingDuration} in milliseconds - * - * @param duration - */ - public void setReleaseToLoadingMoreScrollingDuration(int duration) { - this.mReleaseToLoadMoreToLoadingMoreScrollingDuration = duration; - } - - /** - * set {@link #mLoadMoreCompleteDelayDuration} in milliseconds - * - * @param duration - */ - public void setLoadMoreCompleteDelayDuration(int duration) { - this.mLoadMoreCompleteDelayDuration = duration; - } - - /** - * set {@link #mLoadMoreCompleteToDefaultScrollingDuration} in milliseconds - * - * @param duration - */ - public void setLoadMoreCompleteToDefaultScrollingDuration(int duration) { - this.mLoadMoreCompleteToDefaultScrollingDuration = duration; - } - - /** - * set {@link #mDefaultToLoadingMoreScrollingDuration} in milliseconds - * - * @param duration - */ - public void setDefaultToLoadingMoreScrollingDuration(int duration) { - this.mDefaultToLoadingMoreScrollingDuration = duration; - } - - /** - * set an {@link OnRefreshListener} to listening refresh event - * - * @param listener - */ - public void setOnRefreshListener(OnRefreshListener listener) { - this.mRefreshListener = listener; - } - - /** - * set an {@link OnLoadMoreListener} to listening load more event - * - * @param listener - */ - public void setOnLoadMoreListener(OnLoadMoreListener listener) { - this.mLoadMoreListener = listener; - } - - /** - * auto refresh or cancel - * - * @param refreshing - */ - public void setRefreshing(boolean refreshing) { - if (!isRefreshEnabled() || mHeaderView == null) { - return; - } - this.mAutoLoading = refreshing; - if (refreshing) { - if (STATUS.isStatusDefault(mStatus)) { - setStatus(STATUS.STATUS_SWIPING_TO_REFRESH); - scrollDefaultToRefreshing(); - } - } else { - if (STATUS.isRefreshing(mStatus)) { - mRefreshCallback.onComplete(); - postDelayed(new Runnable() { - @Override - public void run() { - scrollRefreshingToDefault(); - } - }, mRefreshCompleteDelayDuration); - } - } - } - - /** - * auto loading more or cancel - * - * @param loadingMore - */ - public void setLoadingMore(boolean loadingMore) { - if (!isLoadMoreEnabled() || mFooterView == null) { - return; - } - this.mAutoLoading = loadingMore; - if (loadingMore) { - if (STATUS.isStatusDefault(mStatus)) { - setStatus(STATUS.STATUS_SWIPING_TO_LOAD_MORE); - scrollDefaultToLoadingMore(); - } - } else { - if (STATUS.isLoadingMore(mStatus)) { - mLoadMoreCallback.onComplete(); - postDelayed(new Runnable() { - @Override - public void run() { - scrollLoadingMoreToDefault(); - } - }, mLoadMoreCompleteDelayDuration); - } - } - } - - /** - * copy from {@link android.support.v4.widget.SwipeRefreshLayout#canChildScrollUp()} - * - * @return Whether it is possible for the child view of this layout to - * scroll left. Override this if the child view is a custom view. - */ - protected boolean canChildScrollLeft() { - if (android.os.Build.VERSION.SDK_INT < 14) { - if (mTargetView instanceof AbsListView) { - final AbsListView absListView = (AbsListView) mTargetView; - return absListView.getChildCount() > 0 - && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) - .getLeft() < absListView.getPaddingLeft()); - } else if (mTargetView instanceof ViewPager) { - ViewPager pager = (ViewPager) mTargetView; - return pager.getCurrentItem() > 0; - } else { - return ViewCompat.canScrollHorizontally(mTargetView, -1) || mTargetView.getScrollX() > 0; - } - } else { - if (mTargetView instanceof ViewPager) { - ViewPager pager = (ViewPager) mTargetView; - return pager.getCurrentItem() > 0; - } - return ViewCompat.canScrollHorizontally(mTargetView, -1); - } - } - - /** - * Whether it is possible for the child view of this layout to - * scroll right. Override this if the child view is a custom view. - * - * @return - */ - protected boolean canChildScrollRight() { - if (android.os.Build.VERSION.SDK_INT < 14) { - if (mTargetView instanceof AbsListView) { - final AbsListView absListView = (AbsListView) mTargetView; - return absListView.getChildCount() > 0 - && (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1 - || absListView.getChildAt(absListView.getChildCount() - 1).getRight() > absListView.getPaddingRight()); - } else if (mTargetView instanceof ViewPager) { - ViewPager pager = (ViewPager) mTargetView; - return pager.getCurrentItem() < pager.getAdapter().getCount() - 1; - } else { - return ViewCompat.canScrollHorizontally(mTargetView, 1) || mTargetView.getScrollX() < 0; - } - } else { - if (mTargetView instanceof ViewPager) { - ViewPager pager = (ViewPager) mTargetView; - return pager.getCurrentItem() < pager.getAdapter().getCount() - 1; - } - return ViewCompat.canScrollHorizontally(mTargetView, 1); - } - } - - /** - * @see #onLayout(boolean, int, int, int, int) - */ - private void layoutChildren() { - final int width = getMeasuredWidth(); - final int height = getMeasuredHeight(); - - final int paddingLeft = getPaddingLeft(); - final int paddingTop = getPaddingTop(); - final int paddingRight = getPaddingRight(); - final int paddingBottom = getPaddingBottom(); - - if (mTargetView == null) { - return; - } - - // layout header - if (mHeaderView != null) { - final View headerView = mHeaderView; - MarginLayoutParams lp = (MarginLayoutParams) headerView.getLayoutParams(); - final int headerLeft; - final int headerTop = paddingTop + lp.topMargin; - switch (mStyle) { - case STYLE.CLASSIC: - // classic - headerLeft = paddingLeft + lp.leftMargin - mHeaderWidth + mHeaderOffset; - break; - case STYLE.ABOVE: - // classic - headerLeft = paddingLeft + lp.leftMargin - mHeaderWidth + mHeaderOffset; - break; - case STYLE.BLEW: - // blew - headerLeft = paddingLeft + lp.leftMargin; - break; - case STYLE.SCALE: - // scale - headerLeft = paddingLeft + lp.leftMargin - mHeaderWidth / 2 + mHeaderOffset / 2; - break; - default: - // classic - headerLeft = paddingLeft + lp.leftMargin - mHeaderWidth + mHeaderOffset; - break; - } - final int headerRight = headerLeft + headerView.getMeasuredWidth(); - final int headerBottom = headerTop + headerView.getMeasuredHeight(); - headerView.layout(headerLeft, headerTop, headerRight, headerBottom); - } - - - // layout target - if (mTargetView != null) { - final View targetView = mTargetView; - MarginLayoutParams lp = (MarginLayoutParams) targetView.getLayoutParams(); - final int targetLeft; - final int targetTop = paddingTop + lp.topMargin; - - switch (mStyle) { - case STYLE.CLASSIC: - // classic - targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; - break; - case STYLE.ABOVE: - // above - targetLeft = paddingLeft + lp.leftMargin; - break; - case STYLE.BLEW: - // classic - targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; - break; - case STYLE.SCALE: - // classic - targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; - break; - default: - // classic - targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; - break; - } - final int targetRight = targetLeft + targetView.getMeasuredWidth(); - final int targetBottom = targetTop + targetView.getMeasuredHeight(); - targetView.layout(targetLeft, targetTop, targetRight, targetBottom); - } - - // layout footer - if (mFooterView != null) { - final View footerView = mFooterView; - MarginLayoutParams lp = (MarginLayoutParams) footerView.getLayoutParams(); - final int footerRight; - final int footerTop = paddingTop + lp.topMargin; - switch (mStyle) { - case STYLE.CLASSIC: - // classic - footerRight = width - paddingRight - lp.rightMargin + mFooterWidth + mFooterOffset; - break; - case STYLE.ABOVE: - // classic - footerRight = width - paddingRight - lp.rightMargin + mFooterWidth + mFooterOffset; - break; - case STYLE.BLEW: - // blew - footerRight = width - paddingRight - lp.rightMargin; - break; - case STYLE.SCALE: - // scale - footerRight = width - paddingRight - lp.rightMargin + mFooterWidth / 2 + mFooterOffset / 2; - break; - default: - // classic - footerRight = width - paddingRight - lp.rightMargin + mFooterWidth + mFooterOffset; - break; - } - final int footerLeft = footerRight - footerView.getMeasuredWidth(); - final int footerBottom = footerTop + footerView.getMeasuredHeight(); - - footerView.layout(footerLeft, footerTop, footerRight, footerBottom); - } - - if (mStyle == STYLE.CLASSIC - || mStyle == STYLE.ABOVE) { - if (mHeaderView != null) { - mHeaderView.bringToFront(); - } - if (mFooterView != null) { - mFooterView.bringToFront(); - } - } else if (mStyle == STYLE.BLEW || mStyle == STYLE.SCALE) { - if (mTargetView != null) { - mTargetView.bringToFront(); - } - } - } - - private void fixCurrentStatusLayout() { - if (STATUS.isRefreshing(mStatus)) { - mTargetOffset = (int) (mRefreshTriggerOffset + 0.5f); - mHeaderOffset = mTargetOffset; - mFooterOffset = 0; - layoutChildren(); - invalidate(); - } else if (STATUS.isStatusDefault(mStatus)) { - mTargetOffset = 0; - mHeaderOffset = 0; - mFooterOffset = 0; - layoutChildren(); - invalidate(); - } else if (STATUS.isLoadingMore(mStatus)) { - mTargetOffset = -(int) (mLoadMoreTriggerOffset + 0.5f); - mHeaderOffset = 0; - mFooterOffset = mTargetOffset; - layoutChildren(); - invalidate(); - } - } - - /** - * scrolling by physical touch with your fingers - * - * @param xDiff - */ - private void fingerScroll(final float xDiff) { - float ratio = mDragRatio; - float xScrolled = xDiff * ratio; - - // make sure (targetOffset>0 -> targetOffset=0 -> default status) - // or (targetOffset<0 -> targetOffset=0 -> default status) - // forbidden fling (targetOffset>0 -> targetOffset=0 ->targetOffset<0 -> default status) - // or (targetOffset<0 -> targetOffset=0 ->targetOffset>0 -> default status) - // I am so smart :) - - float tmpTargetOffset = xScrolled + mTargetOffset; - if ((tmpTargetOffset > 0 && mTargetOffset < 0) - || (tmpTargetOffset < 0 && mTargetOffset > 0)) { - xScrolled = -mTargetOffset; - } - - - if (mRefreshFinalDragOffset >= mRefreshTriggerOffset && tmpTargetOffset > mRefreshFinalDragOffset) { - xScrolled = mRefreshFinalDragOffset - mTargetOffset; - } else if (mLoadMoreFinalDragOffset >= mLoadMoreTriggerOffset && -tmpTargetOffset > mLoadMoreFinalDragOffset) { - xScrolled = -mLoadMoreFinalDragOffset - mTargetOffset; - } - - if (STATUS.isRefreshStatus(mStatus)) { - mRefreshCallback.onMove(mTargetOffset, false, false); - } else if (STATUS.isLoadMoreStatus(mStatus)) { - mLoadMoreCallback.onMove(mTargetOffset, false, false); - } - updateScroll(xScrolled); - } - - private void autoScroll(final float xScrolled) { - - if (STATUS.isSwipingToRefresh(mStatus)) { - mRefreshCallback.onMove(mTargetOffset, false, true); - } else if (STATUS.isReleaseToRefresh(mStatus)) { - mRefreshCallback.onMove(mTargetOffset, false, true); - } else if (STATUS.isRefreshing(mStatus)) { - mRefreshCallback.onMove(mTargetOffset, true, true); - } else if (STATUS.isSwipingToLoadMore(mStatus)) { - mLoadMoreCallback.onMove(mTargetOffset, false, true); - } else if (STATUS.isReleaseToLoadMore(mStatus)) { - mLoadMoreCallback.onMove(mTargetOffset, false, true); - } else if (STATUS.isLoadingMore(mStatus)) { - mLoadMoreCallback.onMove(mTargetOffset, true, true); - } - updateScroll(xScrolled); - } - - /** - * Process the scrolling(auto or physical) and append the diff values to mTargetOffset - * I think it's the most busy and core method. :) a ha ha ha ha... - * - * @param xScrolled - */ - private void updateScroll(final float xScrolled) { - if (xScrolled == 0) { - return; - } - mTargetOffset += xScrolled; - - if (STATUS.isRefreshStatus(mStatus)) { - mHeaderOffset = mTargetOffset; - mFooterOffset = 0; - } else if (STATUS.isLoadMoreStatus(mStatus)) { - mFooterOffset = mTargetOffset; - mHeaderOffset = 0; - } - - if (mDebug) { - Log.i(TAG, "mTargetOffset = " + mTargetOffset); - } - layoutChildren(); - invalidate(); - } - - /** - * on active finger up - */ - private void onActivePointerUp() { - if (STATUS.isSwipingToRefresh(mStatus)) { - // simply return - scrollSwipingToRefreshToDefault(); - - } else if (STATUS.isSwipingToLoadMore(mStatus)) { - // simply return - scrollSwipingToLoadMoreToDefault(); - - } else if (STATUS.isReleaseToRefresh(mStatus)) { - // return to header height and perform refresh - mRefreshCallback.onRelease(); - scrollReleaseToRefreshToRefreshing(); - - } else if (STATUS.isReleaseToLoadMore(mStatus)) { - // return to footer height and perform loadMore - mLoadMoreCallback.onRelease(); - scrollReleaseToLoadMoreToLoadingMore(); - - } - } - - /** - * on not active finger up - * - * @param ev - */ - private void onSecondaryPointerUp(MotionEvent ev) { - final int pointerIndex = MotionEventCompat.getActionIndex(ev); - final int pointerId = ev.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // This was our active pointer going up. Choose a new - // active pointer and adjust accordingly. - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mActivePointerId = ev.getPointerId(newPointerIndex); - } - } - - private void scrollDefaultToRefreshing() { - mAutoScroller.autoScroll((int) (mRefreshTriggerOffset + 0.5f), mDefaultToRefreshingScrollingDuration); - } - - private void scrollDefaultToLoadingMore() { - mAutoScroller.autoScroll(-(int) (mLoadMoreTriggerOffset + 0.5f), mDefaultToLoadingMoreScrollingDuration); - } - - private void scrollSwipingToRefreshToDefault() { - mAutoScroller.autoScroll(-mHeaderOffset, mSwipingToRefreshToDefaultScrollingDuration); - } - - private void scrollSwipingToLoadMoreToDefault() { - mAutoScroller.autoScroll(-mFooterOffset, mSwipingToLoadMoreToDefaultScrollingDuration); - } - - private void scrollReleaseToRefreshToRefreshing() { - mAutoScroller.autoScroll(mHeaderWidth - mHeaderOffset, mReleaseToRefreshToRefreshingScrollingDuration); - } - - private void scrollReleaseToLoadMoreToLoadingMore() { - mAutoScroller.autoScroll(-mFooterOffset - mFooterWidth, mReleaseToLoadMoreToLoadingMoreScrollingDuration); - } - - private void scrollRefreshingToDefault() { - mAutoScroller.autoScroll(-mHeaderOffset, mRefreshCompleteToDefaultScrollingDuration); - } - - private void scrollLoadingMoreToDefault() { - mAutoScroller.autoScroll(-mFooterOffset, mLoadMoreCompleteToDefaultScrollingDuration); - } - - /** - * invoke when {@link AutoScroller#finish()} is automatic - */ - private void autoScrollFinished() { - int mLastStatus = mStatus; - - if (STATUS.isReleaseToRefresh(mStatus)) { - setStatus(STATUS.STATUS_REFRESHING); - fixCurrentStatusLayout(); - mRefreshCallback.onRefresh(); - - } else if (STATUS.isRefreshing(mStatus)) { - setStatus(STATUS.STATUS_DEFAULT); - fixCurrentStatusLayout(); - mRefreshCallback.onReset(); - - } else if (STATUS.isSwipingToRefresh(mStatus)) { - if (mAutoLoading) { - mAutoLoading = false; - setStatus(STATUS.STATUS_REFRESHING); - fixCurrentStatusLayout(); - mRefreshCallback.onRefresh(); - } else { - setStatus(STATUS.STATUS_DEFAULT); - fixCurrentStatusLayout(); - mRefreshCallback.onReset(); - } - } else if (STATUS.isStatusDefault(mStatus)) { - - } else if (STATUS.isSwipingToLoadMore(mStatus)) { - if (mAutoLoading) { - mAutoLoading = false; - setStatus(STATUS.STATUS_LOADING_MORE); - fixCurrentStatusLayout(); - mLoadMoreCallback.onLoadMore(); - } else { - setStatus(STATUS.STATUS_DEFAULT); - fixCurrentStatusLayout(); - mLoadMoreCallback.onReset(); - } - } else if (STATUS.isLoadingMore(mStatus)) { - setStatus(STATUS.STATUS_DEFAULT); - fixCurrentStatusLayout(); - mLoadMoreCallback.onReset(); - } else if (STATUS.isReleaseToLoadMore(mStatus)) { - setStatus(STATUS.STATUS_LOADING_MORE); - fixCurrentStatusLayout(); - mLoadMoreCallback.onLoadMore(); - } else { - throw new IllegalStateException("illegal state: " + STATUS.getStatus(mStatus)); - } - - if (mDebug) { - Log.i(TAG, STATUS.getStatus(mLastStatus) + " -> " + STATUS.getStatus(mStatus)); - } - } - - /** - * check if it can refresh - * - * @return - */ - private boolean onCheckCanRefresh() { - - return mRefreshEnabled && !canChildScrollLeft() && mHasHeaderView && mRefreshTriggerOffset > 0; - } - - /** - * check if it can load more - * - * @return - */ - private boolean onCheckCanLoadMore() { - - return mLoadMoreEnabled && !canChildScrollRight() && mHasFooterView && mLoadMoreTriggerOffset > 0; - } - - private float getMotionEventY(MotionEvent event, int activePointerId) { - final int index = event.findPointerIndex(activePointerId); - if (index < 0) { - return INVALID_COORDINATE; - } - return event.getY(index); - } - - private float getMotionEventX(MotionEvent event, int activePointId) { - final int index = event.findPointerIndex(activePointId); - if (index < 0) { - return INVALID_COORDINATE; - } - return event.getX(index); - } - - RefreshCallback mRefreshCallback = new RefreshCallback() { - @Override - public void onPrepare() { - if (mHeaderView != null && mHeaderView instanceof SwipeTrigger && STATUS.isStatusDefault(mStatus)) { - mHeaderView.setVisibility(VISIBLE); - ((SwipeTrigger) mHeaderView).onPrepare(); - } - } - - @Override - public void onMove(int x, boolean isComplete, boolean automatic) { - if (mHeaderView != null && mHeaderView instanceof SwipeTrigger && STATUS.isRefreshStatus(mStatus)) { - if (mHeaderView.getVisibility() != VISIBLE) { - mHeaderView.setVisibility(VISIBLE); - } - ((SwipeTrigger) mHeaderView).onMove(x, isComplete, automatic); - } - } - - @Override - public void onRelease() { - if (mHeaderView != null && mHeaderView instanceof SwipeTrigger && STATUS.isReleaseToRefresh(mStatus)) { - ((SwipeTrigger) mHeaderView).onRelease(); - } - } - - @Override - public void onRefresh() { - if (mHeaderView != null && STATUS.isRefreshing(mStatus)) { - if (mHeaderView instanceof SwipeRefreshTrigger) { - ((SwipeRefreshTrigger) mHeaderView).onRefresh(); - } - if (mRefreshListener != null) { - mRefreshListener.onRefresh(); - } - } - } - - @Override - public void onComplete() { - if (mHeaderView != null && mHeaderView instanceof SwipeTrigger) { - ((SwipeTrigger) mHeaderView).onComplete(); - } - } - - @Override - public void onReset() { - if (mHeaderView != null && mHeaderView instanceof SwipeTrigger && STATUS.isStatusDefault(mStatus)) { - ((SwipeTrigger) mHeaderView).onReset(); - mHeaderView.setVisibility(GONE); - } - } - }; - - LoadMoreCallback mLoadMoreCallback = new LoadMoreCallback() { - - @Override - public void onPrepare() { - if (mFooterView != null && mFooterView instanceof SwipeTrigger && STATUS.isStatusDefault(mStatus)) { - mFooterView.setVisibility(VISIBLE); - ((SwipeTrigger) mFooterView).onPrepare(); - } - } - - @Override - public void onMove(int x, boolean isComplete, boolean automatic) { - if (mFooterView != null && mFooterView instanceof SwipeTrigger && STATUS.isLoadMoreStatus(mStatus)) { - if (mFooterView.getVisibility() != VISIBLE) { - mFooterView.setVisibility(VISIBLE); - } - ((SwipeTrigger) mFooterView).onMove(x, isComplete, automatic); - } - } - - @Override - public void onRelease() { - if (mFooterView != null && mFooterView instanceof SwipeTrigger && STATUS.isReleaseToLoadMore(mStatus)) { - ((SwipeTrigger) mFooterView).onRelease(); - } - } - - @Override - public void onLoadMore() { - if (mFooterView != null && STATUS.isLoadingMore(mStatus)) { - if (mFooterView instanceof SwipeLoadMoreTrigger) { - ((SwipeLoadMoreTrigger) mFooterView).onLoadMore(); - } - if (mLoadMoreListener != null) { - mLoadMoreListener.onLoadMore(); - } - } - } - - @Override - public void onComplete() { - if (mFooterView != null && mFooterView instanceof SwipeTrigger) { - ((SwipeTrigger) mFooterView).onComplete(); - } - } - - @Override - public void onReset() { - if (mFooterView != null && mFooterView instanceof SwipeTrigger && STATUS.isStatusDefault(mStatus)) { - ((SwipeTrigger) mFooterView).onReset(); - mFooterView.setVisibility(GONE); - } - } - }; - - /** - * refresh event callback - */ - abstract class RefreshCallback implements SwipeTrigger, SwipeRefreshTrigger { - } - - /** - * load more event callback - */ - abstract class LoadMoreCallback implements SwipeTrigger, SwipeLoadMoreTrigger { - } - - private class AutoScroller implements Runnable { - - private Scroller mScroller; - - private int mmLastX; - - private boolean mRunning = false; - - private boolean mAbort = false; - - public AutoScroller() { - mScroller = new Scroller(getContext()); - } - - @Override - public void run() { - boolean finish = !mScroller.computeScrollOffset() || mScroller.isFinished(); - int currx = mScroller.getCurrX(); - int xDiff = currx - mmLastX; - if (finish) { - finish(); - } else { - mmLastX = currx; - HorSwipeToLoadLayout.this.autoScroll(xDiff); - post(this); - } - } - - /** - * remove the post callbacks and reset default values - */ - private void finish() { - mmLastX = 0; - mRunning = false; - removeCallbacks(this); - // if abort by user, don't call - if (!mAbort) { - autoScrollFinished(); - } - } - - /** - * abort scroll if it is scrolling - */ - public void abortIfRunning() { - if (mRunning) { - if (!mScroller.isFinished()) { - mAbort = true; - mScroller.forceFinished(true); - } - finish(); - mAbort = false; - } - } - - /** - * The param xScrolled here isn't final pos of y. - * It's just like the yScrolled param in the - * {@link #updateScroll(float yScrolled)} - * - * @param xScrolled - * @param duration - */ - private void autoScroll(int xScrolled, int duration) { - removeCallbacks(this); - mmLastX = 0; - if (!mScroller.isFinished()) { - mScroller.forceFinished(true); - } - mScroller.startScroll(0, 0, xScrolled, 0, duration); - post(this); - mRunning = true; - } - } - - /** - * Set the current status for better control - * - * @param status - */ - private void setStatus(int status) { - mStatus = status; - if (mDebug) { - STATUS.printStatus(status); - } - } - - /** - * an inner util class. - * enum of status - */ - private final static class STATUS { - private static final int STATUS_REFRESH_RETURNING = -4; - private static final int STATUS_REFRESHING = -3; - private static final int STATUS_RELEASE_TO_REFRESH = -2; - private static final int STATUS_SWIPING_TO_REFRESH = -1; - private static final int STATUS_DEFAULT = 0; - private static final int STATUS_SWIPING_TO_LOAD_MORE = 1; - private static final int STATUS_RELEASE_TO_LOAD_MORE = 2; - private static final int STATUS_LOADING_MORE = 3; - private static final int STATUS_LOAD_MORE_RETURNING = 4; - - private static boolean isRefreshing(final int status) { - return status == STATUS.STATUS_REFRESHING; - } - - private static boolean isLoadingMore(final int status) { - return status == STATUS.STATUS_LOADING_MORE; - } - - private static boolean isReleaseToRefresh(final int status) { - return status == STATUS.STATUS_RELEASE_TO_REFRESH; - } - - private static boolean isReleaseToLoadMore(final int status) { - return status == STATUS.STATUS_RELEASE_TO_LOAD_MORE; - } - - private static boolean isSwipingToRefresh(final int status) { - return status == STATUS.STATUS_SWIPING_TO_REFRESH; - } - - private static boolean isSwipingToLoadMore(final int status) { - return status == STATUS.STATUS_SWIPING_TO_LOAD_MORE; - } - - private static boolean isRefreshStatus(final int status) { - return status < STATUS.STATUS_DEFAULT; - } - - public static boolean isLoadMoreStatus(final int status) { - return status > STATUS.STATUS_DEFAULT; - } - - private static boolean isStatusDefault(final int status) { - return status == STATUS.STATUS_DEFAULT; - } - - private static String getStatus(int status) { - final String statusInfo; - switch (status) { - case STATUS_REFRESH_RETURNING: - statusInfo = "status_refresh_returning"; - break; - case STATUS_REFRESHING: - statusInfo = "status_refreshing"; - break; - case STATUS_RELEASE_TO_REFRESH: - statusInfo = "status_release_to_refresh"; - break; - case STATUS_SWIPING_TO_REFRESH: - statusInfo = "status_swiping_to_refresh"; - break; - case STATUS_DEFAULT: - statusInfo = "status_default"; - break; - case STATUS_SWIPING_TO_LOAD_MORE: - statusInfo = "status_swiping_to_load_more"; - break; - case STATUS_RELEASE_TO_LOAD_MORE: - statusInfo = "status_release_to_load_more"; - break; - case STATUS_LOADING_MORE: - statusInfo = "status_loading_more"; - break; - case STATUS_LOAD_MORE_RETURNING: - statusInfo = "status_load_more_returning"; - break; - default: - statusInfo = "status_illegal!"; - break; - } - return statusInfo; - } - - private static void printStatus(int status) { - Log.i(TAG, "printStatus:" + getStatus(status)); - } - } -} diff --git a/library/src/main/java/com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java b/library/src/main/java/com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java index 6bbd878..a2f0c6e 100644 --- a/library/src/main/java/com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java +++ b/library/src/main/java/com/aspsine/swipetoloadlayout/SwipeToLoadLayout.java @@ -4,6 +4,7 @@ import android.content.res.TypedArray; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; @@ -61,9 +62,9 @@ public class SwipeToLoadLayout extends ViewGroup { private View mFooterView; - private int mHeaderHeight; + private int mHeaderLength; - private int mFooterHeight; + private int mFooterLength; private boolean mHasHeaderView; @@ -236,7 +237,7 @@ public class SwipeToLoadLayout extends ViewGroup { private int mDefaultToLoadingMoreScrollingDuration = DEFAULT_DEFAULT_TO_LOADING_MORE_SCROLLING_DURATION; /** - *true : the target is vertically scroll + * true : the target is vertically scroll */ private boolean mIsVertically = true; @@ -318,7 +319,8 @@ public SwipeToLoadLayout(Context context, AttributeSet attrs, int defStyleAttr) } else if (attr == R.styleable.SwipeToLoadLayout_default_to_loading_more_scrolling_duration) { setDefaultToLoadingMoreScrollingDuration(a.getInt(attr, DEFAULT_DEFAULT_TO_LOADING_MORE_SCROLLING_DURATION)); - + } else if (attr == R.styleable.SwipeToLoadLayout_swipe_vertically) { + mIsVertically = a.getBoolean(attr, mIsVertically); } } } finally { @@ -363,9 +365,9 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final View headerView = mHeaderView; measureChildWithMargins(headerView, widthMeasureSpec, 0, heightMeasureSpec, 0); MarginLayoutParams lp = ((MarginLayoutParams) headerView.getLayoutParams()); - mHeaderHeight = headerView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; - if (mRefreshTriggerOffset < mHeaderHeight) { - mRefreshTriggerOffset = mHeaderHeight; + mHeaderLength = mIsVertically ? (headerView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin) : (headerView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + if (mRefreshTriggerOffset < mHeaderLength) { + mRefreshTriggerOffset = mHeaderLength; } } // target @@ -378,16 +380,20 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final View footerView = mFooterView; measureChildWithMargins(footerView, widthMeasureSpec, 0, heightMeasureSpec, 0); MarginLayoutParams lp = ((MarginLayoutParams) footerView.getLayoutParams()); - mFooterHeight = footerView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; - if (mLoadMoreTriggerOffset < mFooterHeight) { - mLoadMoreTriggerOffset = mFooterHeight; + mFooterLength = mIsVertically ? (footerView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin) : (footerView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + if (mLoadMoreTriggerOffset < mFooterLength) { + mLoadMoreTriggerOffset = mFooterLength; } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - layoutChildren(); + if (mIsVertically) { + layoutVerChildren(); + }else { + layoutHorChildren(); + } mHasHeaderView = (mHeaderView != null); mHasFooterView = (mFooterView != null); @@ -503,8 +509,15 @@ public boolean onInterceptTouchEvent(MotionEvent event) { } float y = getMotionEventY(event, mActivePointerId); float x = getMotionEventX(event, mActivePointerId); - final float yInitDiff = y - mInitDownY; - final float xInitDiff = x - mInitDownX; + final float yInitDiff; + final float xInitDiff; + if (mIsVertically) { + yInitDiff = y - mInitDownY; + xInitDiff = x - mInitDownX; + } else { + yInitDiff = x - mInitDownX; + xInitDiff = y - mInitDownY; + } mLastY = y; mLastX = x; boolean moved = Math.abs(yInitDiff) > Math.abs(xInitDiff) @@ -549,8 +562,15 @@ public boolean onTouchEvent(MotionEvent event) { final float y = getMotionEventY(event, mActivePointerId); final float x = getMotionEventX(event, mActivePointerId); - final float yDiff = y - mLastY; - final float xDiff = x - mLastX; + final float yDiff; + final float xDiff; + if (mIsVertically) { + yDiff = y - mLastY; + xDiff = x - mLastX; + } else { + yDiff = x - mLastX; + xDiff = y - mLastY; + } mLastY = y; mLastX = x; @@ -743,6 +763,11 @@ public void setSwipeStyle(int style) { requestLayout(); } + public void setVertically(boolean vertically) { + mIsVertically = vertically; + requestLayout(); + } + /** * set how hard to drag. bigger easier, smaller harder; * @@ -754,9 +779,9 @@ public void setDragRatio(float dragRatio) { /** * set the value of {@link #mRefreshTriggerOffset}. - * Default value is the refresh header view height {@link #mHeaderHeight}

- * If the offset you set is smaller than {@link #mHeaderHeight} or not set, - * using {@link #mHeaderHeight} as default value + * Default value is the refresh header view height {@link #mHeaderLength}

+ * If the offset you set is smaller than {@link #mHeaderLength} or not set, + * using {@link #mHeaderLength} as default value * * @param offset */ @@ -766,9 +791,9 @@ public void setRefreshTriggerOffset(int offset) { /** * set the value of {@link #mLoadMoreTriggerOffset}. - * Default value is the load more footer view height {@link #mFooterHeight}

- * If the offset you set is smaller than {@link #mFooterHeight} or not set, - * using {@link #mFooterHeight} as default value + * Default value is the load more footer view height {@link #mFooterLength}

+ * If the offset you set is smaller than {@link #mFooterLength} or not set, + * using {@link #mFooterLength} as default value * * @param offset */ @@ -969,17 +994,34 @@ public void run() { * scroll up. Override this if the child view is a custom view. */ protected boolean canChildScrollUp() { - if (android.os.Build.VERSION.SDK_INT < 14) { + if (mTargetView instanceof ViewPager) { + ViewPager pager = (ViewPager) mTargetView; + return pager.getCurrentItem() > 0; + } else if (android.os.Build.VERSION.SDK_INT < 14) { if (mTargetView instanceof AbsListView) { final AbsListView absListView = (AbsListView) mTargetView; - return absListView.getChildCount() > 0 - && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) - .getTop() < absListView.getPaddingTop()); + if (mIsVertically) { + return absListView.getChildCount() > 0 + && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) + .getTop() < absListView.getPaddingTop()); + } else { + return absListView.getChildCount() > 0 + && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) + .getLeft() < absListView.getPaddingLeft()); + } } else { - return ViewCompat.canScrollVertically(mTargetView, -1) || mTargetView.getScrollY() > 0; + if (mIsVertically) { + return ViewCompat.canScrollVertically(mTargetView, -1) || mTargetView.getScrollY() > 0; + } else { + return ViewCompat.canScrollHorizontally(mTargetView, -1) || mTargetView.getScrollX() > 0; + } } } else { - return ViewCompat.canScrollVertically(mTargetView, -1); + if (mIsVertically) { + return ViewCompat.canScrollVertically(mTargetView, -1); + } else { + return ViewCompat.canScrollHorizontally(mTargetView, -1); + } } } @@ -990,24 +1032,41 @@ protected boolean canChildScrollUp() { * @return */ protected boolean canChildScrollDown() { - if (android.os.Build.VERSION.SDK_INT < 14) { + if (mTargetView instanceof ViewPager) { + ViewPager pager = (ViewPager) mTargetView; + return pager.getCurrentItem() < pager.getAdapter().getCount() - 1; + } else if (android.os.Build.VERSION.SDK_INT < 14) { if (mTargetView instanceof AbsListView) { final AbsListView absListView = (AbsListView) mTargetView; - return absListView.getChildCount() > 0 - && (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1 - || absListView.getChildAt(absListView.getChildCount() - 1).getBottom() > absListView.getPaddingBottom()); + if (mIsVertically) { + return absListView.getChildCount() > 0 + && (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1 + || absListView.getChildAt(absListView.getChildCount() - 1).getBottom() > absListView.getPaddingBottom()); + } else { + return absListView.getChildCount() > 0 + && (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1 + || absListView.getChildAt(absListView.getChildCount() - 1).getRight() > absListView.getPaddingRight()); + } } else { - return ViewCompat.canScrollVertically(mTargetView, 1) || mTargetView.getScrollY() < 0; + if (mIsVertically) { + return ViewCompat.canScrollVertically(mTargetView, 1) || mTargetView.getScrollY() < 0; + } else { + return ViewCompat.canScrollHorizontally(mTargetView, 1) || mTargetView.getScrollX() < 0; + } } } else { - return ViewCompat.canScrollVertically(mTargetView, 1); + if (mIsVertically) { + return ViewCompat.canScrollVertically(mTargetView, 1); + } else { + return ViewCompat.canScrollHorizontally(mTargetView, 1); + } } } /** * @see #onLayout(boolean, int, int, int, int) */ - private void layoutChildren() { + private void layoutVerChildren() { final int width = getMeasuredWidth(); final int height = getMeasuredHeight(); @@ -1029,11 +1088,11 @@ private void layoutChildren() { switch (mStyle) { case STYLE.CLASSIC: // classic - headerTop = paddingTop + lp.topMargin - mHeaderHeight + mHeaderOffset; + headerTop = paddingTop + lp.topMargin - mHeaderLength + mHeaderOffset; break; case STYLE.ABOVE: // classic - headerTop = paddingTop + lp.topMargin - mHeaderHeight + mHeaderOffset; + headerTop = paddingTop + lp.topMargin - mHeaderLength + mHeaderOffset; break; case STYLE.BLEW: // blew @@ -1041,11 +1100,11 @@ private void layoutChildren() { break; case STYLE.SCALE: // scale - headerTop = paddingTop + lp.topMargin - mHeaderHeight / 2 + mHeaderOffset / 2; + headerTop = paddingTop + lp.topMargin - mHeaderLength / 2 + mHeaderOffset / 2; break; default: // classic - headerTop = paddingTop + lp.topMargin - mHeaderHeight + mHeaderOffset; + headerTop = paddingTop + lp.topMargin - mHeaderLength + mHeaderOffset; break; } final int headerRight = headerLeft + headerView.getMeasuredWidth(); @@ -1097,11 +1156,11 @@ private void layoutChildren() { switch (mStyle) { case STYLE.CLASSIC: // classic - footerBottom = height - paddingBottom - lp.bottomMargin + mFooterHeight + mFooterOffset; + footerBottom = height - paddingBottom - lp.bottomMargin + mFooterLength + mFooterOffset; break; case STYLE.ABOVE: // classic - footerBottom = height - paddingBottom - lp.bottomMargin + mFooterHeight + mFooterOffset; + footerBottom = height - paddingBottom - lp.bottomMargin + mFooterLength + mFooterOffset; break; case STYLE.BLEW: // blew @@ -1109,11 +1168,11 @@ private void layoutChildren() { break; case STYLE.SCALE: // scale - footerBottom = height - paddingBottom - lp.bottomMargin + mFooterHeight / 2 + mFooterOffset / 2; + footerBottom = height - paddingBottom - lp.bottomMargin + mFooterLength / 2 + mFooterOffset / 2; break; default: // classic - footerBottom = height - paddingBottom - lp.bottomMargin + mFooterHeight + mFooterOffset; + footerBottom = height - paddingBottom - lp.bottomMargin + mFooterLength + mFooterOffset; break; } final int footerTop = footerBottom - footerView.getMeasuredHeight(); @@ -1137,24 +1196,169 @@ private void layoutChildren() { } } + /** + * @see #onLayout(boolean, int, int, int, int) + */ + private void layoutHorChildren() { + final int width = getMeasuredWidth(); + final int height = getMeasuredHeight(); + + final int paddingLeft = getPaddingLeft(); + final int paddingTop = getPaddingTop(); + final int paddingRight = getPaddingRight(); + final int paddingBottom = getPaddingBottom(); + + if (mTargetView == null) { + return; + } + + // layout header + if (mHeaderView != null) { + final View headerView = mHeaderView; + MarginLayoutParams lp = (MarginLayoutParams) headerView.getLayoutParams(); + final int headerLeft; + final int headerTop = paddingTop + lp.topMargin; + switch (mStyle) { + case STYLE.CLASSIC: + // classic + headerLeft = paddingLeft + lp.leftMargin - mHeaderLength + mHeaderOffset; + break; + case STYLE.ABOVE: + // classic + headerLeft = paddingLeft + lp.leftMargin - mHeaderLength + mHeaderOffset; + break; + case STYLE.BLEW: + // blew + headerLeft = paddingLeft + lp.leftMargin; + break; + case STYLE.SCALE: + // scale + headerLeft = paddingLeft + lp.leftMargin - mHeaderLength / 2 + mHeaderOffset / 2; + break; + default: + // classic + headerLeft = paddingLeft + lp.leftMargin - mHeaderLength + mHeaderOffset; + break; + } + final int headerRight = headerLeft + headerView.getMeasuredWidth(); + final int headerBottom = headerTop + headerView.getMeasuredHeight(); + headerView.layout(headerLeft, headerTop, headerRight, headerBottom); + } + + + // layout target + if (mTargetView != null) { + final View targetView = mTargetView; + MarginLayoutParams lp = (MarginLayoutParams) targetView.getLayoutParams(); + final int targetLeft; + final int targetTop = paddingTop + lp.topMargin; + + switch (mStyle) { + case STYLE.CLASSIC: + // classic + targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; + break; + case STYLE.ABOVE: + // above + targetLeft = paddingLeft + lp.leftMargin; + break; + case STYLE.BLEW: + // classic + targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; + break; + case STYLE.SCALE: + // classic + targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; + break; + default: + // classic + targetLeft = paddingLeft + lp.leftMargin + mTargetOffset; + break; + } + final int targetRight = targetLeft + targetView.getMeasuredWidth(); + final int targetBottom = targetTop + targetView.getMeasuredHeight(); + targetView.layout(targetLeft, targetTop, targetRight, targetBottom); + } + + // layout footer + if (mFooterView != null) { + final View footerView = mFooterView; + MarginLayoutParams lp = (MarginLayoutParams) footerView.getLayoutParams(); + final int footerRight; + final int footerTop = paddingTop + lp.topMargin; + switch (mStyle) { + case STYLE.CLASSIC: + // classic + footerRight = width - paddingRight - lp.rightMargin + mHeaderLength + mFooterOffset; + break; + case STYLE.ABOVE: + // classic + footerRight = width - paddingRight - lp.rightMargin + mHeaderLength + mFooterOffset; + break; + case STYLE.BLEW: + // blew + footerRight = width - paddingRight - lp.rightMargin; + break; + case STYLE.SCALE: + // scale + footerRight = width - paddingRight - lp.rightMargin + mHeaderLength / 2 + mFooterOffset / 2; + break; + default: + // classic + footerRight = width - paddingRight - lp.rightMargin + mHeaderLength + mFooterOffset; + break; + } + final int footerLeft = footerRight - footerView.getMeasuredWidth(); + final int footerBottom = footerTop + footerView.getMeasuredHeight(); + + footerView.layout(footerLeft, footerTop, footerRight, footerBottom); + } + + if (mStyle == STYLE.CLASSIC + || mStyle == STYLE.ABOVE) { + if (mHeaderView != null) { + mHeaderView.bringToFront(); + } + if (mFooterView != null) { + mFooterView.bringToFront(); + } + } else if (mStyle == STYLE.BLEW || mStyle == STYLE.SCALE) { + if (mTargetView != null) { + mTargetView.bringToFront(); + } + } + } + private void fixCurrentStatusLayout() { if (STATUS.isRefreshing(mStatus)) { mTargetOffset = (int) (mRefreshTriggerOffset + 0.5f); mHeaderOffset = mTargetOffset; mFooterOffset = 0; - layoutChildren(); + if (mIsVertically) { + layoutVerChildren(); + }else { + layoutHorChildren(); + } invalidate(); } else if (STATUS.isStatusDefault(mStatus)) { mTargetOffset = 0; mHeaderOffset = 0; mFooterOffset = 0; - layoutChildren(); + if (mIsVertically) { + layoutVerChildren(); + }else { + layoutHorChildren(); + } invalidate(); } else if (STATUS.isLoadingMore(mStatus)) { mTargetOffset = -(int) (mLoadMoreTriggerOffset + 0.5f); mHeaderOffset = 0; mFooterOffset = mTargetOffset; - layoutChildren(); + if (mIsVertically) { + layoutVerChildren(); + }else { + layoutHorChildren(); + } invalidate(); } } @@ -1236,7 +1440,11 @@ private void updateScroll(final float yScrolled) { if (mDebug) { Log.i(TAG, "mTargetOffset = " + mTargetOffset); } - layoutChildren(); + if (mIsVertically) { + layoutVerChildren(); + }else { + layoutHorChildren(); + } invalidate(); } @@ -1298,11 +1506,11 @@ private void scrollSwipingToLoadMoreToDefault() { } private void scrollReleaseToRefreshToRefreshing() { - mAutoScroller.autoScroll(mHeaderHeight - mHeaderOffset, mReleaseToRefreshToRefreshingScrollingDuration); + mAutoScroller.autoScroll(mHeaderLength - mHeaderOffset, mReleaseToRefreshToRefreshingScrollingDuration); } private void scrollReleaseToLoadMoreToLoadingMore() { - mAutoScroller.autoScroll(-mFooterOffset - mFooterHeight, mReleaseToLoadMoreToLoadingMoreScrollingDuration); + mAutoScroller.autoScroll(-mFooterOffset - mFooterLength, mReleaseToLoadMoreToLoadingMoreScrollingDuration); } private void scrollRefreshingToDefault() { @@ -1531,7 +1739,7 @@ private class AutoScroller implements Runnable { private Scroller mScroller; - private int mmLastY; + private int mmLastPoint; private boolean mRunning = false; @@ -1544,13 +1752,13 @@ public AutoScroller() { @Override public void run() { boolean finish = !mScroller.computeScrollOffset() || mScroller.isFinished(); - int currY = mScroller.getCurrY(); - int yDiff = currY - mmLastY; + int currPoint = mIsVertically ? mScroller.getCurrY() : mScroller.getCurrX(); + int diff = currPoint - mmLastPoint; if (finish) { finish(); } else { - mmLastY = currY; - SwipeToLoadLayout.this.autoScroll(yDiff); + mmLastPoint = currPoint; + SwipeToLoadLayout.this.autoScroll(diff); post(this); } } @@ -1559,7 +1767,7 @@ public void run() { * remove the post callbacks and reset default values */ private void finish() { - mmLastY = 0; + mmLastPoint = 0; mRunning = false; removeCallbacks(this); // if abort by user, don't call @@ -1587,16 +1795,20 @@ public void abortIfRunning() { * It's just like the yScrolled param in the * {@link #updateScroll(float yScrolled)} * - * @param yScrolled + * @param scrolled * @param duration */ - private void autoScroll(int yScrolled, int duration) { + private void autoScroll(int scrolled, int duration) { removeCallbacks(this); - mmLastY = 0; + mmLastPoint = 0; if (!mScroller.isFinished()) { mScroller.forceFinished(true); } - mScroller.startScroll(0, 0, 0, yScrolled, duration); + if (mIsVertically) { + mScroller.startScroll(0, 0, 0, scrolled, duration); + } else { + mScroller.startScroll(0, 0, scrolled, 0, duration); + } post(this); mRunning = true; } diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index ec603d5..82153b2 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -27,7 +27,7 @@ - + \ No newline at end of file From ce94b94efe391219e7bedd8c134f8a3dbdf4596c Mon Sep 17 00:00:00 2001 From: wxj <986798656@qq.com> Date: Tue, 7 Feb 2017 15:14:14 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9VierPager?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81=EF=BC=8C=E5=8A=A0=E5=85=A5=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E5=AF=B9=E5=BA=94=E7=9A=84=E6=A8=AA=E5=90=91=E6=BB=91?= =?UTF-8?q?=E5=8A=A8recyclerView=E7=9A=84demo=E5=92=8Cviewpager=E7=9A=84de?= =?UTF-8?q?mo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 + app/src/main/AndroidManifest.xml | 3 + .../swipetoloadlayout/demo/MainActivity.java | 100 ++----- .../demo/OnRecyclerClickListener.java | 12 + .../demo/RecyclerActivity.java | 68 +++++ .../swipetoloadlayout/demo/VerActivity.java | 90 +++++++ .../demo/ViewPagerActivity.java | 75 ++++++ .../demo/adapter/RecyclerAdapter.java | 65 +++++ .../demo/adapter/ViewPagerAdapter.java | 96 +++++++ .../demo/view/VerticalTextView2.java | 244 ++++++++++++++++++ .../demo/view/footer/WRefreshFooterView.java | 96 +++++++ .../demo/view/header/WRefreshHeaderView.java | 132 ++++++++++ app/src/main/res/drawable/refresh_loading.xml | 42 +++ app/src/main/res/layout/activity_main.xml | 43 +-- app/src/main/res/layout/activity_recycler.xml | 10 + app/src/main/res/layout/activity_ver.xml | 24 ++ .../main/res/layout/activity_viewpager.xml | 26 ++ app/src/main/res/layout/item_pager_text.xml | 14 + app/src/main/res/layout/item_text.xml | 21 ++ .../res/layout/layout_refresh_footer_view.xml | 26 ++ .../res/layout/layout_refresh_header_view.xml | 45 ++++ .../res/layout/recycler_view_head_foot.xml | 26 ++ app/src/main/res/menu/menu_main.xml | 2 +- .../res/mipmap-hdpi/refresh_head_arrow.png | Bin 0 -> 568 bytes .../res/mipmap-hdpi/refresh_loading01.png | Bin 0 -> 1335 bytes .../res/mipmap-hdpi/refresh_loading02.png | Bin 0 -> 1305 bytes .../res/mipmap-hdpi/refresh_loading03.png | Bin 0 -> 1282 bytes .../res/mipmap-hdpi/refresh_loading04.png | Bin 0 -> 1316 bytes .../res/mipmap-hdpi/refresh_loading05.png | Bin 0 -> 1327 bytes .../res/mipmap-hdpi/refresh_loading06.png | Bin 0 -> 1294 bytes .../res/mipmap-hdpi/refresh_loading07.png | Bin 0 -> 1280 bytes .../res/mipmap-hdpi/refresh_loading08.png | Bin 0 -> 1293 bytes .../res/mipmap-hdpi/refresh_loading09.png | Bin 0 -> 1296 bytes .../res/mipmap-hdpi/refresh_loading10.png | Bin 0 -> 1277 bytes .../res/mipmap-hdpi/refresh_loading11.png | Bin 0 -> 1303 bytes .../res/mipmap-hdpi/refresh_loading12.png | Bin 0 -> 1309 bytes .../main/res/mipmap-xhdpi/refresh_success.png | Bin 0 -> 770 bytes app/src/main/res/values/attrs.xml | 12 + app/src/main/res/values/dimens.xml | 4 + 39 files changed, 1188 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/OnRecyclerClickListener.java create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/RecyclerActivity.java create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/VerActivity.java create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/ViewPagerActivity.java create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/adapter/RecyclerAdapter.java create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/adapter/ViewPagerAdapter.java create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/VerticalTextView2.java create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/footer/WRefreshFooterView.java create mode 100644 app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/header/WRefreshHeaderView.java create mode 100644 app/src/main/res/drawable/refresh_loading.xml create mode 100644 app/src/main/res/layout/activity_recycler.xml create mode 100644 app/src/main/res/layout/activity_ver.xml create mode 100644 app/src/main/res/layout/activity_viewpager.xml create mode 100644 app/src/main/res/layout/item_pager_text.xml create mode 100644 app/src/main/res/layout/item_text.xml create mode 100644 app/src/main/res/layout/layout_refresh_footer_view.xml create mode 100644 app/src/main/res/layout/layout_refresh_header_view.xml create mode 100644 app/src/main/res/layout/recycler_view_head_foot.xml create mode 100644 app/src/main/res/mipmap-hdpi/refresh_head_arrow.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading01.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading02.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading03.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading04.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading05.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading06.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading07.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading08.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading09.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading10.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading11.png create mode 100644 app/src/main/res/mipmap-hdpi/refresh_loading12.png create mode 100644 app/src/main/res/mipmap-xhdpi/refresh_success.png diff --git a/app/build.gradle b/app/build.gradle index cf2fb9f..ffd7cbb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,4 +35,8 @@ dependencies { compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.lsjwzh:materialloadingprogressbar:0.5.8-RELEASE' compile 'com.github.Aspsine:FragmentNavigator:1.0.2' + + compile 'com.android.support:cardview-v7:25.1.0' + compile 'com.jakewharton:butterknife:8.4.0' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dec8147..24115e6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,9 @@ + + + diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/MainActivity.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/MainActivity.java index 5a860b8..44d2afa 100644 --- a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/MainActivity.java +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/MainActivity.java @@ -1,90 +1,42 @@ package com.aspsine.swipetoloadlayout.demo; - import android.content.Intent; import android.os.Bundle; -import android.support.design.widget.NavigationView; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; +import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; -import android.view.MenuItem; - -import com.aspsine.fragmentnavigator.FragmentNavigator; -import com.aspsine.swipetoloadlayout.demo.fragment.BaseToolbarFragment; - -import java.util.Arrays; -import java.util.List; - -public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener - , BaseToolbarFragment.ToggleDrawerCallBack { - - private static final Integer ID_ARRAY[] = { - R.id.nav_Twitter_style, - R.id.nav_google_style, - R.id.nav_yalantis_style, - R.id.nav_jd_style, - R.id.nav_set_header_footer_via_code - }; - - private static final List IDS = Arrays.asList(ID_ARRAY); +import android.view.View; - private static final int DEFAULT_POSITION = 0; +import butterknife.ButterKnife; +import butterknife.OnClick; - private DrawerLayout drawerLayout; +/** + * Created by wang + * on 2017/1/22 + */ - /** - * https://github.com/Aspsine/FragmentNavigator - */ - private FragmentNavigator mFragmentNavigator; +public class MainActivity extends AppCompatActivity { @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - mFragmentNavigator = new FragmentNavigator(getSupportFragmentManager(), new MainFragmentAdapter(), R.id.container); - - mFragmentNavigator.setDefaultPosition(DEFAULT_POSITION); - - mFragmentNavigator.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - - NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView); - - navigationView.setNavigationItemSelectedListener(this); - - navigationView.setCheckedItem(IDS.get(DEFAULT_POSITION)); - - mFragmentNavigator.showFragment(mFragmentNavigator.getCurrentPosition()); + ButterKnife.bind(this); } - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mFragmentNavigator.onSaveInstanceState(outState); - } - - @Override - public void openDrawer() { - drawerLayout.openDrawer(GravityCompat.START); - } - - @Override - public boolean onNavigationItemSelected(final MenuItem menuItem) { - drawerLayout.closeDrawer(GravityCompat.START); - drawerLayout.postDelayed(new Runnable() { - @Override - public void run() { - int itemId = menuItem.getItemId(); - if (itemId == R.id.nav_about) { - startActivity(new Intent(MainActivity.this, AboutActivity.class)); - } else { - mFragmentNavigator.showFragment(IDS.indexOf(itemId)); - } - } - }, 200); - return true; + @OnClick({R.id.recycler_btn, R.id.pager_btn, R.id.ver_btn}) + public void onClick(View view) { + Intent intent = new Intent(); + switch (view.getId()) { + case R.id.ver_btn: + intent.setClass(this, VerActivity.class); + break; + case R.id.recycler_btn: + intent.setClass(this, RecyclerActivity.class); + break; + case R.id.pager_btn: + intent.setClass(this, ViewPagerActivity.class); + break; + } + startActivity(intent); } } diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/OnRecyclerClickListener.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/OnRecyclerClickListener.java new file mode 100644 index 0000000..efab5b6 --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/OnRecyclerClickListener.java @@ -0,0 +1,12 @@ +package com.aspsine.swipetoloadlayout.demo; + +/** + * Created by wang + * on 2017/1/22 + */ + +public interface OnRecyclerClickListener { + + void onClick(int position); + +} diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/RecyclerActivity.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/RecyclerActivity.java new file mode 100644 index 0000000..6054bfd --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/RecyclerActivity.java @@ -0,0 +1,68 @@ +package com.aspsine.swipetoloadlayout.demo; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.widget.Toast; + +import com.aspsine.swipetoloadlayout.OnLoadMoreListener; +import com.aspsine.swipetoloadlayout.OnRefreshListener; +import com.aspsine.swipetoloadlayout.SwipeToLoadLayout; +import com.aspsine.swipetoloadlayout.demo.adapter.RecyclerAdapter; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class RecyclerActivity extends AppCompatActivity implements OnRefreshListener, OnLoadMoreListener, OnRecyclerClickListener { + + @BindView(R.id.swipe_target) + RecyclerView mRecyclerView; + @BindView(R.id.load_layout) + SwipeToLoadLayout mLoadLayout; + + private ArrayList mNames; + private int mName; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_recycler); + ButterKnife.bind(this); + mLoadLayout.setOnRefreshListener(this); + mLoadLayout.setOnLoadMoreListener(this); + mNames = new ArrayList<>(); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); + mRecyclerView.setAdapter(new RecyclerAdapter(mNames, this)); + onRefresh(); + } + + @Override + public void onRefresh() { + mNames.clear(); + mName = 0; + for (int i = 0; i < 3; i++) { + mNames.add("name: " + mName); + mName ++; + } + mRecyclerView.getAdapter().notifyDataSetChanged(); + mLoadLayout.setRefreshing(false); + } + + @Override + public void onLoadMore() { + for (int i = 0; i < 3; i++) { + mNames.add("name: " + mName); + mName ++; + } + mRecyclerView.getAdapter().notifyDataSetChanged(); + mLoadLayout.setLoadingMore(false); + } + + @Override + public void onClick(int position) { + Toast.makeText(this, mNames.get(position), Toast.LENGTH_SHORT).show(); + } +} diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/VerActivity.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/VerActivity.java new file mode 100644 index 0000000..a0fdcca --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/VerActivity.java @@ -0,0 +1,90 @@ +package com.aspsine.swipetoloadlayout.demo; + + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; + +import com.aspsine.fragmentnavigator.FragmentNavigator; +import com.aspsine.swipetoloadlayout.demo.fragment.BaseToolbarFragment; + +import java.util.Arrays; +import java.util.List; + +public class VerActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener + , BaseToolbarFragment.ToggleDrawerCallBack { + + private static final Integer ID_ARRAY[] = { + R.id.nav_Twitter_style, + R.id.nav_google_style, + R.id.nav_yalantis_style, + R.id.nav_jd_style, + R.id.nav_set_header_footer_via_code + }; + + private static final List IDS = Arrays.asList(ID_ARRAY); + + private static final int DEFAULT_POSITION = 0; + + private DrawerLayout drawerLayout; + + /** + * https://github.com/Aspsine/FragmentNavigator + */ + private FragmentNavigator mFragmentNavigator; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mFragmentNavigator = new FragmentNavigator(getSupportFragmentManager(), new MainFragmentAdapter(), R.id.container); + + mFragmentNavigator.setDefaultPosition(DEFAULT_POSITION); + + mFragmentNavigator.onCreate(savedInstanceState); + + setContentView(R.layout.activity_ver); + + drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + + NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView); + + navigationView.setNavigationItemSelectedListener(this); + + navigationView.setCheckedItem(IDS.get(DEFAULT_POSITION)); + + mFragmentNavigator.showFragment(mFragmentNavigator.getCurrentPosition()); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mFragmentNavigator.onSaveInstanceState(outState); + } + + @Override + public void openDrawer() { + drawerLayout.openDrawer(GravityCompat.START); + } + + @Override + public boolean onNavigationItemSelected(final MenuItem menuItem) { + drawerLayout.closeDrawer(GravityCompat.START); + drawerLayout.postDelayed(new Runnable() { + @Override + public void run() { + int itemId = menuItem.getItemId(); + if (itemId == R.id.nav_about) { + startActivity(new Intent(VerActivity.this, AboutActivity.class)); + } else { + mFragmentNavigator.showFragment(IDS.indexOf(itemId)); + } + } + }, 200); + return true; + } +} diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/ViewPagerActivity.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/ViewPagerActivity.java new file mode 100644 index 0000000..65fb288 --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/ViewPagerActivity.java @@ -0,0 +1,75 @@ +package com.aspsine.swipetoloadlayout.demo; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; + +import com.aspsine.swipetoloadlayout.OnLoadMoreListener; +import com.aspsine.swipetoloadlayout.OnRefreshListener; +import com.aspsine.swipetoloadlayout.SwipeToLoadLayout; +import com.aspsine.swipetoloadlayout.demo.adapter.ViewPagerAdapter; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Created by wang + * on 2017/1/22 + */ + +public class ViewPagerActivity extends AppCompatActivity implements OnRefreshListener, OnLoadMoreListener, OnRecyclerClickListener { + + @BindView(R.id.swipe_target) + ViewPager mViewPager; + @BindView(R.id.load_layout) + SwipeToLoadLayout mLoadLayout; + + private ArrayList mNames; + + private int mName; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_viewpager); + ButterKnife.bind(this); + mLoadLayout.setOnRefreshListener(this); + mLoadLayout.setOnLoadMoreListener(this); + mNames = new ArrayList<>(); + mViewPager.setAdapter(new ViewPagerAdapter(mNames, this)); + onRefresh(); + } + + @Override + public void onRefresh() { + mNames.clear(); + mName = 0; + for (int i = 0; i < 3; i++) { + mNames.add("name: " + mName); + mName ++; + } + mViewPager.getAdapter().notifyDataSetChanged(); + mLoadLayout.setRefreshing(false); + } + + @Override + public void onLoadMore() { + for (int i = 0; i < 3; i++) { + mNames.add("name: " + mName); + mName ++; + } + mViewPager.getAdapter().notifyDataSetChanged(); + mLoadLayout.setLoadingMore(false); + } + + @Override + public void onClick(int position) { + Toast.makeText(this, mNames.get(position), Toast.LENGTH_SHORT).show(); + } + + +} diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/adapter/RecyclerAdapter.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/adapter/RecyclerAdapter.java new file mode 100644 index 0000000..b28d8da --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/adapter/RecyclerAdapter.java @@ -0,0 +1,65 @@ +package com.aspsine.swipetoloadlayout.demo.adapter; + +import android.support.v7.widget.AppCompatTextView; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.aspsine.swipetoloadlayout.demo.OnRecyclerClickListener; +import com.aspsine.swipetoloadlayout.demo.R; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Created by wang + * on 2017/1/22 + */ + +public class RecyclerAdapter extends RecyclerView.Adapter { + + private List mNames; + + private OnRecyclerClickListener mListener; + + public RecyclerAdapter(List names, OnRecyclerClickListener listener) { + mNames = names; + mListener = listener; + } + + @Override + public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text, parent, false); + return new TextViewHolder(itemView); + } + + @Override + public void onBindViewHolder(TextViewHolder holder, int position) { + holder.mNameTv.setText(mNames.get(position)); + } + + @Override + public int getItemCount() { + return mNames.size(); + } + + class TextViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.name_tv) + AppCompatTextView mNameTv; + + public TextViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mListener.onClick(getAdapterPosition()); + } + }); + } + } +} diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/adapter/ViewPagerAdapter.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/adapter/ViewPagerAdapter.java new file mode 100644 index 0000000..69604f8 --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/adapter/ViewPagerAdapter.java @@ -0,0 +1,96 @@ +package com.aspsine.swipetoloadlayout.demo.adapter; + +import android.support.v4.view.PagerAdapter; +import android.support.v7.widget.AppCompatTextView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.aspsine.swipetoloadlayout.demo.OnRecyclerClickListener; +import com.aspsine.swipetoloadlayout.demo.R; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * Created by wang + * on 2017/1/22 + */ + +public class ViewPagerAdapter extends PagerAdapter { + + private List mName; + private OnRecyclerClickListener mListener; + + public ViewPagerAdapter(List name, OnRecyclerClickListener listener) { + mName = name; + mListener = listener; + } + + public ViewPagerAdapter(List name) { + mName = name; + } + + public List getName() { + return mName; + } + + @Override + public int getCount() { + return mName.size(); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == ((ViewHolder) object).itemView; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + View convertView = LayoutInflater.from(container.getContext()).inflate(R.layout.item_pager_text, container, false); + container.addView(convertView); + return new ViewHolder(convertView, position); + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView(((ViewHolder) object).itemView); + } + + @Override + public int getItemPosition(Object object) { + return POSITION_NONE; + } + + + class ViewHolder { + + @BindView(R.id.name_tv) + AppCompatTextView mNameTv; + + View itemView; + + private int mPosition; + + public ViewHolder(View view, int position) { + itemView = view; + mPosition = position; + ButterKnife.bind(this, view); + onBindView(mName.get(position)); + } + + @OnClick(R.id.name_tv) + public void onClick() { + if (mListener != null) { + mListener.onClick(mPosition); + } + } + + private void onBindView(String name) { + mNameTv.setText(name); + } + } +} diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/VerticalTextView2.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/VerticalTextView2.java new file mode 100644 index 0000000..3ae693f --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/VerticalTextView2.java @@ -0,0 +1,244 @@ +package com.aspsine.swipetoloadlayout.demo.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import com.aspsine.swipetoloadlayout.demo.R; + +/** + * Created on 2016/10/31. + * Author: wang + */ + +public class VerticalTextView2 extends View { + + public static enum StartAlign { + + LEFT(0x0), + RIGHT(0x1); + + private int value; + + StartAlign(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + /** + * 需要绘制的文字 + */ + private String mText = ""; + /** + * 文本的颜色 + */ + private int mTextColor = Color.BLACK; + /** + * 文本的大小 + */ + private float mTextSize = 40; + + /** + * 每列最多显示数量 + */ + private int vTextNum = -1; + + + /** + * 字体高度 + */ + private float mFontHeight; + private float mFontBaseLine; + + // 列宽度 + private int mLineWidth = 0; + // 列间距 + private int mLineSpacing = 20; + + private int specHeight; + private int specWidth; + //能够显示文本的最大高度 + + // 绘制字体的默认方向 + private StartAlign textStartAlign = StartAlign.LEFT; + + /** + * 绘制时控制文本绘制的范围 + */ + private Paint mPaint; + + private boolean isMeasure; + + public VerticalTextView2(Context context) { + this(context, null); + } + + public VerticalTextView2(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public VerticalTextView2(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + if (attrs != null) { + //获取自定义属性的值 + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VerticalTextView2, defStyleAttr, 0); + try { + mText = a.getString(R.styleable.VerticalTextView2_ver_text); + mTextColor = a.getColor(R.styleable.VerticalTextView2_ver_textColor, Color.BLACK); + mTextSize = a.getDimensionPixelSize(R.styleable.VerticalTextView2_ver_textSize, 40); + vTextNum = a.getInt(R.styleable.VerticalTextView2_ver_textNum, -1); + mLineSpacing = a.getDimensionPixelOffset(R.styleable.VerticalTextView2_ver_lineSpacing, 20); + int align = a.getInt(R.styleable.VerticalTextView2_ver_textStartAlign, StartAlign.RIGHT.getValue()); + if (StartAlign.LEFT.getValue() == align) { + textStartAlign = StartAlign.LEFT; + } else { + textStartAlign = StartAlign.RIGHT; + } + } finally { + a.recycle(); + } + } + mPaint = new Paint(); + mPaint.setTextSize(mTextSize); + mPaint.setColor(mTextColor); + // 文字居中 + mPaint.setTextAlign(Paint.Align.CENTER); + // 平滑处理 + mPaint.setAntiAlias(true); + + } + + + @Override + protected void onDraw(Canvas canvas) { + //绘制文字 + char StringItem; + boolean isRight = StartAlign.RIGHT == textStartAlign; + float textHeight = mFontHeight; + int viewHeight = specHeight; + float mTextPosY = mFontBaseLine; + float mTextPosX = isRight ? getWidth() : 0; + mTextPosX += isRight ? -mLineWidth >> 1 : mLineWidth >> 1;//字体居住绘制 x起始位置-mLineWidth>>1 + int textLength = mText.length(); + for (int i = 0; i < textLength; i++) { + StringItem = mText.charAt(i); + if (mTextPosY > viewHeight) { + mTextPosY = mFontBaseLine; + mTextPosX += isRight ? -mLineWidth : mLineWidth; + if (mTextPosX > specWidth) { + return; + } + } + canvas.drawText(String.valueOf(StringItem), mTextPosX, mTextPosY, mPaint); + mTextPosY += textHeight; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + //onMeasure方法会调用多次,为了避免重复分段,做判断 + + if (!isMeasure) { + isMeasure = true; + //计算字体高度大小 + measureTextSize(); + } + measureWH(heightMeasureSpec); + //保存测量宽度和测量高度 + setMeasuredDimension(specWidth, specHeight); + } + + + + private void measureWH(int heightMeasureSpec) { + int heightMode = MeasureSpec.getMode(heightMeasureSpec); //获取高的模式 + int heightSize = MeasureSpec.getSize(heightMeasureSpec); //获取高的尺寸 + + int textHeight = (int) mFontHeight; + int textLength = null == mText ? 0 : mText.length(); + + //高度 + //match_parent或者具体的值 + if (heightMode == MeasureSpec.EXACTLY) { + specHeight = heightSize; + } else { + //wrap_content + float tempHeight = Math.min(textHeight * textLength, heightSize); + if (vTextNum != -1) { + tempHeight = vTextNum * textHeight; + } + specHeight = (int) (getPaddingTop() + tempHeight + getPaddingBottom()); + } + + float lineNum = textHeight * textLength / specHeight; + + int textWidth = (int) (Math.ceil(lineNum) * mLineWidth); + specWidth = getPaddingLeft() + textWidth + getPaddingRight(); + } + + public String getText() { + return mText; + } + + public void setText(String mText) { + this.mText = mText; + resetMeasure(); + } + + public int getTextColor() { + return mTextColor; + } + + public void setTextColor(int mTextColor) { + this.mTextColor = mTextColor; + mPaint.setColor(mTextColor); + } + + public float getTextSize() { + return mTextSize; + } + + public void setTextSize(float mTextSize) { + this.mTextSize = mTextSize; + mPaint.setTextSize(mTextSize); + resetMeasure(); + } + + private void resetMeasure() { + isMeasure = false; + requestLayout(); + } + + /** + * 计算文字大小高度 + */ + private void measureTextSize() { + + mPaint.setTextSize(mTextSize); + // 获得行宽包括间隔 + if (mLineWidth == 0) { + float[] widths = new float[1]; + // 获取单个汉字的宽度 + mPaint.getTextWidths("汉", widths); + + mLineWidth = mLineSpacing + (int) widths[0]; + } + + Paint.FontMetrics fm = mPaint.getFontMetrics(); + + // 获得字体高度 + mFontHeight = fm.bottom - fm.top; + mFontBaseLine = - fm.top; + + } + +} diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/footer/WRefreshFooterView.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/footer/WRefreshFooterView.java new file mode 100644 index 0000000..ade1651 --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/footer/WRefreshFooterView.java @@ -0,0 +1,96 @@ +package com.aspsine.swipetoloadlayout.demo.view.footer; + +import android.content.Context; +import android.graphics.drawable.AnimationDrawable; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.aspsine.swipetoloadlayout.SwipeLoadMoreTrigger; +import com.aspsine.swipetoloadlayout.SwipeTrigger; +import com.aspsine.swipetoloadlayout.demo.R; +import com.aspsine.swipetoloadlayout.demo.view.VerticalTextView2; + + +/** + * Created on 2016/1/14. + * Author: wang + */ +public class WRefreshFooterView extends LinearLayout implements SwipeTrigger, SwipeLoadMoreTrigger { + + private ImageView mLoadingImg; + + private VerticalTextView2 mLoadMoreTV; + + private AnimationDrawable mAnimDrawable; + + private int mFooterHeight; + + + public WRefreshFooterView(Context context) { + super(context); + mFooterHeight = getResources().getDimensionPixelOffset(R.dimen.refresh_footer_height); + } + + public WRefreshFooterView(Context context, AttributeSet attrs) { + super(context, attrs); + mFooterHeight = getResources().getDimensionPixelOffset(R.dimen.refresh_footer_height); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mLoadingImg = (ImageView) findViewById(R.id.loading_img); + mLoadMoreTV = (VerticalTextView2) findViewById(R.id.load_more_tv); + mAnimDrawable = (AnimationDrawable) mLoadingImg.getDrawable(); + } + + + @Override + public void onPrepare() { + + } + + @Override + public void onMove(int y, boolean isComplete, boolean automatic) { + if (!isComplete) { + mAnimDrawable.stop(); + mLoadingImg.setVisibility(GONE); + mLoadMoreTV.setVisibility(VISIBLE); + if (-y >= mFooterHeight) { + mLoadMoreTV.setText("释放加载更多"); + } else { + mLoadMoreTV.setText("左滑加载更多"); + } + } + } + + @Override + public void onRelease() { + + } + + @Override + public void onLoadMore() { + mLoadMoreTV.setVisibility(GONE); + mLoadingImg.setVisibility(VISIBLE); + mAnimDrawable.start(); + } + + @Override + public void onComplete() { + mAnimDrawable.stop(); + mLoadingImg.setVisibility(GONE); + mLoadMoreTV.setVisibility(VISIBLE); + mLoadMoreTV.setText("加载完成"); + } + + @Override + public void onReset() { + mAnimDrawable.stop(); + mLoadingImg.setVisibility(GONE); + mLoadMoreTV.setVisibility(VISIBLE); + mLoadMoreTV.setText("左滑加载更多"); + } + +} diff --git a/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/header/WRefreshHeaderView.java b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/header/WRefreshHeaderView.java new file mode 100644 index 0000000..e9ba271 --- /dev/null +++ b/app/src/main/java/com/aspsine/swipetoloadlayout/demo/view/header/WRefreshHeaderView.java @@ -0,0 +1,132 @@ +package com.aspsine.swipetoloadlayout.demo.view.header; + +import android.content.Context; +import android.graphics.drawable.AnimationDrawable; +import android.util.AttributeSet; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.aspsine.swipetoloadlayout.SwipeRefreshTrigger; +import com.aspsine.swipetoloadlayout.SwipeTrigger; +import com.aspsine.swipetoloadlayout.demo.R; +import com.aspsine.swipetoloadlayout.demo.view.VerticalTextView2; + +/** + * Created on 2016/9/20. + * Author: wang + */ +public class WRefreshHeaderView extends LinearLayout implements SwipeTrigger, SwipeRefreshTrigger { + + private ImageView mArrowImg; + + private ImageView mLoadingImg; + + private ImageView mSuccessImg; + + private VerticalTextView2 mRefreshTV; + + private AnimationDrawable mAnimDrawable; + + private int mHeaderHeight; + + private Animation rotateUp; + + private Animation rotateDown; + + private boolean rotated = false; + + public WRefreshHeaderView(Context context) { + this(context, null); + } + + public WRefreshHeaderView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public WRefreshHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mHeaderHeight = getResources().getDimensionPixelOffset(R.dimen.refresh_header_height); + rotateUp = AnimationUtils.loadAnimation(context, R.anim.rotate_up); + rotateDown = AnimationUtils.loadAnimation(context, R.anim.rotate_down); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mRefreshTV = (VerticalTextView2) findViewById(R.id.refresh_tv); + mArrowImg = (ImageView) findViewById(R.id.arrow_img); + mSuccessImg = (ImageView) findViewById(R.id.success_img); + mLoadingImg = (ImageView) findViewById(R.id.loading_img); + mAnimDrawable = (AnimationDrawable) mLoadingImg.getDrawable(); + } + + @Override + public void onRefresh() { + mSuccessImg.setVisibility(GONE); + mArrowImg.clearAnimation(); + mArrowImg.setVisibility(GONE); + mLoadingImg.setVisibility(VISIBLE); + mAnimDrawable.start(); + mRefreshTV.setText("加载中..."); + } + + @Override + public void onPrepare() { + + } + + @Override + public void onMove(int y, boolean isComplete, boolean automatic) { + if (!isComplete) { + mArrowImg.setVisibility(VISIBLE); + mAnimDrawable.stop(); + mLoadingImg.setVisibility(GONE); + mSuccessImg.setVisibility(GONE); + if (y > mHeaderHeight) { + mRefreshTV.setText("释放刷新"); + if (!rotated) { + mArrowImg.clearAnimation(); + mArrowImg.startAnimation(rotateUp); + rotated = true; + } + } else if (y < mHeaderHeight) { + if (rotated) { + mArrowImg.clearAnimation(); + mArrowImg.startAnimation(rotateDown); + rotated = false; + } + mRefreshTV.setText("右滑刷新"); + } + } + } + + @Override + public void onRelease() { + + } + + @Override + public void onComplete() { + rotated = false; + mSuccessImg.setVisibility(VISIBLE); + mArrowImg.clearAnimation(); + mArrowImg.setVisibility(GONE); + mAnimDrawable.stop(); + mLoadingImg.setVisibility(GONE); + mRefreshTV.setText("刷新完成"); + } + + @Override + public void onReset() { + rotated = false; + mSuccessImg.setVisibility(GONE); + mArrowImg.clearAnimation(); + mArrowImg.setVisibility(VISIBLE); + mAnimDrawable.stop(); + mLoadingImg.setVisibility(GONE); + } + +} diff --git a/app/src/main/res/drawable/refresh_loading.xml b/app/src/main/res/drawable/refresh_loading.xml new file mode 100644 index 0000000..8b18765 --- /dev/null +++ b/app/src/main/res/drawable/refresh_loading.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 10d525e..db5d8f1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,24 +1,33 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> - + android:layout_height="wrap_content" + android:text="下拉刷新 上拉加载更多" + android:layout_marginTop="64dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp"/> - +