欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

仿iOS效果 带弹簧动画的ListView

程序员文章站 2022-05-02 20:06:16
...

背景介绍

最近项目打算做一个界面,类似于dayone首页的界面效果,dayone 是一款付费应用,目前只有IOS端。作为一个资深懒惰的程序员,奉行的宗旨是绝对不重复造一个*。于是乎,去网上找一大堆开源项目,发现没有找到合适的,然后,只能硬着头皮自己来了。先看看效果:

其实写起来也比较简单,就是控制ListView的头部和底部的高度就可以了, 如果用RecycleView实现起来也是一样,只是RecycleView添加头和尾巴稍微麻烦一点,处理点击事件也不是很方便,所以就基于ListView去实现了。实现的代码, 我已经上传到github上了。

使用方法

github地址: https://github.com/yll2wcf/YLListView 可以帮我点个star啊~

使用方法

compile 'com.a520wcf.yllistview:YLListView:1.0.1

使用介绍: 布局: 布局注意一个小细节android:layout_height 最好是match_parent, 否则ListView每次滑动的时候都有可能需要重新计算条目高度,比较耗费CPU;

  <com.a520wcf.yllistview.YLListView 
    android:divider="@android:color/transparent"
    android:id="@+id/listView" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" /> 
复制代码

代码:

  private YLListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (YLListView) findViewById(R.id.listView);
        // 不添加也有默认的头和底
        View topView=View.inflate(this,R.layout.top,null);
        listView.addHeaderView(topView);
        View bottomView=new View(getApplicationContext());
        listView.addFooterView(bottomView);

        // 顶部和底部也可以固定最终的高度 不固定就使用布局本身的高度
        listView.setFinalBottomHeight(100);
        listView.setFinalTopHeight(100);

        listView.setAdapter(new DemoAdapter());

        //YLListView默认有头和底  处理点击事件位置注意减去
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                position=position-listView.getHeaderViewsCount();
            }
        });


    }
复制代码

源码介绍

其实这个项目里面只有一个类,大家不需要依赖,直接把这个类复制到项目中就可以了,来看看源码:

package com.a520wcf.yllistview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.Scroller;

/**
 * 邮箱 [email protected]
 * Created by yull on 12/17.
 */
public class YLListView extends ListView implements AbsListView.OnScrollListener {
    private Scroller mScroller; // used for scroll back
    private float mLastY = -1;

    private int mScrollBack;
    private final static int SCROLLBACK_HEADER = 0;
    private final static int SCROLLBACK_FOOTER = 1;

    private final static int SCROLL_DURATION = 400; // scroll back duration
    private final static float OFFSET_RADIO = 1.8f;
    // total list items, used to detect is at the bottom of ListView.
    private int mTotalItemCount;
    private View mHeaderView;  // 顶部图片
    private View mFooterView;  // 底部图片
    private int finalTopHeight;
    private int finalBottomHeight;

    public YLListView(Context context) {
        super(context);
        initWithContext(context);
    }

    public YLListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initWithContext(context);
    }

    public YLListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initWithContext(context);
    }

    private void initWithContext(Context context) {
        mScroller = new Scroller(context, new DecelerateInterpolator());
        super.setOnScrollListener(this);

        this.getViewTreeObserver().addOnGlobalLayoutListener(
                new OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        if(mHeaderView==null){
                            View view=new View(getContext());
                            addHeaderView(view);
                        }
                        if(mFooterView==null){
                            View view=new View(getContext());
                            addFooterView(view);
                        }
                        getViewTreeObserver()
                                .removeGlobalOnLayoutListener(this);
                    }
                });
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mLastY == -1) {
            mLastY = ev.getRawY();
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastY = ev.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float deltaY = ev.getRawY() - mLastY;
                mLastY = ev.getRawY();
                if (getFirstVisiblePosition() == 0 && (mHeaderView.getHeight() > finalTopHeight || deltaY > 0)
                        && mHeaderView.getTop() >= 0) {
                    // the first item is showing, header has shown or pull down.
                    updateHeaderHeight(deltaY / OFFSET_RADIO);
                } else if (getLastVisiblePosition() == mTotalItemCount - 1
                        && (getFootHeight() >finalBottomHeight || deltaY < 0)) {
                    updateFooterHeight(-deltaY / OFFSET_RADIO);
                }
                break;
            default:
                mLastY = -1; // reset
                if (getFirstVisiblePosition() == 0 && getHeaderHeight() > finalTopHeight) {
                    resetHeaderHeight();
                }
                if (getLastVisiblePosition() == mTotalItemCount - 1 ){
                        if(getFootHeight() > finalBottomHeight) {
                            resetFooterHeight();
                        }
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 重置底部高度
     */
    private void resetFooterHeight() {
        int bottomHeight = getFootHeight();
        if (bottomHeight > finalBottomHeight) {
            mScrollBack = SCROLLBACK_FOOTER;
            mScroller.startScroll(0, bottomHeight, 0, -bottomHeight+finalBottomHeight,
                    SCROLL_DURATION);
            invalidate();
        }
    }
    // 计算滑动  当invalidate()后 系统会自动调用
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            if (mScrollBack == SCROLLBACK_HEADER) {
                setHeaderHeight(mScroller.getCurrY());
            } else {
                setFooterViewHeight(mScroller.getCurrY());
            }
            postInvalidate();
        }
        super.computeScroll();
    }
    // 设置顶部高度
    private void setHeaderHeight(int height) {
        LayoutParams layoutParams = (LayoutParams) mHeaderView.getLayoutParams();
        layoutParams.height = height;
        mHeaderView.setLayoutParams(layoutParams);
    }
    // 设置底部高度
    private void setFooterViewHeight(int height) {
        LayoutParams layoutParams =
                (LayoutParams) mFooterView.getLayoutParams();
        layoutParams.height =height;
        mFooterView.setLayoutParams(layoutParams);
    }
    // 获取顶部高度
    public int getHeaderHeight() {
        AbsListView.LayoutParams layoutParams =
                (AbsListView.LayoutParams) mHeaderView.getLayoutParams();
        return layoutParams.height;
    }
    // 获取底部高度
    public int getFootHeight() {
        AbsListView.LayoutParams layoutParams =
                (AbsListView.LayoutParams) mFooterView.getLayoutParams();
        return layoutParams.height;
    }

    private void resetHeaderHeight() {
        int height = getHeaderHeight();
        if (height == 0) // not visible.
            return;
        mScrollBack = SCROLLBACK_HEADER;
        mScroller.startScroll(0, height, 0, finalTopHeight - height,
                SCROLL_DURATION);
        invalidate();
    }

    /**
     * 设置顶部高度  如果不设置高度,默认就是布局本身的高度
     * @param height 顶部高度
     */
    public void setFinalTopHeight(int height) {
        this.finalTopHeight = height;
    }
    /**
     * 设置底部高度  如果不设置高度,默认就是布局本身的高度
     * @param height 底部高度
     */
    public void setFinalBottomHeight(int height){
        this.finalBottomHeight=height;
    }
    @Override
    public void addHeaderView(View v) {
        mHeaderView = v;
        super.addHeaderView(mHeaderView);
        mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(
                new OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        if(finalTopHeight==0) {
                            finalTopHeight = mHeaderView.getMeasuredHeight();
                        }
                        setHeaderHeight(finalTopHeight);
                        getViewTreeObserver()
                                .removeGlobalOnLayoutListener(this);
                    }
                });
    }

    @Override
    public void addFooterView(View v) {
        mFooterView = v;
        super.addFooterView(mFooterView);

        mFooterView.getViewTreeObserver().addOnGlobalLayoutListener(
                new OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        if(finalBottomHeight==0) {
                            finalBottomHeight = mFooterView.getMeasuredHeight();
                        }
                        setFooterViewHeight(finalBottomHeight);
                        getViewTreeObserver()
                                .removeGlobalOnLayoutListener(this);
                    }
                });
    }

    private OnScrollListener mScrollListener; // user's scroll listener

    @Override
    public void setOnScrollListener(OnScrollListener l) {
        mScrollListener = l;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(view, scrollState);
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
                         int visibleItemCount, int totalItemCount) {
        // send to user's listener
        mTotalItemCount = totalItemCount;
        if (mScrollListener != null) {
            mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount,
                    totalItemCount);
        }
    }

    private void updateHeaderHeight(float delta) {
        setHeaderHeight((int) (getHeaderHeight()+delta));
        setSelection(0); // scroll to top each time
    }

    private void updateFooterHeight(float delta) {
        setFooterViewHeight((int) (getFootHeight()+delta));

    }
}

复制代码

更多精彩请关注微信公众账号likeDev,公众账号名称:爱上Android。