一步步实现带动画效果的下拉刷新

先看效果

一步步实现带动画效果的下拉刷新_第1张图片

分析
1.先要在listview的头部加上一个布局,布局中包含一个文本控件一个图片
2.这个图片控件会随着下拉的过程做一个缩放
3.整个下拉刷新过程分三步:
第一步:下拉未超过布局的原始高度,图片做缩放动作,文字显示下拉刷新
第二步:下拉超过布局的原始高度,图片大小保持不变,文字显示松开刷新
第三步:松手后,如果当前位置在原始高度的上方,不进行刷新,直接回弹;如果在下方,执行刷新任务,并播放动画效果,完成后回弹 

实战
1.我们需要一个能随着滑动改变自身大小的自定义控件,继承view是个不错的选择
2.通过画布的缩放来控制图片的大小,同时别忘了处理padding 
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //考虑padding的影响
    int leftPadding = getPaddingLeft();
    int topPadding = getPaddingTop();
    int rightPadding = getPaddingRight();
    int bottomPadding = getPaddingBottom();

    int lastWidth = getMeasuredWidth() - leftPadding - rightPadding;
    int lastHeight = getMeasuredHeight() - topPadding - bottomPadding;

    scaleBitmap = Bitmap.createScaledBitmap(initBitmap, lastWidth, lastHeight, true);

    canvas.save();
    //缩放画布
    canvas.scale(mCurrentProgress, mCurrentProgress, lastWidth / 2 + leftPadding, lastHeight / 2 + topPadding);
    //缩放图形,要写在画布缩放后边
    canvas.drawBitmap(scaleBitmap, topPadding, leftPadding, null);

    canvas.restore();
}
3.需要一个方法供外部调用,用于控制绘制控件的大小
public void setCurrentProgress(float currentProgress) {
    mCurrentProgress = currentProgress;
    postInvalidate();
}
4.完整的代码
public class ScaleView extends View {
    private Bitmap initBitmap;
    private Bitmap scaleBitmap;
    private float mCurrentProgress = 1;
    private int mWidth;
    private int mHeight;

    public ScaleView(Context context) {
        super(context);
        init(context);
    }

    public ScaleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

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

    private void init(Context context) {
        initBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.bell));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            mWidth = widthSize;
        } else {
            mWidth = initBitmap.getWidth();
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            mHeight = heightSize;
        } else {
            mHeight = initBitmap.getHeight();
        }

        setMeasuredDimension(mWidth, mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //考虑padding的影响
        int leftPadding = getPaddingLeft();
        int topPadding = getPaddingTop();
        int rightPadding = getPaddingRight();
        int bottomPadding = getPaddingBottom();

        int lastWidth = getMeasuredWidth() - leftPadding - rightPadding;
        int lastHeight = getMeasuredHeight() - topPadding - bottomPadding;

        scaleBitmap = Bitmap.createScaledBitmap(initBitmap, lastWidth, lastHeight, true);

        canvas.save();
        //缩放画布
        canvas.scale(mCurrentProgress, mCurrentProgress, lastWidth / 2 + leftPadding, lastHeight / 2 + topPadding);
        //缩放图形,要写在画布缩放后边
        canvas.drawBitmap(scaleBitmap, topPadding, leftPadding, null);

        canvas.restore();
    }

    public void setCurrentProgress(float currentProgress) {
        mCurrentProgress = currentProgress;
        postInvalidate();
    }
}
5.开始实现继承listview的PullRefreshListView。先加载布局
mHeadView = LayoutInflater.from(context).inflate(R.layout.item_headview, null, false);
loadMoreView = (ScaleView) mHeadView.findViewById(R.id.loadMoreView);
tv = (TextView) mHeadView.findViewById(R.id.tv);
addHeaderView(mHeadView);
6.根据下拉的距离,控制headview的状态。靠listView的paddingTop来控制headView的显示程度
@Override
public boolean onTouchEvent(MotionEvent ev) {
    if(!refreshEnable || isAnimatoring)
    {
        return super.onTouchEvent(ev);
    }

    y = ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_MOVE:
            //下拉,不超过原始的布局高度

            if (mfirstVisibleItem == 0 && y > mLastY && offsetY < mHeadViewHeight) {
                changState();
            }
            //上滑
            if (mfirstVisibleItem == 0 && y < mLastY && offsetY > 0) {
                changState();
            }
            break;
        case MotionEvent.ACTION_UP:
            int curPaddingTop = getPaddingTop();

            if (curPaddingTop > 0) {
                isAnimatoring = true;

                refreshingState();

                mObjectAnimator = startRefreshAnim(loadMoreView);
                post(new Runnable() {
                    @Override
                    public void run() {
                        mOnPullRefreshListener.onRefresh();
                    }
                });
            } else {
                resetState();
            }
            break;
    }
    mLastY = y;
    return super.onTouchEvent(ev);
}
7.抖动的动画其实就是x方向的来回位移
private ObjectAnimator startRefreshAnim(ScaleView target) {
    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(target, View.TRANSLATION_X, 0, 20, 0, -20, 0);
    objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
    objectAnimator.setInterpolator(new DecelerateInterpolator());
    objectAnimator.setDuration(ANIM_DURATION);
    objectAnimator.start();
    return objectAnimator;
}
8.提供监听供调用刷新任务,同时提供任务完成的终止方法
/**
 * 刷新完成
 */
public void complete()
{
    mObjectAnimator.cancel();
    resetState();
    isAnimatoring = false;
}

/**
 * 设置刷新回调监听
 * @param onPullRefreshListener
 */
public void setOnPullRefreshListener(OnPullRefreshListener onPullRefreshListener) {
    if (onPullRefreshListener == null) {
        return;
    }
    this.mOnPullRefreshListener = onPullRefreshListener;
    refreshEnable = true;
}

public interface OnPullRefreshListener {
    void onRefresh();
}
9.完整的源码
public class PullRefreshListView extends ListView implements AbsListView.OnScrollListener {
    private ScaleView loadMoreView;
    private TextView tv;
    private View mHeadView;
    private int mHeadViewHeight;
    private float mLastY, y, offsetY;
    private int mfirstVisibleItem;
    /**
     * 动画播放时间
     */
    private static final int ANIM_DURATION = 200;
    /**
     * 缩小滑动时对padding的影响
     */
    private static final int RESISTANCE = 3;
    /**
     * 是否实现下拉刷新接口
     */
    private boolean refreshEnable = false;
    /**
     * 是否在播放动画
     */
    private boolean isAnimatoring = false;
    /**
     * 下拉刷新回调接口
     */
    private OnPullRefreshListener mOnPullRefreshListener;
    /**
     * 刷新动画
     */
    private ObjectAnimator mObjectAnimator;

    public PullRefreshListView(Context context) {
        super(context);
        init(context);
    }

    public PullRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

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

    private void init(Context context) {
        setOverScrollMode(OVER_SCROLL_NEVER);

        //先把布局加载进来
        mHeadView = LayoutInflater.from(context).inflate(R.layout.item_headview, null, false);
        loadMoreView = (ScaleView) mHeadView.findViewById(R.id.loadMoreView);
        tv = (TextView) mHeadView.findViewById(R.id.tv);
        addHeaderView(mHeadView);

        post(new Runnable() {
            @Override
            public void run() {
                //把headView的高度取出来
                mHeadViewHeight = mHeadView.getMeasuredHeight();
                resetState();
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(!refreshEnable || isAnimatoring)
        {
            return super.onTouchEvent(ev);
        }

        y = ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_MOVE:
                //下拉,最多下拉到2倍高度的位置
                if (mfirstVisibleItem == 0 && y > mLastY && offsetY < 2 * mHeadViewHeight) {
                    changState();
                }
                //上滑
                if (mfirstVisibleItem == 0 && y < mLastY && offsetY > 0) {
                    changState();
                }
                break;
            case MotionEvent.ACTION_UP:
                int curPaddingTop = getPaddingTop();

                if (curPaddingTop > 0) {
                    isAnimatoring = true;

                    refreshingState();

                    mObjectAnimator = startRefreshAnim(loadMoreView);
                    post(new Runnable() {
                        @Override
                        public void run() {
                            mOnPullRefreshListener.onRefresh();
                        }
                    });
                } else {
                    resetState();
                }
                break;
        }
        mLastY = y;
        return super.onTouchEvent(ev);
    }

    /**
     * 正在刷新的状态
     */
    private void refreshingState() {
        setHeadViewPadding(mHeadViewHeight);
        setCurrentProgress(mHeadViewHeight);
        offsetY = mHeadViewHeight;
        tv.setText("正在刷新");
    }

    /**
     * 将状态设置回原始状态
     */
    private void resetState() {
        offsetY = 0;
        setHeadViewPadding(0);
        setCurrentProgress(0);
    }

    /**
     * 滑动时动态设置各个组件的状态
     */
    private void changState() {
        offsetY = offsetY + (y - mLastY) / RESISTANCE;
        setHeadViewPadding((int) (offsetY));
        //从二分之一的地方开始缩放,使缩放效果更明显
        if (offsetY > mHeadViewHeight / 2) {
            setCurrentProgress((offsetY - mHeadViewHeight / 2) * 2);
        }
        //设置字体状态
        if (offsetY > mHeadViewHeight) {
            tv.setText("松开刷新");
        } else {
            tv.setText("下拉刷新");
        }
    }

    /**
     * 播放刷新动画
     *
     * @param target
     */
    private ObjectAnimator startRefreshAnim(ScaleView target) {
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(target, View.TRANSLATION_X, 0, 20, 0, -20, 0);
        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        objectAnimator.setInterpolator(new DecelerateInterpolator());
        objectAnimator.setDuration(ANIM_DURATION);
        objectAnimator.start();
        return objectAnimator;
    }

    /**
     * 根据滑动的距离设置图片的缩放
     *
     * @param offsetY
     */
    private void setCurrentProgress(float offsetY) {
        float scale = offsetY / mHeadViewHeight;
        scale = scale > 1 ? 1 : scale;
        loadMoreView.setCurrentProgress(scale);
    }

    /**
     * 位移相对于隐藏headview原点
     *
     * @param offset
     */
    private void setHeadViewPadding(int offset) {
        setPadding(0, offset - mHeadViewHeight, 0, 0);
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mfirstVisibleItem = firstVisibleItem;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    /**
     * 刷新完成
     */
    public void complete()
    {
        mObjectAnimator.cancel();
        resetState();
        isAnimatoring = false;
    }

    /**
     * 设置刷新回调监听
     * @param onPullRefreshListener
     */
    public void setOnPullRefreshListener(OnPullRefreshListener onPullRefreshListener) {
        if (onPullRefreshListener == null) {
            return;
        }
        this.mOnPullRefreshListener = onPullRefreshListener;
        refreshEnable = true;
    }

    public interface OnPullRefreshListener {
        void onRefresh();
    }
}
10.试一下
public class MainActivity extends AppCompatActivity implements PullRefreshListView.OnPullRefreshListener {
    private List mDatas;
    private ArrayAdapter mAdapter;
    private PullRefreshListView mPullRefreshListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        init();
    }

    private void init() {
        try {
            mPullRefreshListView = (PullRefreshListView) findViewById(R.id.pullRefreshListView);
            initData();
            mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, mDatas);
            mPullRefreshListView.setOnPullRefreshListener(this);
            mPullRefreshListView.setAdapter(mAdapter);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initData() {
        mDatas = new ArrayList<>();
        for (int i=0; i<20; i++)
        {
            mDatas.add(String.valueOf(i));
        }
    }

    @Override
    public void onRefresh() {
        Toast.makeText(MainActivity.this, "success", Toast.LENGTH_SHORT).show();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模拟耗时任务
                    Thread.sleep(3000);

                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            //任务执行完毕
                            mPullRefreshListView.complete();
                        }
                    });

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

总结
1.headview的位置变化没有使用弹性滑动,可以完善
2.可以在刷新阶段加入更多酷炫的动画
3.上拉加载后边加上
4.后边用RecyclerView来实现下 


源码地址:https://github.com/wolow3/PullRefreshListView




你可能感兴趣的:(自定义view)