(在虚拟机或者真机跑起来是很流畅,可能是录制视频做成gif的时候有点卡顿。)
实现原理
其实只需要重写下它的拦截方法的逻辑就好了,ScrollView的拦截方法onInterceptTouchEvent一般情况下都默认地返回false,表示不拦截该事件。我们只需要在用户滑动了界面的情况下,拦截下来并移动布局就好了,当用户松开手就恢复布局。很简单
第一步:集成ScrollView重写几个构造方法;
第二步:重写下onFinishInflate方法,获得第一个子view;
第三步:在拦截方法里面处理上面所说的逻辑;
public class JellyScrollView extends ScrollView {private View inner;// 子Viewprivate float y;// 点击时y坐标private Rect normal = new Rect();// 矩形(这里只是个形式,只是用于判断是否需要动画.)private boolean isCount = false;// 是否开始计算private boolean isMoving = false;// 是否开始移动.private int top;// 拖动时时高度。private int mTouchSlop;//系统最少滑动距离public JellyScrollView(Context context) {super(context); }public JellyScrollView(Context context, AttributeSet attrs) {super(context, attrs); }public JellyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); }/*** * 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆盖了 onFinishInflate * 方法,也应该调用父类的方法,使该方法得以执行. */ @Override protected void onFinishInflate() {if (getChildCount() > 0) { inner = getChildAt(0);} } /**拦截事件*/ @Override public boolean onInterceptTouchEvent(MotionEvent ev) {if (inner != null) { int action = ev.getAction(); switch (action) {case MotionEvent.ACTION_DOWN: y = ev.getY(); top = 0; break; case MotionEvent.ACTION_UP: // 手指松开. isMoving = false; if (isNeedAnimation()) {animation(); } break;/**** 排除出第一次移动计算,因为第一次无法得知y坐标, 在MotionEvent.ACTION_DOWN中获取不到,* 因为此时是ScrollView的touch事件传递到到了ListView的子item上面.所以从第二次计算开始.* 然而我们也要进行初始化,就是第一次移动的时候让滑动距离归0. 之后记录准确了就正常执行.*/case MotionEvent.ACTION_MOVE: final float preY = y;// 按下时的y坐标 float nowY = ev.getY();// 每时刻y坐标 int deltaY = (int) (nowY - preY);// 滑动距离 if (!isCount) {deltaY = 0; // 在这里要归0. }if (Math.abs(deltaY) < mTouchSlop && top <= 0)return true;// 当滚动到最上或者最下时就不会再滚动,这时移动布局 isNeedMove();if (isMoving) {// 初始化头部矩形if (normal.isEmpty()) { // 保存正常的布局位置 normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom());} // 移动布局inner.layout(inner.getLeft(), inner.getTop() + deltaY / 3, inner.getRight(), inner.getBottom() + deltaY / 3); top += (deltaY / 6); }isCount = true; y = nowY; break; }}return super.onInterceptTouchEvent(ev); } /*** * 回缩动画 */ public void animation() {// 开启移动动画TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top);ta.setDuration(200);inner.startAnimation(ta); // 设置回到正常的布局位置inner.layout(normal.left, normal.top, normal.right, normal.bottom);normal.setEmpty(); // 手指松开要归0.isCount = false;y = 0; }// 是否需要开启动画 public boolean isNeedAnimation() {return !normal.isEmpty(); }/*** * 是否需要移动布局 * inner.getMeasuredHeight():获取的是控件的总高度 * getHeight():获取的是屏幕的高度 * * @return */ public void isNeedMove() {int offset = inner.getMeasuredHeight() - getHeight();int scrollY = getScrollY();// scrollY == 0是顶部// scrollY == offset是底部if (scrollY == 0 || scrollY == offset) { isMoving = true;} }}然后在布局里面在最外层就使用我们的JellyScrollView
<?xml version="1.0" encoding="utf-8"?><cn.ichengxi.fang.view.JellyScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/bg" android:scrollbars="none"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><TextView android:id="@+id/personal_setting_txt" android:layout_width="match_parent" android:layout_height="wrap_content" android:alpha="0.8" android:gravity="center_vertical" android:text="设置" android:textColor="@android:color/black" /></LinearLayout></cn.ichengxi.fang.view.JellyScrollView>12345678910111213141516171819202122231234567891011121314151617181920212223总结