1、先看效果图
二、实现原理
通过自定义ScrollView 然后在通过自定义LinearLayout ,然后去修改其中的参数,然后在添加平移、缩放或者透明度渐变效果就能实现
三、实现
1、首先继承ScrollView ,并重写 onScrollChanged方法
public class DiscrollView extends ScrollView {
public DiscrollView(@NonNull Context context) {
super(context);
}
public DiscrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DiscrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private DiscrollViewContent mContent;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//first first take all the parent height
View first = mContent.getChildAt(0);
if (first != null) {
first.getLayoutParams().height = getHeight();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() != 1) {
throw new IllegalStateException("Discrollview must host one child.");
}
View content = getChildAt(0);
if (!(content instanceof DiscrollViewContent)) {
throw new IllegalStateException("Discrollview must host a DiscrollViewContent.");
}
mContent = (DiscrollViewContent) content;
if (mContent.getChildCount() < 2) {
throw new IllegalStateException("Discrollview must have at least 2 children.");
}
}
private int getAbsoluteBottom() {
View last = getChildAt(getChildCount() - 1);
if (last == null) {
return 0;
}
return last.getBottom();
}
@Override
protected void onScrollChanged(int l, int top, int oldl, int oldt) {
super.onScrollChanged(l, top, oldl, oldt);
int scrollViewHeight = getHeight();
int scrollViewBottom = getAbsoluteBottom();
int scrollViewHalfHeight = scrollViewHeight / 2;
//判断当前控件,然后判断是不是有我们自定义的控件
for (int index = 1; index < mContent.getChildCount(); index++) {
View child = mContent.getChildAt(index);
if (!(child instanceof DiscrollvableInterface)) {
//不是跳出当前循环,进入下一循环
continue;
}
DiscrollvableInterface discrollvable = (DiscrollvableInterface) child;
int discrollvableTop = child.getTop();
int discrollvableHeight = child.getHeight();
int discrollvableAbsoluteTop = discrollvableTop - top;
if (scrollViewBottom - child.getBottom() < discrollvableHeight + scrollViewHalfHeight) {
if (discrollvableAbsoluteTop <= scrollViewHeight) {
int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;
discrollvable.onDiscrollview(clamp(visibleGap / (float) discrollvableHeight, 0.0f, 1.0f));
} else {
discrollvable.onResetDiscrollview();
}
} else {
if (discrollvableAbsoluteTop <= scrollViewHalfHeight) {
int visibleGap = scrollViewHalfHeight - discrollvableAbsoluteTop;
discrollvable.onDiscrollview(clamp(visibleGap / (float) discrollvableHeight, 0.0f, 1.0f));
} else {
discrollvable.onResetDiscrollview();
}
}
}
}
private float clamp(float value, float max, float min) {
return Math.max(Math.min(value, min), max);
}
}
2、因为我们自定义的属性,在原来xml中是没有的,所以就必须的让该属性去得到原来的值,并且在LayoutParams中构造我们有的带了自定义属性的Parmas,因为在获取xml的属性中通过源码得知 generateLayoutParams很关键,在这里可以得到xml中所有的参数,因此,我们可以重写该方法,并且组合自己需要的Parmas
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
//得到xml里边过来的参数
return new MyLayoutParams(getContext(), attrs);
}
public static class MyLayoutParams extends LinearLayout.LayoutParams {
private int mDiscrollviewFromBgColor;
private int mDiscrollviewToBgColor;
private float mDiscrollviewThreshold;
public boolean mDiscrollviewAlpha;
public boolean mDiscrollviewScaleX;
public boolean mDiscrollviewScaleY;
private int mDiscrollviewTranslation;
public MyLayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
try {
mDiscrollviewAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_alpha, false);
mDiscrollviewScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_scaleX, false);
mDiscrollviewScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_scaleY, false);
mDiscrollviewTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollview_translation, -1);
mDiscrollviewThreshold = a.getFloat(R.styleable.DiscrollView_LayoutParams_discrollview_threshold, 0.0f);
mDiscrollviewFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollview_fromBgColor, -1);
mDiscrollviewToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollview_toBgColor, -1);
} finally {
a.recycle();
}
}
public MyLayoutParams(int width, int height) {
super(width, height);
}
}
通过查看加载源码知道,在加载前会通过 checkLayoutParams,去检查参数
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
if (mTransition != null) {
// Don't prevent other add transitions from completing, but cancel remove
// transitions to let them complete the process before we add to the container
mTransition.cancel(LayoutTransition.DISAPPEARING);
}
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
if (mTransition != null) {
mTransition.addChild(this, child);
}
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p != null;
}
所以我们在自定义控件中的判断是不是属于自己的参数
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MyLayoutParams;
}
所以最后包裹的代码完整的是
public class DiscrollViewContent extends LinearLayout {
public DiscrollViewContent(Context context) {
super(context);
setOrientation(VERTICAL);
}
public DiscrollViewContent(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
}
public DiscrollViewContent(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOrientation(VERTICAL);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
//得到xml里边过来的参数
return new MyLayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MyLayoutParams;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
return new MyLayoutParams(lp.width, lp.height);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
//从child里边拿到自定义属性,传递到discrollvableView里边
MyLayoutParams lp = (MyLayoutParams) params;
if (!isDiscrollvable(lp)) {
//判断该view是否穿了自定义属性,不是就不需要
super.addView(child, index, params);
} else {
DiscrollvableView discrollvableChild = new DiscrollvableView(getContext());
discrollvableChild.setDiscrollviewAlpha(lp.mDiscrollviewAlpha);
discrollvableChild.setDiscrollviewTranslation(lp.mDiscrollviewTranslation);
discrollvableChild.setDiscrollviewScaleX(lp.mDiscrollviewScaleX);
discrollvableChild.setDiscrollviewScaleY(lp.mDiscrollviewScaleY);
discrollvableChild.setDiscrollviewThreshold(lp.mDiscrollviewThreshold);
discrollvableChild.setDiscrollviewFromBgColor(lp.mDiscrollviewFromBgColor);
discrollvableChild.setDiscrollviewToBgColor(lp.mDiscrollviewToBgColor);
discrollvableChild.addView(child);
super.addView(discrollvableChild, index, params);
}
}
private boolean isDiscrollvable(MyLayoutParams lp) {
return lp.mDiscrollviewAlpha ||
lp.mDiscrollviewTranslation != -1 ||
lp.mDiscrollviewScaleX ||
lp.mDiscrollviewScaleY ||
(lp.mDiscrollviewFromBgColor != -1 && lp.mDiscrollviewToBgColor != -1);
}
public static class MyLayoutParams extends LinearLayout.LayoutParams {
private int mDiscrollviewFromBgColor;
private int mDiscrollviewToBgColor;
private float mDiscrollviewThreshold;
public boolean mDiscrollviewAlpha;
public boolean mDiscrollviewScaleX;
public boolean mDiscrollviewScaleY;
private int mDiscrollviewTranslation;
public MyLayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
try {
mDiscrollviewAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_alpha, false);
mDiscrollviewScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_scaleX, false);
mDiscrollviewScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollview_scaleY, false);
mDiscrollviewTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollview_translation, -1);
mDiscrollviewThreshold = a.getFloat(R.styleable.DiscrollView_LayoutParams_discrollview_threshold, 0.0f);
mDiscrollviewFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollview_fromBgColor, -1);
mDiscrollviewToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollview_toBgColor, -1);
} finally {
a.recycle();
}
}
public MyLayoutParams(int width, int height) {
super(width, height);
}
}
}
3、得到包裹参数,然后实现对应的动画
先定义接口,拿到滑动返回的值
public interface DiscrollvableInterface {
void onDiscrollview(float ratio);
void onResetDiscrollview();
}
然后去实现
public class DiscrollvableView extends FrameLayout implements DiscrollvableInterface {
public DiscrollvableView(@NonNull Context context) {
super(context);
}
public DiscrollvableView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DiscrollvableView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public DiscrollvableView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private static final int TRANSLATION_FROM_TOP = 0x01;
private static final int TRANSLATION_FROM_BOTTOM = 0x02;
private static final int TRANSLATION_FROM_LEFT = 0x04;
;
private static final int TRANSLATION_FROM_RIGHT = 0x08;
//颜色估值器
private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();
private float mDiscrollviewThreshold;
private int mDiscrollviewFromBgColor;//背景颜色变化开始值
private int mDiscrollviewToBgColor;//背景颜色变化结束值
private boolean mDiscrollviewAlpha;//是否需要透明度动画
private int mDiscrollviewTranslation;//平移值
private boolean mDiscrollviewScaleX;//是否需要x轴方向缩放
private boolean mDiscrollviewScaleY;//是否需要y轴方向缩放
private int mWidth;//宽度
private int mHeight;//本view的高度
@Override
protected void onFinishInflate() {
super.onFinishInflate();
onResetDiscrollview();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
public void setDiscrollviewTranslation(int discrollviewTranslation) {
this.mDiscrollviewTranslation = discrollviewTranslation;
Log.i("wxf","mDiscrollviewTranslation:"+mDiscrollviewTranslation);
if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_BOTTOM) && isDiscrollviewTranslationFrom(TRANSLATION_FROM_TOP)) {
throw new IllegalArgumentException("cannot translate from bottom and top");
}
if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_LEFT) && isDiscrollviewTranslationFrom(TRANSLATION_FROM_RIGHT)) {
throw new IllegalArgumentException("cannot translate from left and right");
}
}
public void setDiscrollviewThreshold(float discrollviewThreshold) {
if (discrollviewThreshold < 0.0f || discrollviewThreshold > 1.0f) {
throw new IllegalArgumentException("threshold must be >= 0.0f and <= 1.0f");
}
this.mDiscrollviewThreshold = discrollviewThreshold;
}
public void setDiscrollviewFromBgColor(int discrollviewFromBgColor) {
mDiscrollviewFromBgColor = discrollviewFromBgColor;
}
public void setDiscrollviewToBgColor(int discrollviewToBgColor) {
mDiscrollviewToBgColor = discrollviewToBgColor;
}
public void setDiscrollviewAlpha(boolean discrollviewAlpha) {
mDiscrollviewAlpha = discrollviewAlpha;
}
public void setDiscrollviewScaleX(boolean discrollviewScaleX) {
mDiscrollviewScaleX = discrollviewScaleX;
}
public void setDiscrollviewScaleY(boolean discrollviewScaleY) {
mDiscrollviewScaleY = discrollviewScaleY;
}
private float withThreshold(float ratio) {
return (ratio - mDiscrollviewThreshold) / (1.0f - mDiscrollviewThreshold);
}
@Override
public void onDiscrollview(float ratio) {
//ratio 0~1
//控制自身的动画属性
if (ratio >= mDiscrollviewThreshold) {
ratio = withThreshold(ratio);
float ratioInverse = 1 - ratio;
if (mDiscrollviewAlpha) {
setAlpha(ratio);
}
//判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
//fromBottom
if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
setTranslationY(mHeight * ratioInverse);
}
if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_TOP)) {
setTranslationY(-mHeight * ratioInverse);
}
if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_LEFT)) {
setTranslationX(-mWidth * ratioInverse);
}
if (isDiscrollviewTranslationFrom(TRANSLATION_FROM_RIGHT)) {
setTranslationX(mWidth * ratioInverse);
}
if (mDiscrollviewScaleX) {
setScaleX(ratio);
}
if (mDiscrollviewScaleY) {
setScaleY(ratio);
}
if (mDiscrollviewFromBgColor!=-1&&mDiscrollviewToBgColor!=-1){
setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio,mDiscrollviewFromBgColor,mDiscrollviewToBgColor));
}
}
}
@Override
public void onResetDiscrollview() {
if(mDiscrollviewAlpha) {
setAlpha(0.0f);
}
if(isDiscrollviewTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
setTranslationY(mHeight);
}
if(isDiscrollviewTranslationFrom(TRANSLATION_FROM_TOP)) {
setTranslationY(-mHeight);
}
if(isDiscrollviewTranslationFrom(TRANSLATION_FROM_LEFT)) {
setTranslationX(-mWidth);
}
if(isDiscrollviewTranslationFrom(TRANSLATION_FROM_RIGHT)) {
setTranslationX(mWidth);
}
if(mDiscrollviewScaleX) {
setScaleX(0.0f);
}
if(mDiscrollviewScaleY) {
setScaleY(0.0f);
}
if(mDiscrollviewFromBgColor != -1 && mDiscrollviewToBgColor != -1) {
setBackgroundColor(mDiscrollviewFromBgColor);
}
}
private boolean isDiscrollviewTranslationFrom(int translationMask) {
if (mDiscrollviewTranslation == -1) {
return false;
}
return (mDiscrollviewTranslation & translationMask) == translationMask;
}
}
然后在加上自定义属性
<declare-styleable name="DiscrollView_LayoutParams">
<attr name="discrollview_alpha" format="boolean" />
<attr name="discrollview_scaleX" format="boolean" />
<attr name="discrollview_scaleY" format="boolean" />
<attr name="discrollview_threshold" format="float" />
<attr name="discrollview_fromBgColor" format="color" />
<attr name="discrollview_toBgColor" format="color" />
<attr name="discrollview_translation" />
</declare-styleable>
<attr name="discrollview_translation">
<flag name="fromTop" value="0x01" />
<flag name="fromBottom" value="0x02" />
<flag name="fromLeft" value="0x04" />
<flag name="fromRight" value="0x08" />
</attr>
注意:
1、第一张是不能做改变的,正常情况下第一张应该占满整个屏幕,然后滑动之后的子控件就能实现效果
2、这里设置的是竖直排列,你可以设置为水平排列之类的效果当时ScrollView也得改为水平的