/**
* Copyright 2016 JustWayward Team
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
*/
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;
/**
* 弹性ScrollView 上下拉超出后,手指离开后弹回的“阻尼”效果
*/
public class ReboundScrollView extends ScrollView {
private static final float MOVE_FACTOR = 0.5f;
private static final int ANIM_TIME = 300;
private View contentView;
private float startY;
private Rect originalRect = new Rect();
private boolean canPullDown = false;
private boolean canPullUp = false;
private boolean isMoved = false;
public ReboundScrollView(Context context) {
super(context);
}
public ReboundScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
if (getChildCount() > 0) {
contentView = getChildAt(0);
}
super.onFinishInflate();
}
/**
* 在该方法中获取ScrollView中的唯一子控件的位置信息 这个位置信息在整个控件的生命周期中保持不变
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (contentView == null)
return;
originalRect.set(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom());
}
/**
* 在触摸事件中, 处理上拉和下拉的逻辑
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (contentView == null) {
return super.dispatchTouchEvent(ev);
}
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
startY = ev.getY();
break;
case MotionEvent.ACTION_UP:
if (!isMoved)
break;
TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(), originalRect.top);
anim.setDuration(ANIM_TIME);
contentView.startAnimation(anim);
contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);
canPullDown = false;
canPullUp = false;
isMoved = false;
break;
case MotionEvent.ACTION_MOVE:
if (!canPullDown && !canPullUp) {
startY = ev.getY();
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
break;
}
float nowY = ev.getY();
int deltaY = (int) (nowY - startY);
boolean shouldMove = (canPullDown && deltaY > 0)
|| (canPullUp && deltaY < 0)
|| (canPullUp && canPullDown);
if (shouldMove) {
int offset = (int) (deltaY * MOVE_FACTOR);
contentView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset);
isMoved = true;
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
/**
* 判断是否滚动到顶部
*/
private boolean isCanPullDown() {
return getScrollY() == 0 || contentView.getHeight() < getHeight() + getScrollY();
}
/**
* 判断是否滚动到底部
*/
private boolean isCanPullUp() {
return contentView.getHeight() <= getHeight() + getScrollY();
}
}
使用
<com.XXXX.ReboundScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/common_bg"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foregroundGravity="center"
android:layerType="software" />
</LinearLayout>
</com.XXXX.ReboundScrollView>