Android鬼点子 又来了一个下拉刷新

302 阅读5分钟

之前写的一个LoadingBar,这次把LoadingBar加到下拉刷新的头部。从头写一个下拉刷新,附赠上拉加载更多。

我是动图.gif

首先是自定义属性,attrs.xml中定义头部的高度和上下的padding。

####attrs.xml####

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PPRefreshView_header">
        <attr name="header_height" format="dimension"/>
        <attr name="header_padding" format="dimension"/>
    </declare-styleable>
</resources>

然后是头部的文件,里面放了一个TextView和一个图片

header_layout.xml####

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="vertical">

    <ImageView
        android:src="@mipmap/down"
        android:layout_centerVertical="true"
        android:id="@+id/icon"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_toLeftOf="@+id/text"
        android:layout_marginRight="5dp"/>
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="下拉刷新" />
</RelativeLayout>

然后是布局文件,让PPRefreshView作为父布局,下面可以放AbsListView的子类。

activity_main.xml####

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ppRefreshView="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    tools:context="top.greendami.greendami.MainActivity">

    <top.greendami.greendami.PPRefreshView
        ppRefreshView:header_height="50dp"
        ppRefreshView:header_padding="10dp"
        android:id="@+id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#d4d4d4">
        <ListView
            android:background="@color/white"
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></ListView>
    </top.greendami.greendami.PPRefreshView>
</RelativeLayout>

最后是重点,下拉刷新的控件。 "精心"准备了一张图

####PPRefreshView.java####

package top.greendami.greendami;

package com.allrun.arsmartelevatorformanager.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.TextView;

import com.allrun.arsmartelevatorformanager.R;
import com.allrun.arsmartelevatorformanager.util.DPUnitUtil;

/**
 * Created by GreendaMI on 2017/3/21.
 */

public class PPRefreshView extends ViewGroup {
    Context context;
    RecyclerView mListView;
    PPView mPPView;
    View header;
    TextView title;
    ImageView mImage;//箭头
    int listTop = 0;

    float headerHeight = 10 + 30 + 10;//header的高度,上留白 + 文字(PPVIew)高度 + 下留白
    float headerpadding = 10;//上留白,下留白
    private int mYDown, mLastY;
    //最短滑动距离
    int a = 0;

    RotateAnimation mRotateAnimation;
    int state = 0; //0,正常;1,下拉;2,松开

    public void setPPRefreshViewListener(PPRefreshViewListener mPPRefreshViewListener) {
        this.mPPRefreshViewListener = mPPRefreshViewListener;
    }

    PPRefreshViewListener mPPRefreshViewListener;

    public PPRefreshView(Context context) {
        super(context);
        this.context = context;
    }

    public PPRefreshView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;

        //px转dp
        a = DPUnitUtil.px2dip(context,ViewConfiguration.get(context).getScaledDoubleTapSlop());
        TypedArray b = context.obtainStyledAttributes(attrs, R.styleable.PPRefreshView_header);
        headerHeight = b.getDimension(R.styleable.PPRefreshView_header_header_height, 100);
        headerpadding = b.getDimension(R.styleable.PPRefreshView_header_header_padding, 10);
        b.recycle();
        initAnima();
    }

    private void initAnima() {
        //箭头旋转
        mRotateAnimation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateAnimation.setFillAfter(true);
        mRotateAnimation.setDuration(200);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mPPView != null) {
            mPPView.measure(widthMeasureSpec,(int) (headerHeight- 2 * headerpadding));
        }
        if (header != null) {
            header.measure(widthMeasureSpec, (int) (headerHeight- 2 * headerpadding));
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mListView == null && getChildCount() == 1) {
            mListView = (RecyclerView)getChildAt(0);
            mListView.setOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                    if(!recyclerView.canScrollVertically(1)){
                        //添加外部回调
                        if(mPPRefreshViewListener != null){
                            mPPRefreshViewListener.LoadMore();
                        }
                    }
                }
            });

        }
        if (mListView != null) {
            mListView.layout(l, listTop, getMeasuredWidth(), b);
        }
        if (mPPView != null) {
            //top:文字(PPVIew)高度 + 下留白
            mPPView.layout(l, (int)(listTop - headerHeight + headerpadding), r, listTop);
        }

        if (header != null) {
            //top:文字(PPView)高度 + 下留白
            header.layout(l, (int)(listTop - headerHeight + headerpadding), r, listTop);
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        //松开手指,list回到顶部
        if (state == 2) {
            listTop = listTop - 25;
            if (listTop < headerHeight) {
                listTop = (int)headerHeight;
            }
            requestLayout();
        }

        //刷新完毕,关闭header
        if (state == 0 && listTop > 0) {
            listTop = listTop - 25;
            if (listTop < 0) {
                listTop = 0;
            }
            requestLayout();
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 按下
                mYDown = (int) event.getRawY();
                break;

            case MotionEvent.ACTION_MOVE:
                // 移动
                mLastY = (int) event.getRawY();

                if (!mListView.canScrollVertically(-1) && mLastY > mYDown &&(mLastY - mYDown) > a) {
                    state = 1;
                    listTop = mLastY - mYDown;
                    if (mPPView != null) {
                        removeView(mPPView);
                        mPPView = null;
                    }
                    if (header == null) {
                        header = LayoutInflater.from(context).inflate(R.layout.header_layout, null, false);
                        title = ((TextView) header.findViewById(R.id.text));
                        mImage = ((ImageView) header.findViewById(R.id.icon));
                        addView(header);
                    }
                    if (title != null && (mLastY - mYDown) > a * 2f) {
                        title.setText("松开刷新");
                        if (mImage.getAnimation() == null) {
                            mImage.startAnimation(mRotateAnimation);
                        }

                    }
                    if (title != null && (mLastY - mYDown) < a * 2f) {
                        title.setText("下拉刷新");
                        mImage.setImageResource(R.mipmap.down);
                    }

                    requestLayout();
                    //已经判断是下拉刷新,拦截手势
                    return false;
                }
                break;

            case MotionEvent.ACTION_UP:
                // 抬起
//                if (canLoad()) {
//                    loadData();
//                }
                //松手的时候,把文字标题去掉
                if (header != null) {
                    removeView(header);
                    header = null;
                }
                //如果之前是下拉状态,就添加PPVIew
                if (mPPView == null && state == 1) {
                    //添加外部回调
                    if(mPPRefreshViewListener != null){
                        mPPRefreshViewListener.onRefresh();
                    }

                    mPPView = new PPView(context);
                    addView(mPPView);
                    mYDown = 0;
                    mLastY = 0;
                    state = 2;
                    requestLayout();
                }
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }



    /**
     * 收起下拉刷新的header,刷新结束
     */
    public void RefreshOver() {
        if (mPPView != null) {
            removeView(mPPView);
            mPPView = null;
        }
        if (header != null) {
            removeView(header);
            header = null;
            title = null;
            mImage = null;
        }
        state = 0;
    }

    public void setRefreshing(boolean b) {
        if(!b){
            state = 0;
            postInvalidate();
        }else{
            state = 2;
            postInvalidate();
        }
    }

    public interface PPRefreshViewListener{
        void onRefresh();
        void LoadMore();
    }

}

主要思路是监听手势的滑动距离,如果ListView已经划到顶部,则ListView跟随手指位置,并添加Header。放开手指后,ListView慢慢回到顶部。

外部回调。监听下拉和上拉。

 mSwipeRefreshLayout.setPPRefreshViewListener(new PPRefreshView.PPRefreshViewListener() {
            @Override
            public void onRefresh() {
                Toast.makeText(MainActivity.this,"亲,刷新了",Toast.LENGTH_SHORT).show();
                data.add("测试数据100");
                mAdapter.notifyDataSetChanged();
            }

            @Override
            public void LoadMore() {
                Toast.makeText(MainActivity.this,"加载更多",Toast.LENGTH_SHORT).show();
            }
        });

refreshLayout.setRefreshing(false);;//刷新完毕

差点忘了粘连小球的View。 ####PPView.java####

package top.greendami.greendami;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by GreendaMi on 2017/3/17.
 */

public class PPView extends View {

    String TAG = "PPView";
    //动画开关
    boolean isLoading = true;
    Context mContext;

    private int mWidth = 100;
    private int mheight = 100;

    public int mColor;
    public Paint mPaint = new Paint();

    float time = 0;

    //小球与中间打球的最远距离
    float distance = 100;

    public PPView(Context context) {
        super(context);
        mContext = context;
        mColor = context.getResources().getColor(R.color.colorPrimary);
        init();
    }

    public PPView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mColor = context.getResources().getColor(R.color.colorPrimary);
        init();
    }

    private void init() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(mColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        //宽度至少是高度的4倍
        if (widthSpecSize < 4 * heightSpecSize) {
            widthSpecSize = 4 * heightSpecSize;
        }

        mWidth = widthSpecSize;
        mheight = heightSpecSize;

        distance = 1.2f * mheight;
        setMeasuredDimension(widthSpecSize, heightSpecSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isLoading) {

            //大圆半径
            float bigR = mheight * 0.32f + mheight * 0.03f * Math.abs((float) Math.sin(Math.toRadians(time)));
            float smallR = mheight * 0.17f + mheight * 0.03f * Math.abs((float) Math.cos(Math.toRadians(time)));
            float bigx = (getWidth()) / 2;
            //画中间大圆
            canvas.drawCircle(bigx, mheight / 2, bigR, mPaint);

            float smalx = getSmallCenterX();

            //画小圆
            canvas.drawCircle(smalx, mheight / 2, smallR, mPaint);

            //画链接

            //小球在右侧
            if (smalx > bigx) {

                Path path = new Path();
                //上面的贝塞尔曲线的第一个点,在大圆身上
                float x1 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
                float y1 = mheight / 2 - bigR * (float) Math.sin(Math.toRadians(time));
                if (y1 > mheight / 2 - smallR) {
                    y1 = mheight / 2 - smallR;
                    x1 = bigx + (float) (Math.sqrt(bigR * bigR - smallR * smallR));
                }
                //上面的贝塞尔曲线的第三个点,在小圆身上
                float x2 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
                float y2 = mheight / 2 - smallR * (float) Math.sin(Math.toRadians(time));
                if (y2 > mheight / 2 - smallR * 0.8) {
                    y2 = mheight / 2 - smallR * 0.8f;
                    x2 = smalx - smallR * (float) (Math.sqrt(1-0.64f));
                }
                //下面的贝塞尔曲线的第三个点,在小圆身上
                float x3 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
                float y3 = mheight / 2 + smallR * (float) Math.sin(Math.toRadians(time));
                if (y3 < mheight / 2 + smallR * 0.8) {
                    y3 = mheight / 2 + smallR * 0.8f;
                    x3 = smalx - smallR * (float) (Math.sqrt(1-0.64f));
                }
                //下面的贝塞尔曲线的第一个点,在大圆身上
                float x4 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
                float y4 = mheight / 2 + bigR * (float) Math.sin(Math.toRadians(time));
                if (y4 < mheight / 2 + smallR) {
                    y4 = mheight / 2 + smallR;
                    x4 = bigx + (float) (Math.sqrt(bigR * bigR - smallR * smallR));
                }

                path.moveTo(x1, y1);

                path.quadTo((bigx + smalx) / 2, mheight / 2, x2, y2);
                // 绘制贝赛尔曲线(Path)

                path.lineTo(x3, y3);

                path.quadTo((bigx + smalx) / 2, mheight / 2, x4, y4);
                canvas.drawPath(path, mPaint);
            }

            //小球在左侧
            if (smalx < bigx) {
                Path path = new Path();
                float x1 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
                float y1 = mheight / 2 - bigR * (float) Math.sin(Math.toRadians(time));
                if (y1 > mheight / 2 - smallR) {
                    y1 = mheight / 2 - smallR;
                    x1 = bigx - (float) (Math.sqrt(bigR * bigR - smallR * smallR));
                }

                float x2 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
                float y2 = mheight / 2 - smallR * (float) Math.sin(Math.toRadians(time));
                if (y2 > mheight / 2 - smallR * 0.8) {
                    y2 = mheight / 2 - smallR * 0.8f;
                    x2 = smalx + smallR * (float) (Math.sqrt(1-0.64f));
                }

                float x3 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
                float y3 = mheight / 2 + smallR * (float) Math.sin(Math.toRadians(time));
                if (y3 < mheight / 2 + smallR * 0.8) {
                    y3 = mheight / 2 + smallR * 0.8f;
                    x3 = smalx + smallR * (float) (Math.sqrt(1-0.64f));
                }
                float x4 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
                float y4 = mheight / 2 + bigR * (float) Math.sin(Math.toRadians(time));
                if (y4 < mheight / 2 + smallR) {
                    y4 = mheight / 2 + smallR;
                    x4 = bigx - (float) (Math.sqrt(bigR * bigR - smallR * smallR));
                }

                path.moveTo(x1, y1);

                path.quadTo((bigx + smalx) / 2, mheight / 2, x2, y2);
                // 绘制贝赛尔曲线(Path)

                path.lineTo(x3, y3);

                path.quadTo((bigx + smalx) / 2, mheight / 2, x4, y4);
                canvas.drawPath(path, mPaint);
            }

            postInvalidate();
        }
    }

    //计算小球的X坐标
    private float getSmallCenterX() {
        //此处控制速度
        time = time + 4f;
        return mWidth / 2 + distance * (float) Math.cos(Math.toRadians(time));
    }
}

两张素材

up.png
down.png