feat:SwipeLayout
This commit is contained in:
parent
9c6802dccf
commit
055699d221
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
Reference in New Issue
Block a user