学学下拉刷新

来源:互联网 发布:淘宝永久模板在哪里买 编辑:程序博客网 时间:2024/05/24 04:54

推荐

http://android-ultra-ptr.liaohuqiu.net/cn/


第一步 布局



因为用的都是 LinearLayout 和 FrameLayout,所以习惯于认为 Parent View 应该包含 Child View。

但这里的 PullLayout 不是。

public class PullLayout extends ViewGroup {    public PullLayout(Context context) {        super(context);    }    public PullLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    public PullLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    // 总偏移量    int offset = 0;    View header;    View content;    @Override    protected void onFinishInflate() {        // 获取 header 和 content        header = getChildAt(0);        content = getChildAt(1);        super.onFinishInflate();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // 默认处理        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        measureChildren(widthMeasureSpec, heightMeasureSpec);        Log.e("result", "onMeasure");    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        layoutHeaderView();        layoutContentView();        Log.e("result", "PullLayout: " + l + " " + t + " " + r + " " + b + " ");    }    private void layoutHeaderView() {        final int left = 0;        final int top = offset - header.getMeasuredHeight();        final int right = left + header.getMeasuredWidth();        final int bottom = top + header.getMeasuredHeight();        header.layout(left, top, right, bottom);        Log.e("result", "header: " + left + " " + top + " " + right + " " + bottom + " ");    }    private void layoutContentView() {        final int left = 0;        final int top = offset;        final int right = left + content.getMeasuredWidth();        final int bottom = top + content.getMeasuredHeight();        content.layout(left, top, right, bottom);        Log.e("result", "content: " + left + " " + top + " " + right + " " + bottom + " ");    }}
ContentView 贴着 PullLayout 的左上角,HeaderView 则隐藏在上方,没有被绘制。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:padding="50px">    <ivolianer.pulllayout.PullLayout        android:id="@+id/pullLayout"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="@android:color/holo_red_light">        <TextView            android:layout_width="match_parent"            android:layout_height="300px"            android:background="@android:color/holo_blue_dark"            android:gravity="center"            android:text="HeaderView"            android:textColor="@android:color/white"            android:textSize="20dp" />        <TextView            android:layout_width="match_parent"            android:layout_height="1200px"            android:background="@android:color/holo_blue_light"            android:gravity="center"            android:text="ContentView"            android:textColor="@android:color/white"            android:textSize="20dp" />    </ivolianer.pulllayout.PullLayout></FrameLayout>


如果调整下 mScrollY,可以看到 HeaderView 是确实存在的。

     PullLayout pullLayout = (PullLayout)findViewById(R.id.pullLayout);        pullLayout.setScrollY(-200);


日志:

04-11 11:28:12.601 28676-28676/ivolianer.pulllayout E/result: onMeasure

04-11 11:28:12.601 28676-28676/ivolianer.pulllayout E/result: PullLayout: 50 50 1030 1651

04-11 11:28:12.601 28676-28676/ivolianer.pulllayout E/result: header: 0 -300 980 0 

04-11 11:28:12.601 28676-28676/ivolianer.pulllayout E/result: content: 0 0 980 1200 



第二步 拖拽和完善

假设暂时由 PullLayout 处理所有的事件。
   float lastY;    @Override    public boolean dispatchTouchEvent(MotionEvent e) {        switch (e.getAction()) {            case MotionEvent.ACTION_DOWN:                break;            case MotionEvent.ACTION_MOVE:                // 滑动距离                float dy = e.getY() - lastY;                // 新的偏移量                int newOffset = (int) (offset + dy);                changeOffset(newOffset);                break;            case MotionEvent.ACTION_UP:                changeOffset(0);                break;        }        lastY = e.getY();        return true;    }    private void changeOffset(int offset) {        this.offset = offset;        // 会导致 onLayout 的调用        requestLayout();    }



已经能实现拖拽了,再来优化下。

比如 ContentView 不应被向上拖拽, HeaderView 不该拖拽离开 PullLayout 。

就是给 offset 加个大小的限制。





继续优化。

比如,松手后加上回弹动画。

比如,加上下拉的阻力。

比如,根据下拉距离选择执行加载动画,还是回弹动画。

比如,执行动画的过程,不应该接受到任何事件。

    float lastY;    @Override    public boolean dispatchTouchEvent(MotionEvent e) {        // 执行动画过程,屏蔽所有事件        if (animating) {            return false;        }        switch (e.getAction()) {            case MotionEvent.ACTION_DOWN:                break;            case MotionEvent.ACTION_MOVE:                // 滑动距离                float dy = e.getY() - lastY;                // 阻力                dy = dy / 2;                // 新的偏移量                int newOffset = (int) (offset + dy);                newOffset = checkOffsetRange(newOffset);                changeOffset(newOffset);                break;            case MotionEvent.ACTION_UP:                if (offset > 280) {                    doYourLoadingAnimation();                } else {                    clearOffset();                }                break;        }        lastY = e.getY();        return true;    }    private void changeOffset(int offset) {        this.offset = offset;        // 会导致 onLayout 的调用        requestLayout();    }    private int checkOffsetRange(int newOffset) {        newOffset = Math.min(300, newOffset);        newOffset = Math.max(0, newOffset);        return newOffset;    }    //    boolean animating = false;    private void doYourLoadingAnimation() {        // 缩放动画        ValueAnimator animator = ValueAnimator.ofFloat(0.9f, 1.1f, 0.9f, 1.1f, 0.9f, 1.1f, 0.9f, 1.1f, 1);        animator.setDuration(2000);        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animator) {                float scale = (Float) animator.getAnimatedValue();                header.setScaleX(scale);                if (1 == animator.getAnimatedFraction()) {                    animating = false;                    clearOffset();                }            }        });        animator.start();        animating = true;    }    private void clearOffset() {        ValueAnimator animator = ValueAnimator.ofInt(offset, 0);        animator.setDuration(300);        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animator) {                int currentOffset = (Integer) animator.getAnimatedValue();                changeOffset(currentOffset);                if (1 == animator.getAnimatedFraction()) {                    animating = false;                }            }        });        animator.start();        animating = true;    }


第三步 和 ContentView 分享事件

之前的 Content 是个 TextView ,现在换成 ScrollView。


ScrollView 失去了它一切的特性,不能滚动 ,不能 fling,因为所有的事件都被 PullLayout 消费拦截,没有进一步分发下去。

    @Override    public boolean dispatchTouchEvent(MotionEvent e) {        // 执行动画过程,屏蔽所有事件        if (animating) {            return false;        }        boolean result = true;        switch (e.getAction()) {            case MotionEvent.ACTION_DOWN:                // 把事件分发下去,但始终消费 DOWN 事件                super.dispatchTouchEvent(e);                break;            case MotionEvent.ACTION_MOVE:                // 滑动距离                float dy = e.getY() - lastY;                Log.e("result","" + dy);                // 阻力                dy = dy / 2;                // 最难的地方,谁来处理滑动事件                if (offset > 0 || offset == 0 && dy > 0 && content.getScrollY() == 0) {                    selfHandleMoveEvent(dy);                } else {                    // 坑,千万不要用 return ,lastY 的每次赋值都很重要... 否则会突然滑动一段距离什么的...                    result = contentHandleMoveEvent(e);                }                break;            case MotionEvent.ACTION_UP:                // 把事件分发下去,但始终消费 UP 事件                super.dispatchTouchEvent(e);                if (offset > 280) {                    doYourLoadingAnimation();                } else {                    clearOffset();                }                break;        }        lastY = e.getY();        return result;    }    private void selfHandleMoveEvent(float dy) {        int newOffset = (int) (offset + dy);        newOffset = checkOffsetRange(newOffset);        changeOffset(newOffset);    }    private boolean contentHandleMoveEvent(MotionEvent e) {        return super.dispatchTouchEvent(e);    }


难点在在于,何时让 PullLayout 响应移动事件,何时让 Content 响应移动事件。

    if (offset > 0 || offset == 0 && dy > 0 && content.getScrollY() == 0) {
offset > 0,说明是一个拖拽 Header 的过程,无论向上向下。

offset == 0 && dy > 0 && mContent.getScrollY() == 0
offset == 0 header 完全隐藏, dy > 0 下拉拖拽。

mContent.get ScrollY() == 0,滚动条已经滚动到头部了,不需要再消费 MOVE 事件了。












0 0
原创粉丝点击