仿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));

    }
}
时间: 2024-11-03 17:57:56

仿IOS效果-带弹簧动画的ListView的相关文章

仿IOS效果 带弹簧动画的ListView_IOS

最近项目打算做一个界面,类似于dayone首页的界面效果,dayone 是一款付费应用,目前只有IOS端.作为一个资深懒惰的程序员,奉行的宗旨是绝对不重复造一个轮子.于是乎,去网上找一大堆开源项目,发现没有找到合适的,然后,只能硬着头皮自己来了.先看看效果: 效果图 其实写起来也比较简单,就是控制ListView的头部和底部的高度就可以了, 如果用RecycleView实现起来也是一样,只是RecycleView添加头和尾巴稍微麻烦一点,处理点击事件也不是很方便,所以就基于ListView去实现

ios开发之Swift实现的登录界面(带猫头鹰动画效果)

这个是一个可爱的登录框页面,动画效果仿自国外网站readme.io.在GitHub上有人写了个objective-C版本,这里我翻译成Swift版的分享给大家.   效果图如下: (1)当输入用户名时,猫头鹰的手是搭在登录框上 (2)当输入密码时,猫头鹰的手会遮住眼睛.这个是有动画效果的.实现方式其实就是图片移动动画. (3)离开密码框时,猫头鹰手又会放下. 开发之Swift实现的登录界面(带猫头鹰动画效果)-猫头鹰动画片">     动态效果如下: import UIKit   clas

Android开发实现带有反弹效果仿IOS反弹scrollview教程详解_Android

首先给大家看一下我们今天这个最终实现的效果图:   这个是ios中的反弹效果.当然我们安卓中如果想要实现这种效果,感觉不会那么生硬,滚动到底部或者顶部的时候.当然 使用scrollview是无法实现的.所以我们需要新建一个view继承ScrollView package davidbouncescrollview.qq986945193.com.davidbouncescrollview; import android.annotation.SuppressLint; import androi

CADisplayLink+弹簧动画实现果冻效果

项目中在Tabbar中间的按钮要从底部弹出视图并有果冻效果,在CocoaChina中找了一篇博客用 UIBezierPath 实现果冻效果,github,自己就按着上面的demo修改了一下( 之前也是想着自己一行一行动手敲代码,小伙伴总是说我不要重复造轮子),也正好通过这个学习一下CADisplayLink. CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器.我们在应用中创建一个新的 CADisplayLink 对象,把它添加到一个runloop中,并给它

iOS中 仿Tumblr点赞心破碎动画 韩俊强的博客

上一篇:高仿Tumblr热度-滚动条数-JQScrollNumberLabel 最近Tumblr轻博客无论是web端还是移动端,都非常受欢迎,简单调研了一下,其中动画是我感兴趣的,特此写了个仿Tumblr点赞心破碎动画: 1.首先看下效果: 2.模仿Tumblr中的效果应用如下: 原理:使用按钮点击Action增加两个事件,通过改变背景hidden和frame,切换图片,增加动画效果等: setupUI及touch Action: - (void)setupUI { // 点击的btn UIBu

Android自定义带增长动画和点击弹窗提示效果的柱状图DEMO_Android

项目中最近用到各种图表,本来打算用第三方的,例如MPAndroid,这是一个十分强大的图表库,应用起来十分方便,但是最终发现和设计不太一样,没办法,只能自己写了.今天将写好的柱状图的demo贴在这,该柱状图可根据数据的功能有一下几点:      1. 根据数据的多少,动态的绘制柱状图柱子的条数:      2. 柱状图每条柱子的绘制都有动态的动画效果:      3. 每条柱子有点击事件,点击时弹出提示框,显示相关信息,规定时间后,弹窗自动消失.      好了,先上演示图:      下边贴出

jquery仿QQ商城带左右按钮控制焦点图片切换滚动效果

jquery图片特效制作仿腾讯QQ商城首页banner焦点图片轮播切换效果,带索引按钮控制和左右按钮控制图片切换,实例代码如下,感兴趣的朋友可以参考下哈   复制代码 代码如下: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>jquery图片

Android UI设计系列之自定义SwitchButton开关实现类似IOS中UISwitch的动画效果(2)_Android

做IOS开发的都知道,IOS提供了一个具有动态开关效果的UISwitch组件,这个组件很好用效果相对来说也很绚丽,当我们去点击开关的时候有动画效果,但遗憾的是Android上并没有给我们提供类似的组件(听说在Android4.0的版本上提供了具有动态效果的开关组件,不过我还没有去看文档),如果我们想实现类似的效果那该怎么办了呢?看来又得去自定义了. 公司的产品最近一直在做升级,主要做的就是把界面做的更绚丽更美观给用户更好的体验(唉,顾客是上帝......),其中的设置功能中就有开关按钮,原来的开

iOS利用CALayer实现动画加载的效果_IOS

首先来看看效果图 实现过程如下 控制器调用就一句代码: [self showLoadingInView:self.view]; 方便控制器如此调用,就要为控制器添加一个分类 .h文件 #import <UIKit/UIKit.h> #import "GQCircleLoadView.h" @interface UIViewController (GQCircleLoad) //显示动画 - (void)showLoadingInView:(UIView*)view; //隐