具有回弹效果的RecyclerView,RecyclerView外层可滚动容器(二)

上一篇菊花loading可以根据项目的的UI设计要求设置自己需要的样式,现在再贴一 段color[]代码,

 

private String[] color = {"#aaaaaa", "#999999", "#888888", "#777777", "#666666", "#bbbbbb"};

 

这个颜色数组可以可以很好的模仿出ios的菊花颜色,然后是菊花矩形圆角问题,  可以使用canvas.drawRoundRect( RectF rect, float rx, float ry, Paint paint),这样就可以完美的解决问题了。本来老铁说上一篇菊花loading会火的,因为网上没有写过类似的文章,可惜到现在也没火。哎,废话不多了,上一篇说过要给大家一个全新的弹性容器,不过在此之前我还是要想说一句,view的事件分发机制,大家一定要弄清楚,这样在阅读下面代码的时候会事半功倍。

 

效果图:


实现原理:定义一个外层容器,内层可以是recyclerview,Listview,ScrollView等可以滑动的组件,主要是在事件分发上动手脚,在事件分发dispatchTouchEvent中根据内层recyclerview中可见的item是否处于顶部或者底部来处理事件分发(自己消费掉或者将事件交由当前view的onlnterceptTouchEvent进行事件拦截,而此处事件不拦截,交给recycelrview进行消费),还是那句话把事件分发搞明白,具体查看具有回弹效果的RecyclerView,RecyclerView外层可滚动容器,不会的一定要搞明白了,哈哈,我实诚吧。

还有下面的弹性是通过onlayout来实现的,和上一篇的弹性容器不一样,大家可以参考上一篇进行修改,重要的点会在下面代码中给大家进行讲解

 

/**
 * Created by Sick on 2016/12/1.
 */
public class PullRecyclerViewGroup extends LinearLayout implements ViewTreeObserver.OnGlobalLayoutListener {


    /**
     * 滚动时间
     */
    private static final long ANIM_TIME = 400;

    //listview 或者recyclerview或者ScrollView
    private View childView;

    // 用于记录正常的布局位置
    private Rect originalRect = new Rect();

    //滚动时,移动的view和位置
    private List mMoveViews = new ArrayList();
    private List mMoveRects = new ArrayList();

    // 在手指滑动的过程中记录是否移动了布局
    private boolean isMoved = false;

    // 如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值
    private float startY;

    //阻尼
    private static final float OFFSET_RADIO = 0.5f;

    private boolean isRecyclerReuslt = false;


    public PullRecyclerViewGroup(Context context) {
        this(context, null);
    }

    public PullRecyclerViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullRecyclerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //关闭右侧滚动条
        this.setVerticalScrollBarEnabled(false);
    }

    /**
     * 加载布局后初始化,这个方法会在加载完布局后调用
     */
    @Override
    protected void onFinishInflate() {
        //此处为容器中的子view   必须有RecyclerView、ListView、ScrollView,当然这里忽略ListView和ScrollView
        if (getChildCount() > 0) {
            for (int i = 0; i < getChildCount(); i++) {
                if (getChildAt(i) instanceof RecyclerView || getChildAt(i) instanceof ListView || getChildAt(i) instanceof ScrollView) {
                    if (childView == null) {
                        childView = getChildAt(i);
                    } else {
                        throw new RuntimeException("PullRecyclerViewGroup 中只能存在一个RecyclerView、ListView或者ScrollView");
                    }
                }
            }
        }

        if (childView == null) {
            throw new RuntimeException("PullRecyclerViewGroup 子容器中必须有一个RecyclerView、ListView或者ScrollView");
        }
        //布局重绘监听,比如华为屏幕键盘可以弹出和隐藏,改变布局,加监听就可以虽键盘弹出关闭的变化而变化
        getViewTreeObserver().addOnGlobalLayoutListener(this);

        super.onFinishInflate();
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //ScrollView中唯一的子控件的位置信息,这个位置在整个控件的生命周期中保持不变
        originalRect.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
        for (int i = 0; i < mMoveViews.size(); i++) {
            final View v = mMoveViews.get(i);
            v.addOnLayoutChangeListener(new OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    Rect rect = new Rect();
                    rect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
                    mMoveRects.add(rect);
                    v.removeOnLayoutChangeListener(this);
                }
            });
        }
    }

    /**
     * 事件分发
     */

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (childView == null) {
            return super.dispatchTouchEvent(ev);
        }

        boolean isTouchOutOfScrollView = ev.getY() >= originalRect.bottom || ev.getY() <= originalRect.top; //如果当前view的Y上的位置
        if (isTouchOutOfScrollView) {//如果不在view的范围内
            if (isMoved) {      //当前容器已经被移动
                recoverLayout();
            }
            return true;
        }

        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //记录按下时的Y
                startY = ev.getY();
            case MotionEvent.ACTION_MOVE:
                float nowY = ev.getY();
                int scrollY = (int) (nowY - startY);
                if ((isCanPullDown() && scrollY > 0) || (isCanPullUp() && scrollY < 0) || (isCanPullDown() && isCanPullUp())) {
                    int offset = (int) (scrollY * OFFSET_RADIO);
                    childView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset);
                    for (int i = 0; i < mMoveViews.size(); i++) {
                        if (mMoveViews.get(i) != null) {
                            mMoveViews.get(i).layout(mMoveRects.get(i).left, mMoveRects.get(i).top + offset, mMoveRects.get(i).right, mMoveRects.get(i).bottom + offset);
                        }
                    }
                    isMoved = true;
                    isRecyclerReuslt = false;
                    return true;
                } else {
                    startY = ev.getY();
                    isMoved = false;
                    isRecyclerReuslt = true;
                    recoverLayout();
                    return super.dispatchTouchEvent(ev);
                }
            case MotionEvent.ACTION_UP:

                if (isMoved) {
                    recoverLayout();
                }

                if (isRecyclerReuslt) {
                    return super.dispatchTouchEvent(ev);
                } else {
                    return true;
                }
            default:
                return true;
        }

    }

    /**
     * 位置还原
     */
    private void recoverLayout() {

        if (!isMoved) {
            return;//如果没有移动布局,则跳过执行
        }
        
        for (int i = 0; i < mMoveViews.size(); i++) {
            if (mMoveRects.get(i) != null) {
                TranslateAnimation anims = new TranslateAnimation(0, 0, mMoveViews.get(i).getTop(), mMoveRects.get(i).top);
                anims.setDuration(ANIM_TIME);
                mMoveViews.get(i).startAnimation(anims);
                mMoveViews.get(i).layout(mMoveRects.get(i).left, mMoveRects.get(i).top, mMoveRects.get(i).right, mMoveRects.get(i).bottom);
            }

        }

        TranslateAnimation anim = new TranslateAnimation(0, 0, childView.getTop() - originalRect.top, 0);
        anim.setDuration(ANIM_TIME);
        childView.startAnimation(anim);

        childView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);
        
        isMoved = false;
    }

    /**
     * 容器的的事件都在事件分发中处理,这里处理的是事件分发传递过来的事件,
     *
     * 传递过来的为RecyclerVIew的事件  不拦截,直接交给reyclerview处理
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;  //不拦截  直接传递给子的view
    }


    /**
     * 判断是否可以下拉
     * @return
     */
    private boolean isCanPullDown() {

        final RecyclerView.Adapter adapter = ((RecyclerView) childView).getAdapter();
        if (null == adapter) {
            return true;
        }
        //spf  ========================================================
        final int firstVisiblePosition = ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findFirstVisibleItemPosition();
        if (firstVisiblePosition != 0 && adapter.getItemCount() != 0) {
            return false;
        }
        //===================================================================================
        int mostTop = (((RecyclerView) childView).getChildCount() > 0) ? ((RecyclerView) childView).getChildAt(0).getTop() : 0;
        return mostTop >= 0;
    }


    /**
     * 判断是否可以上拉
     * @return
     */
    private boolean isCanPullUp() {
        final RecyclerView.Adapter adapter = ((RecyclerView) childView).getAdapter();

        if (null == adapter) {
            return true;
        }

        final int lastItemPosition = adapter.getItemCount() - 1;
        final int lastVisiblePosition = ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findLastVisibleItemPosition();

        if (lastVisiblePosition >= lastItemPosition) {
            final int childIndex = lastVisiblePosition - ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findFirstVisibleItemPosition();
            final int childCount = ((RecyclerView) childView).getChildCount();
            final int index = Math.min(childIndex, childCount - 1);
            final View lastVisibleChild = ((RecyclerView) childView).getChildAt(index);
            if (lastVisibleChild != null) {
                return lastVisibleChild.getBottom() <= childView.getBottom() - childView.getTop();
            }
        }

        return false;
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onGlobalLayout() {
        //华为手机屏幕下方的返回、home键显示隐藏改变布局
        requestLayout();
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    /**
     * 跟随弹性移动的view
     * @param view
     */
    public void setMoveViews(View view) {
        this.mMoveViews.add(view);
        requestLayout();
    }

}

 

上面就是核心的代码了,调用记得给标题栏设置随着view的移动而移动,即setMoveViews()

 

 

 

你可能感兴趣的:(android)