仿照抖音个人主页头像回弹放大功能,结合自己的思路去实现:
实现效果如下:
- 1.自定义 NestedScrollView
重写 onFinishInflate 以及fling 方法:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setOverScrollMode(OVER_SCROLL_ALWAYS);
}
@Override
public void fling(int y) {
this.isCanOverScroll = y <= -6000;
super.fling(y);
}
- 2 触摸事件 根据手指移动距离对应改变图片大小 松开回弹
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (null != mTargetView) {
if (mTargetViewWidth <= 0 || mTargetViewHeight <= 0) {
mTargetViewWidth = mTargetView.getMeasuredWidth();
mTargetViewHeight = mTargetView.getMeasuredHeight();
}
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
//手指移开,放大的View恢复原样
isScrolling = false;
callbackView();
break;
case MotionEvent.ACTION_MOVE:
if (!isScrolling) {
if (getScrollY() == 0) {
mLastPosition = ev.getY();
} else {
break;
}
}
float value = (ev.getY() - mLastPosition) * mScaleRatio;
if (value < 0) {
break;
}
isScrolling = true;
updateTargetViewValue(value);
return true;
default:
break;
}
}
return super.onTouchEvent(ev);
}
/**
* View恢复到最初状态动画
*/
private void callbackView() {
float value = mTargetView.getMeasuredWidth() - mTargetViewWidth;
ValueAnimator animator = ValueAnimator.ofFloat(value, 0f);
animator.setDuration((long) (value * mCallbackSpeed));
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateTargetViewValue((float) animation.getAnimatedValue());
}
});
animator.start();
}
/**
* 更新View的宽高属性值
*/
private void updateTargetViewValue(float value) {
if (null == mTargetView) {
return;
}
if (mTargetViewWidth <= 0 || mTargetViewHeight <= 0) {
return;
}
ViewGroup.LayoutParams lp = mTargetView.getLayoutParams();
if (null != lp) {
lp.width = (int) (mTargetViewWidth + value);
lp.height = (int) (mTargetViewHeight * ((mTargetViewWidth + value) / mTargetViewWidth));
if (lp instanceof MarginLayoutParams) {
((MarginLayoutParams) lp).setMargins(-(lp.width - mTargetViewWidth) / 2, 0, 0, 0);
}
mTargetView.setLayoutParams(lp);
}
}
/**
* 先放大后恢复动画
*/
private void zoomAnimator() {
float value = mTargetViewWidth * mScaleRatio;
ValueAnimator enlarge = ValueAnimator.ofFloat(0f, value);
enlarge.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateTargetViewValue((float) animation.getAnimatedValue());
}
});
ValueAnimator narrow = ValueAnimator.ofFloat(value, 0f);
narrow.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateTargetViewValue((float) animation.getAnimatedValue());
}
});
AnimatorSet animationSet = new AnimatorSet();
animationSet.setDuration((long) (value * mCallbackSpeed));
animationSet.playSequentially(enlarge, narrow);
animationSet.setInterpolator(new DecelerateInterpolator());
animationSet.start();
}
- 滑动到顶部,速度很快,开启动画
/**
* @author 桂雁彬
* 顶部下拉图片放大回弹效果
*/
public class ScaleScrollView extends NestedScrollView {
/**
* 需要放大的View
*/
private View mTargetView;
/**
* 放大View的宽
*/
private int mTargetViewWidth;
/**
* 放大View的高
*/
private int mTargetViewHeight;
/**
* 上一次按下的位置
*/
private float mLastPosition;
/**
* 是否正在滑动
*/
private boolean isScrolling;
/**
* 滑到顶部是否需要回弹效果
*/
private boolean isCanOverScroll;
/**
* 放大系数
*/
private float mScaleRatio = 1.2f;
/**
* 恢复原样速度
*/
private float mCallbackSpeed = 0.2f;
public ScaleScrollView(@NonNull Context context) {
super(context);
}
public ScaleScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ScaleScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setTargetView(View targetView) {
this.mTargetView = targetView;
}
public void setScaleRatio(float ratio) {
this.mScaleRatio = ratio;
}
public void setCallbackSpeed(float speed) {
this.mCallbackSpeed = speed;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setOverScrollMode(OVER_SCROLL_ALWAYS);
}
@Override
public void fling(int y) {
this.isCanOverScroll = y <= -6000;
super.fling(y);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (null != mTargetView) {
if (mTargetViewWidth <= 0 || mTargetViewHeight <= 0) {
mTargetViewWidth = mTargetView.getMeasuredWidth();
mTargetViewHeight = mTargetView.getMeasuredHeight();
}
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
//手指移开,放大的View恢复原样
isScrolling = false;
callbackView();
break;
case MotionEvent.ACTION_MOVE:
if (!isScrolling) {
if (getScrollY() == 0) {
mLastPosition = ev.getY();
} else {
break;
}
}
float value = (ev.getY() - mLastPosition) * mScaleRatio;
if (value < 0) {
break;
}
isScrolling = true;
updateTargetViewValue(value);
return true;
default:
break;
}
}
return super.onTouchEvent(ev);
}
/**
* View恢复到最初状态动画
*/
private void callbackView() {
float value = mTargetView.getMeasuredWidth() - mTargetViewWidth;
ValueAnimator animator = ValueAnimator.ofFloat(value, 0f);
animator.setDuration((long) (value * mCallbackSpeed));
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateTargetViewValue((float) animation.getAnimatedValue());
}
});
animator.start();
}
/**
* 更新View的宽高属性值
*/
private void updateTargetViewValue(float value) {
if (null == mTargetView) {
return;
}
if (mTargetViewWidth <= 0 || mTargetViewHeight <= 0) {
return;
}
ViewGroup.LayoutParams lp = mTargetView.getLayoutParams();
if (null != lp) {
lp.width = (int) (mTargetViewWidth + value);
lp.height = (int) (mTargetViewHeight * ((mTargetViewWidth + value) / mTargetViewWidth));
if (lp instanceof MarginLayoutParams) {
((MarginLayoutParams) lp).setMargins(-(lp.width - mTargetViewWidth) / 2, 0, 0, 0);
}
mTargetView.setLayoutParams(lp);
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if (t == 0 && isCanOverScroll) {
zoomAnimator();
}
super.onScrollChanged(l, t, oldl, oldt);
}
/**
* 先放大后恢复动画
*/
private void zoomAnimator() {
float value = mTargetViewWidth * mScaleRatio;
ValueAnimator enlarge = ValueAnimator.ofFloat(0f, value);
enlarge.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateTargetViewValue((float) animation.getAnimatedValue());
}
});
ValueAnimator narrow = ValueAnimator.ofFloat(value, 0f);
narrow.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateTargetViewValue((float) animation.getAnimatedValue());
}
});
AnimatorSet animationSet = new AnimatorSet();
animationSet.setDuration((long) (value * mCallbackSpeed));
animationSet.playSequentially(enlarge, narrow);
animationSet.setInterpolator(new DecelerateInterpolator());
animationSet.start();
}
}
- 外部调用方式:
public class MainActivity extends AppCompatActivity implements ScaleScrollView.OnScrollChangeListener {
private TabLayout tab1, tab2;
private TitleLayout titleLayout;
private int colorPrimary;
private ArgbEvaluator evaluator;
private View statusView;
private int getStatusBarHeight() {
int height = 0;
int resId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
height = getResources().getDimensionPixelSize(resId);
}
return height;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
colorPrimary = ContextCompat.getColor(this, R.color.colorPrimary);
initView();
}
private void initView() {
//设置状态栏和导航栏
statusView = findViewById(R.id.statusView);
LinearLayoutCompat.LayoutParams lp = new LinearLayoutCompat.LayoutParams(LinearLayoutCompat.LayoutParams.MATCH_PARENT, getStatusBarHeight());
statusView.setLayoutParams(lp);
statusView.setBackgroundColor(Color.TRANSPARENT);
statusView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
getWindow().setNavigationBarColor(colorPrimary);
AppCompatImageView banner = findViewById(R.id.banner);
ScaleScrollView scrollView = findViewById(R.id.scrollView);
scrollView.setTargetView(banner);
scrollView.setOnScrollChangeListener(this);
tab1 = findViewById(R.id.tab1);
tab2 = findViewById(R.id.tab2);
titleLayout = findViewById(R.id.title_layout);
FullViewPager viewPager = findViewById(R.id.viewpager);
viewPager.setAdapter(new TabAdapter(this, getSupportFragmentManager(), getTabs()));
tab1.setupWithViewPager(viewPager);
tab2.setupWithViewPager(viewPager);
}
private List<TabItemModel> getTabs() {
List<TabItemModel> tabs = new ArrayList<>();
tabs.add(new TabItemModel("作品30", TabFragment.class.getName(), null));
tabs.add(new TabItemModel("动态30", TabFragment.class.getName(), null));
tabs.add(new TabItemModel("喜欢30", TabFragment.class.getName(), null));
return tabs;
}
@Override
public void onScrollChange(NestedScrollView v, int x, int y, int ox, int oy) {
if (null != tab1 && null != tab2 && null != titleLayout && null != statusView) {
int distance = tab1.getTop() - titleLayout.getHeight() - statusView.getHeight();
float ratio = v.getScaleY() * 1f / distance;
if (distance <= v.getScrollY()) {
ratio = 1;
if (tab2.getVisibility() != View.VISIBLE) {
tab2.setVisibility(View.VISIBLE);
statusView.setBackgroundColor(colorPrimary);
}
} else {
if (tab2.getVisibility() == View.VISIBLE) {
tab2.setVisibility(View.INVISIBLE);
statusView.setBackgroundColor(Color.TRANSPARENT);
}
}
if (null == evaluator) {
evaluator = new ArgbEvaluator();
}
titleLayout.setBackgroundColor((int) evaluator.evaluate(ratio, Color.TRANSPARENT, colorPrimary));
titleLayout.setTitleColor((int) evaluator.evaluate(ratio, Color.TRANSPARENT, Color.WHITE));
}
}
}