仿IOS效果-带弹簧动画的ListView

简介: 背景介绍最近项目打算做一个界面,类似于dayone首页的界面效果,dayone 是一款付费应用,目前只有IOS端。作为一个资深懒惰的程序员,奉行的宗旨是绝对不重复造一个轮子。于是乎,去网上找一大堆开源项目,发现没有找到合适的,然后,只能硬着头皮自己来了。先看看效果:其实写起来也比较简单,就是控制ListView的头部和底部的高度就可以了, 如果用RecycleVi

背景介绍

最近项目打算做一个界面,类似于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));

    }
}
相关文章
|
iOS开发
iOS 动画绘制圆形
iOS 动画绘制圆形
80 1
|
编译器 iOS开发 异构计算
读iOS核心动画笔记
读iOS核心动画笔记
55 0
|
13天前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户点击按钮时,按钮将从圆形变为椭圆形,颜色从蓝色渐变到绿色;释放按钮时,动画以相反方式恢复。通过UIView的动画方法和弹簧动画效果,实现平滑自然的过渡。
28 1
|
22天前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
【10月更文挑战第18天】本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户按下按钮时,按钮将从圆形变为椭圆形并从蓝色渐变为绿色;释放按钮时,动画恢复原状。通过UIView的动画方法和弹簧动画效果,实现平滑自然的动画过渡。
43 5
|
2月前
|
Swift iOS开发 UED
揭秘一款iOS应用中令人惊叹的自定义动画效果,带你领略编程艺术的魅力所在!
【9月更文挑战第5天】本文通过具体案例介绍如何在iOS应用中使用Swift与UIKit实现自定义按钮动画,当用户点击按钮时,按钮将从圆形变为椭圆形并从蓝色渐变到绿色,释放后恢复原状。文中详细展示了代码实现过程及动画平滑过渡的技巧,帮助读者提升应用的视觉体验与特色。
61 11
|
3月前
|
Swift iOS开发 UED
【绝妙创意】颠覆你的视觉体验!揭秘一款iOS应用中令人惊叹的自定义动画效果,带你领略编程艺术的魅力所在!
【8月更文挑战第13天】本文通过一个具体案例,介绍如何使用Swift与UIKit在iOS应用中创建独特的按钮动画效果。当按钮被按下时,其形状从圆形变化为椭圆形,颜色则从蓝色渐变为绿色;释放后,动画反向恢复原状。利用UIView动画方法及弹簧动画效果,实现了平滑自然的过渡。通过调整参数,开发者可以进一步优化动画体验,增强应用的互动性和视觉吸引力。
51 7
|
iOS开发
iOS 常用阅读软件打开书籍的转场动画
iOS 常用阅读软件打开书籍的转场动画
93 0
|
6月前
|
iOS开发
iOS设备功能和框架: 如何使用 Core Animation 创建动画效果?
iOS设备功能和框架: 如何使用 Core Animation 创建动画效果?
136 0
|
API iOS开发
iOS 自定义转场动画 UIViewControllerTransitioning
iOS 自定义转场动画 UIViewControllerTransitioning
93 0
|
iOS开发
iOS - 个人中心果冻弹性下拉动画
iOS - 个人中心果冻弹性下拉动画
253 0
iOS - 个人中心果冻弹性下拉动画