不知道怎么写标题了

一直想实现一个下拉刷新与上拉加载,然而自己又比较懒.以前都是使用开源的框架,现在想自己实现。

这个只是用于简单记录和实现上下拉动效果,可参考以及copy修改。当然要用来做上拉加载与下拉刷新还有很多事情要做。后续的继续实现不知道什么时候完成,先记录一下,避免又弃坑了。


参考:
https://github.com/HomHomLin/SlidingLayout/
https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh

编写记录:

/**
 * Created by itzhu on 2017/6/7 0007.
 * desc 参考:https://github.com/HomHomLin/SlidingLayout/
 * <p>
 * <p>
 * <p 2017-6-8>
 * 此实验失败,带下拉和上拉的通用控件编写失败。
 * 因为上下拉没有事件拦截,当ACTION_DOWN事件没有拦截时,不管手指有没有滑动,按下时所在的按钮会一直处于pressed状态。
 * 这个没有找到解决的办法。
 * <p>
 * <p 2017-6-9>
 * 查看这个开源框架
 * {android-Ultra-Pull-To-Refresh  https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh}的PtrFrameLayout类,
 * 发现它在移动的过程copy MotionEvent,然后发送出去,试了一下,成功。这个框架的不足之处就是没有处理好多个手指界面跳动问题,可能就是几行代码的事情... :)
 * 这样一个简单的上下拉动界面就完成了。
 * <p>
 * <p 总结1:2017-6-9 09:47>
 * <p>
 * 这里的多个手指点击,滑动是照着RecyclerView里面的onTouchEvent里面的写的,解决多个手指点击UI跳动问题。(当某个问题出现时,如果android自带控件没有问题,不妨参考一下它的源码实现...)
 * <p>
 * 完成这个初步的上下拉动,尝试了onInterceptTouchEvent 和onTouchEvent 实现,参考(SlidingLayout),
 * 发现当出现上下拉动的时候,childView是不会滚动的。这个是因为尝试了onInterceptTouchEvent返回true之后,事件会传递给自己的onTouchEvent,不会再传给childView.
 * 而我们的事件是需要在view与childView之间来回传递,就只能在dispatchTouchEvent里面处理了。
 * 1.这里没有测试childView的横向滚动,应该会出问题,后面再继续优化
 * 2.targeView应该自己设置,不能写死
 * 3.没有暴露任何方法给别人
 * ...
 * 慢点来。
 * <p>
 * 发现问题,解决问题,这个对要学习View滑动效果的新手来说应该会有点用处,自己参考吧。
 * 不足之处应该是chileView的横向滑动应该不行,会出现问题,这个也应该可以参考RecycleView里面滑动时对X的处理。
 * 先记录这段代码,后面一步一步优化,使其通用。
 */

下面直接贴代码了,代码写的很简单,应该很容易看懂。很多都是直接copy那两个开源框架里面的。

public class PullLayout extends FrameLayout {

    private static final String TAG = "PullLayout";

    /**
     * 标记的pointY
     * 滑动距离以此为原点计算
     */
    private int markPointY = 0;

    /**
     * 滑动手指ID
     */
    private int mScrollPointerId = -1;

    /**
     * 偏移位置
     */
    private int offsetY = 0;

    /**
     * 初始偏移量
     * 当多个手指按下状态变换时,初始偏移量改变
     */
    private int initOffsetY = 0;

    /**
     * 需要检测View的滑动状态
     */
    private View targeView;

    private IBindChildScrollListener childScrollListener;

    /**
     * 当前状态,0-未偏移状态  1-偏移量大于0  -1-偏移量小于0
     * <p>
     * 因为滑动的offset在快速往返滑动不一定会有为0的状态,故使用此字段标志滑动状态的切换,以此来初始化标记点markPointY{@link #markPointY}
     */
    private static int scrollState = 0;

    public PullLayout(@NonNull Context context) {
        super(context);
    }

    public PullLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public PullLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        //检测view能否向上滑动或者能否向下滑动
        final int action = MotionEventCompat.getActionMasked(event);
        final int actionIndex = MotionEventCompat.getActionIndex(event);

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "ACTION_DOWN");
                mScrollPointerId = event.getPointerId(0);// 获取索引为0的手指id
                markPointY = (int) (event.getY() + 0.5f);
                initOffsetY = 0;
                scrollState = 0;
                Log.e(TAG, "markPointY->" + markPointY);
                super.dispatchTouchEvent(event);
                return true;
            //break;
            case MotionEvent.ACTION_POINTER_DOWN:
                Log.e(TAG, "ACTION_POINTER_DOWN");
                mScrollPointerId = event.getPointerId(actionIndex);
                markPointY = (int) (event.getY(actionIndex) + 0.5f);
                initOffsetY = getCurrentOffsetY();
                Log.e(TAG, "markPointY->" + markPointY);
                break;
            case MotionEvent.ACTION_MOVE:
                final int index = event.findPointerIndex(mScrollPointerId);
                if (index < 0) {
                    Log.e(TAG, "Error processing scroll; pointer index for id " + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                    return false;
                }
                offsetY = (int) (event.getY(index) - markPointY) + initOffsetY;//偏移距离

                /*----- // TODO: 2017/6/9 0009 阻尼效果-简单除2,高级一点的自己去实现-----*/
                offsetY = offsetY / 2;
                /*-----------*/
                Log.e(TAG, "offsetY-->" + offsetY);
                if (offsetY > 0 && !childScrollListener.canScrollUp()) {
                    //Log.e(TAG, "dispatchTouchEvent----A->" + offsetY);
                    if (scrollState <= 0) {
                        initPointY(event, index);
                        scrollState = 1;
                        //开始下拉刷新
                        sendCancelEvent(event);
                        return true;
                    }
                    smoothTo(targeView, offsetY, 0);
                    //消费滑动,不往下传递
                    return true;
                } else if (offsetY < 0 && !childScrollListener.canScrollDown()) {
                    //Log.d(TAG, "dispatchTouchEvent----B->" + offsetY);
                    if (scrollState >= 0) {
                        initPointY(event, index);
                        scrollState = -1;
                        //开始上拉加载
                        sendCancelEvent(event);
                        return true;
                    }
                    smoothTo(targeView, offsetY, 0);
                    return true;
                } else if (scrollState != 0) {
                    scrollState = 0;
                    smoothTo(targeView, 0, 0);
                    sendDownEvent(event);
                    return true;
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onPointerUp(event);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                offsetY = 0;
                initOffsetY = 0;
                smoothTo(targeView, offsetY, 0);
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    private void sendCancelEvent(MotionEvent event) {
        Log.d(TAG, "send cancel event");
        // The ScrollChecker will update position and lead to send cancel event when mLastMoveEvent is null.
        // fix #104, #80, #92
        if (event == null) {
            return;
        }
        MotionEvent last = event;
        MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime() + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_CANCEL, last.getX(), last.getY(), last.getMetaState());
        dispatchTouchEventSupper(e);
    }

    /*-------------重新设置targeView的aCTION_CANCEL和ACTION_DOWN事件- 这段代码copy自PtrFrameLayout-----------*/
    private void sendDownEvent(MotionEvent event) {
        Log.d(TAG, "send down event");
        final MotionEvent last = event;
        MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime(), MotionEvent.ACTION_DOWN, last.getX(), last.getY(), last.getMetaState());
        dispatchTouchEventSupper(e);
    }

    public boolean dispatchTouchEventSupper(MotionEvent e) {
        return super.dispatchTouchEvent(e);
    }

    /*-----------------------------------*/

    /**
     * 这里不能使用onInterceptTouchEvent
     * onInterceptTouchEvent在一次按下滑动 拦截事件后就不会被执行了。(可以理解为在action_move事件开始的很短时间内会执行,之后就不会执行了。)
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 如果onInterceptTouchEvent 没有返回true,onTouchEvent是不会执行的
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent");
        return super.onTouchEvent(event);
    }

    /**
     * 手指抬起,参考RecyclerView
     *
     * @param e
     */
    private void onPointerUp(MotionEvent e) {
        final int actionIndex = MotionEventCompat.getActionIndex(e);
        if (e.getPointerId(actionIndex) == mScrollPointerId) {
            // Pick a new pointer to pick up the slack.
            final int newIndex = actionIndex == 0 ? 1 : 0;
            mScrollPointerId = e.getPointerId(newIndex);
            markPointY = (int) (e.getY(newIndex) + 0.5f);
            initOffsetY = getCurrentOffsetY();
        }
    }

    /**
     * 初始化手指按下的位置
     *
     * @param event
     * @param index
     */
    private void initPointY(MotionEvent event, int index) {
        Log.d(TAG, "initPointY");
        smoothTo(targeView, 0, 0);
        initOffsetY = 0;
        markPointY = (int) (event.getY(index) + 0.5f);
    }

    /**
     * 获取当前targeView的偏移量
     *
     * @return
     */
    public int getCurrentOffsetY() {
        return (int) (getTranslationY(targeView) + 0.5f);
    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (getChildCount() == 0) return;
         TODO: 2017/6/9 0009 只是简单的获取childView
        if (targeView == null) ensureTarget();
    }

    /**
     * 获取targeview
     */
    private void ensureTarget() {
        if (targeView == null) {
            targeView = getChildAt(getChildCount() - 1);
            childScrollListener = new SimpleChildScrollListener(targeView);
        }
    }

    /**
     * 得到当前view的偏移量
     *
     * @param view
     * @return
     */
    private float getTranslationY(View view) {
        if (view != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB)
            return view.getTranslationY();
        return 0;
    }

    private void smoothTo(View view, float y, long duration) {
        if (view == null) return;
        view.clearAnimation();
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            android.animation.ObjectAnimator.ofFloat(view, "translationY", y).setDuration(duration).start();
        }
    }

}

下面两个直接在slideLayout里面copy的,就是判断view能否继续上下滑动

public interface IBindChildScrollListener {

    /*能否向上滑动*/
    boolean canScrollUp();

    /*能否向下滑动*/
    boolean canScrollDown();
}
public class SimpleChildScrollListener implements IBindChildScrollListener {
    private View targeView;

    public SimpleChildScrollListener(View targeView) {
        this.targeView = targeView;
    }

    @Override
    public boolean canScrollUp() {
        if (targeView == null) return false;
        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            if (targeView instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) targeView;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return ViewCompat.canScrollVertically(targeView, -1) || targeView.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(targeView, -1);
        }
    }

    @Override
    public boolean canScrollDown() {
        if (targeView == null) return false;
        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            if (targeView instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) targeView;
                return absListView.getChildCount() > 0 && absListView.getAdapter() != null
                        && (absListView.getLastVisiblePosition() < absListView.getAdapter().getCount() - 1 || absListView.getChildAt(absListView.getChildCount() - 1)
                        .getBottom() < absListView.getPaddingBottom());
            } else {
                return ViewCompat.canScrollVertically(targeView, 1) || targeView.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(targeView, 1);
        }
    }
}

这里简单的xml里面添加就好了,recyclerview和ListView都可以
记得要修改这个quickly.common.me.customview.layout.pull.PullLayout

<quickly.common.me.customview.layout.pull.PullLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/slidingLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#000000"
        android:gravity="center_horizontal"
        android:padding="15dp"
        android:text="pull view"
        android:textColor="@color/white" />

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FFFFFF">

    </ListView>
</quickly.common.me.customview.layout.pull.PullLayout>