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

来源:互联网 发布:方舟画质优化 编辑:程序博客网 时间:2024/05/21 06:39

先看效果



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

实战
1.我们需要一个能随着滑动改变自身大小的自定义控件,继承view是个不错的选择
2.通过画布的缩放来控制图片的大小,同时别忘了处理padding 
@Overrideprotected 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的显示程度
@Overridepublic 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<String> mDatas;    private ArrayAdapter<String> 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<String>(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




4 0
原创粉丝点击