feat:SwipeLayout

This commit is contained in:
pengfei.zhou 2019-11-25 20:35:11 +08:00
parent 9c6802dccf
commit 055699d221
3 changed files with 51 additions and 254 deletions

View File

@ -54,7 +54,7 @@ public class MainActivity extends AppCompatActivity {
});
swipeLayout.setBackgroundColor(Color.YELLOW);
swipeLayout.getRefreshView().setBackgroundColor(Color.RED);
swipeLayout.setPullDownHeight(100);
swipeLayout.setPullDownHeight(300);
RecyclerView recyclerView = findViewById(R.id.root);
recyclerView.setBackgroundColor(Color.WHITE);
recyclerView.setLayoutManager(new LinearLayoutManager(this));

View File

@ -1,159 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pub.doric.pullable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.view.View;
import android.view.animation.Animation;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
/**
* Private class created to work around issues with AnimationListeners being
* called before the animation is actually complete and support shadows on older
* platforms.
*/
class CircleImageView extends AppCompatImageView {
private static final int KEY_SHADOW_COLOR = 0x1E000000;
private static final int FILL_SHADOW_COLOR = 0x3D000000;
// PX
private static final float X_OFFSET = 0f;
private static final float Y_OFFSET = 1.75f;
private static final float SHADOW_RADIUS = 3.5f;
private static final int SHADOW_ELEVATION = 4;
private Animation.AnimationListener mListener;
int mShadowRadius;
CircleImageView(Context context, int color) {
super(context);
final float density = getContext().getResources().getDisplayMetrics().density;
final int shadowYOffset = (int) (density * Y_OFFSET);
final int shadowXOffset = (int) (density * X_OFFSET);
mShadowRadius = (int) (density * SHADOW_RADIUS);
ShapeDrawable circle;
if (elevationSupported()) {
circle = new ShapeDrawable(new OvalShape());
ViewCompat.setElevation(this, SHADOW_ELEVATION * density);
} else {
OvalShape oval = new OvalShadow(mShadowRadius);
circle = new ShapeDrawable(oval);
setLayerType(View.LAYER_TYPE_SOFTWARE, circle.getPaint());
circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
KEY_SHADOW_COLOR);
final int padding = mShadowRadius;
// set padding so the inner image sits correctly within the shadow.
setPadding(padding, padding, padding, padding);
}
circle.getPaint().setColor(color);
ViewCompat.setBackground(this, circle);
}
private boolean elevationSupported() {
return android.os.Build.VERSION.SDK_INT >= 21;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!elevationSupported()) {
setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight()
+ mShadowRadius * 2);
}
}
public void setAnimationListener(Animation.AnimationListener listener) {
mListener = listener;
}
@Override
public void onAnimationStart() {
super.onAnimationStart();
if (mListener != null) {
mListener.onAnimationStart(getAnimation());
}
}
@Override
public void onAnimationEnd() {
super.onAnimationEnd();
if (mListener != null) {
mListener.onAnimationEnd(getAnimation());
}
}
/**
* Update the background color of the circle image view.
*
* @param colorRes Id of a color resource.
*/
public void setBackgroundColorRes(int colorRes) {
setBackgroundColor(ContextCompat.getColor(getContext(), colorRes));
}
@Override
public void setBackgroundColor(int color) {
if (getBackground() instanceof ShapeDrawable) {
((ShapeDrawable) getBackground()).getPaint().setColor(color);
}
}
private class OvalShadow extends OvalShape {
private RadialGradient mRadialGradient;
private Paint mShadowPaint;
OvalShadow(int shadowRadius) {
super();
mShadowPaint = new Paint();
mShadowRadius = shadowRadius;
updateRadialGradient((int) rect().width());
}
@Override
protected void onResize(float width, float height) {
super.onResize(width, height);
updateRadialGradient((int) width);
}
@Override
public void draw(Canvas canvas, Paint paint) {
final int viewWidth = CircleImageView.this.getWidth();
final int viewHeight = CircleImageView.this.getHeight();
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint);
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint);
}
private void updateRadialGradient(int diameter) {
mRadialGradient = new RadialGradient(diameter / 2, diameter / 2,
mShadowRadius, new int[]{FILL_SHADOW_COLOR, Color.TRANSPARENT},
null, Shader.TileMode.CLAMP);
mShadowPaint.setShader(mRadialGradient);
}
}
}

View File

@ -1,5 +1,7 @@
package pub.doric.pullable;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@ -17,7 +19,6 @@ import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.NestedScrollingChild;
import androidx.core.view.NestedScrollingChildHelper;
@ -69,8 +70,6 @@ public class DoricSwipeLayout extends ViewGroup implements NestedScrollingParent
private static final int ANIMATE_TO_START_DURATION = 200;
// Default background for the progress spinner
private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;
// Default offset in dips from the top of the view to where the progress spinner should stop
private static final int DEFAULT_CIRCLE_TARGET = 64;
@ -159,17 +158,55 @@ public class DoricSwipeLayout extends ViewGroup implements NestedScrollingParent
}
};
private int mPullDownHeight = 0;
private ValueAnimator headerViewAnimator;
void reset() {
mRefreshView.stopAnimation();
mRefreshView.setVisibility(View.GONE);
// Return the circle to its start position
if (mScale) {
setAnimationProgress(0 /* animation complete and view is hidden */);
} else {
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop);
if (headerViewAnimator != null && headerViewAnimator.isRunning()) {
headerViewAnimator.cancel();
}
mCurrentTargetOffsetTop = mRefreshView.getTop();
headerViewAnimator = ValueAnimator.ofInt(mRefreshView.getBottom(), 0);
headerViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentTargetOffsetTop = (int) animation.getAnimatedValue()
- mRefreshView.getMeasuredHeight();
mRefreshView.requestLayout();
}
});
headerViewAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mRefreshView.stopAnimation();
mRefreshView.setVisibility(View.GONE);
// Return the circle to its start position
if (mScale) {
setAnimationProgress(0 /* animation complete and view is hidden */);
} else {
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop);
}
mCurrentTargetOffsetTop = mRefreshView.getTop();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
headerViewAnimator.setDuration(SCALE_DOWN_DURATION);
headerViewAnimator.start();
}
@Override
@ -186,80 +223,6 @@ public class DoricSwipeLayout extends ViewGroup implements NestedScrollingParent
reset();
}
/**
* The refresh indicator starting and resting position is always positioned
* near the top of the refreshing content. This position is a consistent
* location, but can be adjusted in either direction based on whether or not
* there is a toolbar or actionbar present.
* <p>
* <strong>Note:</strong> Calling this will reset the position of the refresh indicator to
* <code>start</code>.
* </p>
*
* @param scale Set to true if there is no view at a higher z-order than where the progress
* spinner is set to appear. Setting it to true will cause indicator to be scaled
* up rather than clipped.
* @param start The offset in pixels from the top of this view at which the
* progress spinner should appear.
* @param end The offset in pixels from the top of this view at which the
* progress spinner should come to rest after a successful swipe
* gesture.
*/
public void setProgressViewOffset(boolean scale, int start, int end) {
mScale = scale;
mOriginalOffsetTop = start;
mSpinnerOffsetEnd = end;
mUsingCustomStart = true;
reset();
mRefreshing = false;
}
/**
* @return The offset in pixels from the top of this view at which the progress spinner should
* appear.
*/
public int getProgressViewStartOffset() {
return mOriginalOffsetTop;
}
/**
* @return The offset in pixels from the top of this view at which the progress spinner should
* come to rest after a successful swipe gesture.
*/
public int getProgressViewEndOffset() {
return mSpinnerOffsetEnd;
}
/**
* The refresh indicator resting position is always positioned near the top
* of the refreshing content. This position is a consistent location, but
* can be adjusted in either direction based on whether or not there is a
* toolbar or actionbar present.
*
* @param scale Set to true if there is no view at a higher z-order than where the progress
* spinner is set to appear. Setting it to true will cause indicator to be scaled
* up rather than clipped.
* @param end The offset in pixels from the top of this view at which the
* progress spinner should come to rest after a successful swipe
* gesture.
*/
public void setProgressViewEndTarget(boolean scale, int end) {
mSpinnerOffsetEnd = end;
mScale = scale;
mRefreshView.invalidate();
}
/**
* Sets a custom slingshot distance.
*
* @param slingshotDistance The distance in pixels that the refresh indicator can be pulled
* beyond its resting position. Use
* {@link #DEFAULT_SLINGSHOT_DISTANCE} to reset to the default value.
*/
public void setSlingshotDistance(@Px int slingshotDistance) {
mCustomSlingshotDistance = slingshotDistance;
}
/**
* Simple constructor to use when creating a SwipeRefreshLayout from code.
*
@ -308,6 +271,8 @@ public class DoricSwipeLayout extends ViewGroup implements NestedScrollingParent
public void setPullDownHeight(int height) {
mPullDownHeight = height;
mOriginalOffsetTop = mCurrentTargetOffsetTop = -height;
mSpinnerOffsetEnd = height;
mTotalDragDistance = height;
}
@Override
@ -441,15 +406,6 @@ public class DoricSwipeLayout extends ViewGroup implements NestedScrollingParent
}
}
/**
* Set the distance to trigger a sync in dips
*
* @param distance
*/
public void setDistanceToTriggerSync(int distance) {
mTotalDragDistance = distance;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int width = getMeasuredWidth();