弹性,滑动隐藏导航栏ListVIew

安卓ListView原设定并没有办法实现类似IOS那种上下拉弹性的效果,这样体验起来难免会有些生硬。主要源于于ListView的一个方法overScrollBy(),当中“maxOverScrollY”在垂直方向上可超出拉升距离默认值为0,若想实现弹性的效果只要设定该值大于0即可

@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
    int scrollRangeX, int scrollRangeY, int maxOverScrollX,int maxOverScrollY, 
    boolean isTouchEvent) {
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
            maxOverScrollX, maxOverScrollY, isTouchEvent);
}

想要实现导航栏的滑动隐藏或者显示必然得先获取列表的滚动方向,获取滚动方向有两种方法,一种是通过setOnScrollListener();中的onScroll();判断,其判断原理主要是通过获取滚动列表前第一个可见项Item的索引与随后滑动置顶第一个Item的索引带下关系获取滚动方向。

int lastVisibleItem;

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    if (firstVisibleItem > lastVisibleItem) {
        // 上滑
    } else if (firstVisibleItem < lastVisibleItem){
        // 下滑
    }
    lastVisibleItem = firstVisibleItem;
}

另外的一种方法即使通过setOnTouchListener();记录触控点的位置与滑动位置的比较以获得ListView的滚动方向。其中”mMinSlopDistance”为获取默认最小滑动距离。

mMinSlopDistance = ViewConfiguration.get(context).getScaledScrollBarSize();

OnTouchListener mOnTouch = new OnTouchListener() {
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 mStartY = event.getY();
                 break;
             case MotionEvent.ACTION_MOVE:
                 float currentY = event.getY();
                 if (mStartY - currentY > mMinSlopDistance) {
                     // 上滑
                 } else if (currentY - mStartY > mMinSlopDistance) {
                     // 下滑
                 }
                 break;
             case MotionEvent.ACTION_UP:
                 break;
         }
         return false;
     }
 };

准确获取到ListView的滑动方向之后,我们就可以根据滑动方向来控制导航栏的显示状态了。其中有个需要注意的地方是,我们要为ListView添加一个高度与导航栏一致的HeaderView才行,这样才能避免导航栏出现挡住ListView首项。另外导航栏的显示/隐藏动画主要通过ObjectAnimation这个动画对象控制。下面把自定义ListView源码贴下,以及最终效果(弹性效果还需要继续完善)。

public class StretchListView extends ListView {

    // 上下文
    private Context mContext;
    // 最大拉伸
    private int mMaxOverDistance;
    // 是否启动弹性
    private boolean mIsSetOver;

    // 对象动画
    private ObjectAnimator mAnimator;
    // 隐藏导航栏的最低滑动距离限制
    private int mMinSlopDistance;
    // 起始滑动Y轴触控点
    private float mStartY;
    // 需要控制现实/隐藏的View
    private View mToolbar;
    // 是否可启动隐藏动画
    private boolean mIsShow;

    public StretchListView(Context context) {
        super(context);
        this.mContext = context;
        initView();
    }

    public StretchListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        initView();
    }

    public StretchListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        initView();
    }

    private void initView() {
        this.mMaxOverDistance = getStretchDistance(mContext, 30);
        this.mMinSlopDistance = ViewConfiguration.get(mContext).getScaledScrollBarSize();
    }

    /**
     * @param isSetOver 是否启动列表滑动弹性功能
     */
    public void setScrollOver(boolean isSetOver) {
        this.mIsSetOver = isSetOver;
    }

    /**
     * @return 获取视图控件高度
     */
    private int getNavHeight(View v) {
        int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        v.measure(w, h);
        return v.getMeasuredHeight();
    }

    /**
     * @param v 需要被隐藏的导航视图
     */
    public void setHideHeader(View v) {
        if (v == null) return;
        this.mToolbar = v;
        // 先为ListView添加一个HeaderView,避免第一个Item被toolbar挡住
        View headerV = new View(mContext);
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, getNavHeight(mToolbar));
        headerV.setLayoutParams(params);
        this.addHeaderView(headerV);
        this.setOnTouchListener(mOnTouch);
    }

    /**
     * @param context 上下文
     * @param maxOver 自定义最大弹射距离
     * @return 最大拉伸
     */
    protected int getStretchDistance(Context context, int maxOver) {
        // DisplayMetrics 类提供了一种关于显示的通用信息,如显示大小,分辨率和字体
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        // 获取密度
        float dy = metrics.density;
        return (int) dy * maxOver;
    }

    /**
     * @param flag 方向
     */
    private void toolbarAnim(int flag) {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.cancel();
        }
        mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY", mToolbar.getTranslationY(),
                flag == 0 ? 0 : -mToolbar.getHeight());
        mAnimator.start();
    }

    OnTouchListener mOnTouch = new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mStartY = event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float currentY = event.getY();
                    if (mStartY - currentY > mMinSlopDistance && mIsShow) {
                        toolbarAnim(1);// 向上
                        mIsShow = !mIsShow;
                    } else if (currentY - mStartY > mMinSlopDistance && !mIsShow) {
                        toolbarAnim(0);// 向下
                        mIsShow = !mIsShow;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    break;
            }
            return false;
        }
    };

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
                                   int scrollRangeX, int scrollRangeY, int maxOverScrollX,
                                   int maxOverScrollY, boolean isTouchEvent) {
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
                maxOverScrollX, mIsSetOver ? mMaxOverDistance : maxOverScrollY, isTouchEvent);
    }
}

最终效果图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值