zhouhaibing089 / blog

A repository to record some ideas
https://blog.zhouhaibing.com
9 stars 2 forks source link

如何在Android中的ListView实现Bounces效果 #2

Open zhouhaibing089 opened 9 years ago

zhouhaibing089 commented 9 years ago

在写安卓应用中, 有时不得不抄一抄iOS中的Bounces效果, 也就是说在上拉或下拉时能过超过原来的大小限制.

解决这个问题的思路是给ListView添加Header和Footer, 然后实现自己的onTouchEvent, 在需要时增加Header和Footer的padding.

首先我们继承ListView并添加相关属性:

public class BounceListView extends ListView implements AbsListView.OnScrollListener {
    /**
     * the header view
     */
    View mHeaderView;
    /**
     * the footer view
     */
    View mFooterView;
    /**
     * current scroll state
     */
    int mCurrentScrollState;
    /**
     * the y coordinate of last motion event
     */
    int mLastY;

我们添加了对Header View和Footer View的引用, 还存有当前的滑动状态, 以及最后点击屏幕时的y坐标.

由于只需要Bounces效果, Header View以及Footer View都可以用最简单的View.

// create the HeaderView
mHeaderView = LayoutInflater.from(context).inflate(R.layout.item_bounce_header, this, false);
// and add it into the header
addHeaderView(mHeaderView);

// create the FooterView
mFooterView = LayoutInflater.from(context).inflate(R.layout.item_bounce_footer, this, false);
// and add it into the footer
addFooterView(mFooterView);

代码中提到的R.layout.item_bounce_headerR.layout.item_bounce_footer都是一个非常简单的布局文件, 仅是一个空的LinearLayout, 直接用View也是可以的.

由于顶部的Bounces和底部的Bounces实现很类似, 我只描述顶部Bounces的效果如何实现. 首先我们得自己实现onTouchEvent方法.

@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
    // the new y position
    int y = (int) event.getY();
    switch (event.getAction()) {
        // the gesture is released
        case MotionEvent.ACTION_UP:
            // the first item is visible and the header view is not entire visible
            if (getFirstVisiblePosition() == 0 &&
                    (mHeaderView.getBottom() < 0 || mHeaderView.getTop() <= 0)) {
                resetHeaderPadding();
            }
            break;
        case MotionEvent.ACTION_DOWN:
            // record the initial y position
            mLastY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            changeHeaderPadding(event);
            break;
    }
    return super.onTouchEvent(event);
}

首先我们在开始滑动动作时记录y坐标, 即MotionEvent.ACTION_DOWN, 在滑动结束(MotionEvent.ACTION_UP)时, 如果Header View并不可见, 就要将其的padding重新设置为0, 由于是顶部的Bounces效果, 修改topPadding即可. 还在滑动过程中(MotionEvent.ACTION_MOVE)时, 就要时刻改变Header View的topPadding.

改变padding的方法实现如下:

protected void changeHeaderPadding(MotionEvent ev) {
    int histSize = ev.getHistorySize();

    for (int i = 0; i < histSize; i++) {
        int histY = (int) ev.getHistoricalY(i);
        int topPadding = ((histY - mLastY)) / 2;
        mHeaderView.setPadding(
                mHeaderView.getPaddingLeft(),
                topPadding,
                mHeaderView.getPaddingRight(),
                mHeaderView.getPaddingBottom());
    }
}

也很简单, 就是取出所有的点左边, 一下一下移动padding.

看这行代码: int topPadding = ((histY - mLastY)) / 2, 这里的2是硬编码的一个数值, 表示下滑的位移与顶部padding增加的数量之间的比例, 越大表示padding增加的越慢.

另外, 关于重设padding, 因为要动画效果, 这个时候就要动画了, ValueAnimator就起了作用:

protected void resetHeaderPadding() {
    ValueAnimator animator = ValueAnimator.ofInt(mHeaderView.getPaddingTop(), 0);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            mHeaderView.setPadding(
                    mHeaderView.getPaddingLeft(),
                    (Integer) valueAnimator.getAnimatedValue(),
                    mHeaderView.getPaddingRight(),
                    mHeaderView.getPaddingBottom());
        }
    });
    animator.setDuration(200);
    animator.start();
}

嗯, 要实现Bounces效果看懂上面这些代码就OK了.

ninjachen commented 8 years ago

嘿哥们,我试了一下你这个逻辑,发现没有work,方便给出一下完整的代码吗?

顺便说一下,我原来用的是Android-Parallax-ListView-Sticky,他的效率要比你的高。现在有需求给imageview设一个minheight,所以找到了你这篇。

zhouhaibing089 commented 8 years ago

已经不是Android程序员了... 代码我找找看..

ninjachen commented 8 years ago

那不用麻烦了,我用了一种比较冗余的方法搞定了。