通过ViewGroup实现下拉刷新和上拉加载,2018/2/12 06

为了重新了解一下自定义ViewGroup,自己实现了一个下拉刷新view,冲突的解决Recyclerview滚动到底部和顶部的处理全部放在了父view 中,滚动实现使用的是Scroller,所以使整个控件还有类似ios的弹性效果,代码很简单,使用也很简单,刷新的头布局和脚布局都可以在布局文件中直接添加,处理。
其中内容也可以不是Recyclerview,直接写在布局里就行,布局效果类似LinearLayout 但只能是竖直方向堆叠。
初始化布局等代码:

private Scroller mScroller;
    private RecyclerView mRecyclerView;
    private RefreshListener mListener;

    private int mTotalHeight; //子view加在一起总高度
    private  HashMap mViewMarginTop; //所有子view marginTop距离
    private  HashMap mViewMarginBottom;//所有子view marginBottom距离
    private float mStartY;//手指落下位置 移动之后更新
    private float mStartX;
    private int mMoveHeight; //移动的总高度
    private int mState; // 刷新状态
    private static  int NORMAL=0; // 正常状态
    private static  int REFRESH=1; // 刷新中
    private static  int LOAD=2; //上拉加载中
    private boolean isPull; //是否拖动中
    private int mCanScrollDistance=-1; //可拖动最远距离
    private boolean mCanLoadMore;//可否上拉加载

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

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

    public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mViewMarginTop= new HashMap<>();
        mViewMarginBottom= new HashMap<>();
        mScroller = new Scroller(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for (int i = 0; i < getChildCount(); i++) {
            //测量每一个子
            measureChild(getChildAt(i),widthMeasureSpec,heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int layoutWidth = getMeasuredWidth();
        mTotalHeight=t;//初始化高度
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            if (view.getVisibility() != GONE) {
                LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
                int viewHeight = view.getMeasuredHeight();
                int viewWidth = view.getMeasuredWidth();
                if (i != 0) {
                    mTotalHeight += viewHeight;
                    if (mViewMarginTop.get(i) != null) {//总高度加上子view距离上方的距离
                        mTotalHeight += mViewMarginTop.get(i);
                    }
                }
                if(layoutParams.mCenter) {
                    int marginHorizontal = (layoutWidth - viewWidth-getPaddingLeft()-getPaddingRight()) / 2;
                    if(mTotalHeight>getMeasuredHeight()) {//如果子view总高度大于父控件总高度则最大值为父控件高度
                        int viewMarginBottom=0;
                        if (mViewMarginBottom.get(i) != null) {
                            viewMarginBottom = mViewMarginBottom.get(i);
                        }
                        if((mTotalHeight-viewHeight)>=getMeasuredHeight()) {//判断如果子view在父控件高度之外 绘制的位置
                            view.layout(l + marginHorizontal + getPaddingLeft(), mTotalHeight - viewHeight, viewWidth + marginHorizontal + l + getPaddingLeft(), mTotalHeight);
                        }else {
                            view.layout(l + marginHorizontal + getPaddingLeft(), mTotalHeight - viewHeight, viewWidth + marginHorizontal + l + getPaddingLeft(), getMeasuredHeight() - viewMarginBottom);
                        }
                    }else{
                        view.layout(l + marginHorizontal + getPaddingLeft(), mTotalHeight - viewHeight, viewWidth + marginHorizontal + l + getPaddingLeft(), mTotalHeight);
                    }
                }else{
                    if(mTotalHeight>getMeasuredHeight()) {//如果子view总高度大于父控件总高度则最大值为父控件高度
                        int viewMarginBottom=0;
                        if (mViewMarginBottom.get(i) != null) {
                             viewMarginBottom = mViewMarginBottom.get(i);
                        }
                        if((mTotalHeight-viewHeight)>=getMeasuredHeight()) {//判断如果子view在父控件高度之外 绘制的位置
                            view.layout(l + paddingLeft, mTotalHeight - viewHeight, viewWidth + l + paddingLeft + paddingRight, mTotalHeight);
                        }else{
                            view.layout(l + paddingLeft, mTotalHeight - viewHeight, viewWidth + l + paddingLeft + paddingRight, getMeasuredHeight() - viewMarginBottom);
                            mTotalHeight=getMeasuredHeight() - viewMarginBottom;
                        }
                    }else{
                        view.layout(l + paddingLeft, mTotalHeight - viewHeight, viewWidth + l + paddingLeft + paddingRight, mTotalHeight);
                    }
                }
                if (mViewMarginBottom.get(i) != null) {//总高度加上子view距离下方的距离
                    mTotalHeight += mViewMarginBottom.get(i);
                }
            }
        }

    }

获取子view属性,和自定义属性,其中自定义属性需要在values文件夹下创建attrs资源文件,仅仅只取了几个处理了一下:


<resources>
    <declare-styleable name="Refresh_Layout">
        <attr name="center_horizontal" format="boolean" />
    declare-styleable>
resources>
@Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return super.generateLayoutParams(p);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(),attrs);
    }

    /**
     * 自定义LayoutParams 添加自定义属性
     * 并可以获取到子view的属性
     */
    private  class LayoutParams extends ViewGroup.LayoutParams {
        private boolean mCenter;
        private LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            for (int i = 0; i < attrs.getAttributeCount(); i++) {
                String attributeName = attrs.getAttributeName(i);
                String attributeValue = attrs.getAttributeValue(i);
                switch (attributeName){
                    case "layout_marginTop"://单位px 保存所有子view marginTop高度
                        if(attributeValue.length()>2) {
                            mViewMarginTop.put(getChildCount(),(int) Double.parseDouble(attributeValue.substring(0, attributeValue.length() - 2)));
                        }
                        break;
                    case "layout_marginBottom"://单位px 保存所有子view marginBottom高度
                        if(attributeValue.length()>2) {
                            mViewMarginBottom.put(getChildCount(),(int) Double.parseDouble(attributeValue.substring(0, attributeValue.length() - 2)));
                        }
                        break;
                    case "center_horizontal"://自定义属性水平居中
                        mCenter = Boolean.valueOf(attributeValue);
                        break;
                }
            }
        }
    }

之后是触摸事件的处理:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if (mScroller != null && !mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                mStartY = event.getRawY();
                mStartX = event.getRawY();
                super.dispatchTouchEvent(event);
                return true;
            case MotionEvent.ACTION_MOVE:
                if(Math.abs(mMoveHeight)>mCanScrollDistance && mCanScrollDistance>0){//判断是否到达设置的极限滚动距离
                    return true;
                }
                int round = Math.round(mStartY - event.getRawY());
                int roundX = Math.round(mStartX - event.getRawX());
                if(mListener!=null){
                    mListener.moveDy(round);
                }
                if(Math.abs(roundX)>Math.abs(round)){//保证横向能够滚动
                    mStartY -= round;
                    mStartX -= roundX;
                    return super.dispatchTouchEvent(event);
                }
                if(mRecyclerView!=null) {
                    if (!mRecyclerView.canScrollVertically(-1)) {//竖直方向recyclerView可否下拉
                        if (round < 0) {
                            isPull = true;
                            dealMove(round,event);
                            return true;
                        }
                    }
                    if (!mRecyclerView.canScrollVertically(1)) {//竖直方向recyclerView可否上拉
                        if (round > 0) {//上拉
                            isPull = true;
                            dealMove(round,event);
                            return true;
                        }
                    }
                    if (mState == REFRESH || isPull) {//刷新状态,拖动状态下拦截,自己处理移动
                        dealMove(round,event);
                        return true;
                    }
                    if(mMoveHeight>0){//如果底部view还在显示则下拉是从底部view开始
                        dealMove(round,event);
                        return true;
                    }
                    mStartY -= round;
                    mStartX -= roundX;
                }else{
                    dealMove(round,event);
                    return true;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                isPull=false;
                int height = getChildAt(0).getHeight();
                if(mMoveHeight<0 || mState == REFRESH) {
                    if (-mMoveHeight > height && mState == NORMAL) { // 正常状态下下拉超过第一个view高度,改为刷新状态
                        mState = REFRESH;
                        if (mListener != null) {
                            mListener.refresh();
                        }
                        smoothScrollBy( -mMoveHeight - height);
                    } else if (mMoveHeight > height / 2 && mState == REFRESH) {//刷新状态下移动到第一个view显示小于一半会滚到到正常显示状态
                        mState = NORMAL;
                        smoothScrollBy( -mMoveHeight + height);
                    } else {
                        smoothScrollBy( -mMoveHeight);
                    }
                    mMoveHeight = 0;
                }else{
                    if(mMoveHeight>mTotalHeight-getMeasuredHeight()) {//判断上拉加载
                        if(mCanLoadMore) {
                            mState = LOAD;
                            if(mListener!=null){
                                mListener.loadMore();
                            }
                        }
                        if(mTotalHeight>getMeasuredHeight()) {
                            //滚动到最后一个view显示的位置
                            smoothScrollBy(-mMoveHeight + mTotalHeight - getMeasuredHeight());
                            //计算滚回到最后一个view显示位置需要移动的距离
                            mMoveHeight = mTotalHeight - getMeasuredHeight();
                        }else{
                            smoothScrollBy( -mMoveHeight );
                            mMoveHeight = 0;
                        }
                    }
                }
                super.dispatchTouchEvent(event);
                return true;
        }
        return super.dispatchTouchEvent(event);
    }
    /**
     * 处理拖动 的高度计算和回调
     */
    private void dealMove(int round, MotionEvent event) {
        mMoveHeight += round;
        if(mListener!=null) {
            if (Math.abs(mMoveHeight) > getChildAt(0).getHeight()) {
                mListener.pullDown();
            }else{
                mListener.pullUp();
            }
        }
        smoothScrollBy( round);
        mStartY = event.getRawY();
        mStartX = event.getRawX();
    }

    /**
     * 滑动到指定位置
     * @param dy 竖直方向偏移量
     */
    private void smoothScrollBy(int dy) {
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), 0, dy);
        postInvalidate();
    }

最后是事件的监听回调 和方法调用:

public abstract class RefreshListener {
    abstract  void refresh();
     void pullUp(){

     }
     void pullDown(){

     }
     void loadMore(){

     }

     void moveDy(int dy){

     }
}
/**
     * 如果含有recyclerView需要绑定之后才能滚动
     */
    public void bindRecyclerView(RecyclerView recyclerView){
        mRecyclerView=recyclerView;
    }

    /**
     * 刷新完成调用
     */
    public void refreshComplete(){
        if(mState==REFRESH) {
            int height = getChildAt(0).getHeight();
            mState = NORMAL;
            smoothScrollBy( -mMoveHeight + height);
            mMoveHeight = 0;
        }
    }

    /**
     * 上拉加载完成调用
     * @param type 0为还有数据需要加载 1 为数据全部加载完成
     */
    public void loadComplete(int type){
        if(mState==LOAD) {
            mState = NORMAL;
            if(type==0) {
                smoothScrollBy(-mMoveHeight);
                mMoveHeight = 0;
            }else{
                //滚动到最后一个view显示的位置
                smoothScrollBy( -mMoveHeight + mTotalHeight - getMeasuredHeight());
                //计算滚回到最后一个view显示位置需要移动的距离
                mMoveHeight =mTotalHeight - getMeasuredHeight();
            }
        }
    }

    /**
     * 最大可弹性滚动的距离
     * @param scrollDistance 距离单位px
     */
    public void canScrollDistance(int scrollDistance){
        mCanScrollDistance=scrollDistance;
    }

    /**
     * 绑定刷新状态监听
     */
    /**
     * 绑定刷新状态监听
     */
    public void setRefreshListener(RefreshListener listener,boolean canLoadMore){
        mListener=listener;
        mCanLoadMore=canLoadMore;
    }

最后别忘了Scroller要想滚动需要调用的方法:

@Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

使用:

    <com.zqb.refreshlayout.RefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10px"
        android:paddingRight="200px">

        "@+id/header_content"
            android:layout_width="match_parent"
            android:layout_height="150px">

            "@+id/header_text_layout"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:orientation="vertical">

                "@+id/header_hint_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/header_hint_refresh_loading"
                    android:textSize="13sp"/>

                "wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="3dp">

                    "wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/header_hint_refresh_time"
                        android:textSize="14sp"/>

                    "@+id/header_hint_time"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="无记录"
                        android:textSize="12sp"/>
                
            
        

        "wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimaryDark"
            android:paddingBottom="40px"
            android:paddingTop="20px"
            android:text="sfhsdkfh "/>

        .support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        .support.v7.widget.RecyclerView>

        "加载中...."
            android:id="@+id/load_text_view"
            app:center_horizontal="true"
            android:layout_width="250px"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimaryDark"
            android:paddingBottom="40px"
            android:paddingTop="20px"/>
    com.zqb.refreshlayout.RefreshLayout>
mRecyclerView =  findViewById(R.id.recycler_view);
        mRefreshLayout = findViewById(R.id.refresh_layout);
        mHeaderHintTextView = findViewById(R.id.header_hint_text);
        mLoadTextView = findViewById(R.id.load_text_view);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        mAdapter = new MyAdapter();
        mRecyclerView.setAdapter(mAdapter);
        mRefreshLayout.bindRecyclerView(mRecyclerView);
        mRefreshLayout.setRefreshListener(new RefreshListener() {
            @Override
            void refresh() {
                mHeaderHintTextView.setText(R.string.header_hint_refresh_loading);
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mRefreshLayout.refreshComplete();
                    }

                }, 2000);
            }

            @Override
            void pullUp() {
                mHeaderHintTextView.setText(R.string.header_hint_refresh_normal);
            }

            @Override
            void pullDown() {
                mHeaderHintTextView.setText(R.string.header_hint_refresh_ready);
            }

            @Override
            void loadMore() {
                super.loadMore();
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if(mCount>30) {
                            mLoadTextView.setText("加载完毕");
                            mRefreshLayout.loadComplete(1);
                        }else {
                            mCount += 10;
                            mAdapter.notifyDataSetChanged();
                            mRefreshLayout.loadComplete(0);
                        }
                    }

                }, 2000);
            }
        },false);

忘了加下拉减速效果了,之后有时间补上
GitHub上源码地址

你可能感兴趣的:(下拉刷新,自定义ViewGroup,上拉加载,自定义属性,Android)