zoukankan      html  css  js  c++  java
  • 仿IOS效果-带弹簧动画的ListView

    背景介绍

    最近项目打算做一个界面,类似于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;
    
    /**
     * 邮箱 yll@520wcf.com
     * 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));
    
        }
    }
    
  • 相关阅读:
    ZooKeeper-3.3.4集群安装配置
    zookeeper原理(转)
    flume 转
    Flume NG 简介及配置实战
    Flume NG 配置详解
    '增量赋值(augmented assignment)', 多么痛的领悟!
    用matplotlib制作的比较满意的蜡烛图
    Spyder code editor里的小秘密: 右侧高亮提示
    pywinauto: 导入时遇到 "TypeError: LoadLibrary() argument 1 must be string, not unicode"
    爬取新浪财经个股的历史财报摘要
  • 原文地址:https://www.cnblogs.com/hehe520/p/6329905.html
Copyright © 2011-2022 走看看