仿IOS效果 带弹簧动画的ListView

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

效果图

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

1、使用方法

compile 'com.a520wcf.yllistview:YLListView:1.0.1

2、使用介绍:
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" />

2)、代码:

 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();
   }
  });
 }

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

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;

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

 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助。

(0)

相关推荐

  • 利用iOS动画来模拟音量振动条的实现

    iOS动画来模拟音量振动条,即利用CAReplicatorLayer实现,以下将详细的介绍CAReplicatorLayer和实现方法. 音量振动条 效果图: 如何实现? 创建3个layer,按顺序播放y轴缩放动画 利用CAReplicatorLayer实现 1.什么是CAReplicatorLayer? 一种可以复制自己子层的layer,并且复制出来的layer和原生子层有同样的属性,位置,形变,动画. 2.CAReplicatorLayer属性 instanceCount: 子层总数(包括原

  • iOS基础动画教程分享

    iOS的动画多种多样,动画做的好的应用会更加吸引人,用起来也会更加炫目,本文介绍iOS几种基础动画,单个讲解便于理解,但真正使用时,结合起来用会看起来更加帅,这就看具体的应用场景和大家的想象力啦. 所有的基础动画都给予UIView一个基础的方法:animateWithDuration.这个方法可以包含一个代码块,里面设置要改变的东西,在执行的时候iOS会自动以动画的形式展现出来,代码如下: [UIView animateWithDuration:1 animations:^{ // 要执行的动作

  • IOS框架Spring常用的动画效果

    Spring 作用:开发中常用的动画效果及自定义转场动画 演示 介绍 SpringView 最重要的一个类,是一个继承自UIView的控件,所有的动画属性,都是围绕着这个类的对象 demo跑起来有一个code按钮,点击这个按钮会出现已经设置的动画属性及其api,自己可以自定义设置,其对应的api这里就不赘述了(上面的gif图片也可以看到) demo里code按钮点开显示的layer对象是用SpringView创建的对象,不是我们平时说的layer animateNext(completion:

  • iOS中UIActivityIndicatorView的用法及齿轮等待动画实例

    基础 @派生自UIView,所以它是视图,也可以附着在视图上. 一.创建 复制代码 代码如下: // 因为UIActivityIndicatorView的大小是固定的,可以直接设置它.center UIActivityIndicatorView* activityIndicatorView = [ [ UIActivityIndicatorView alloc ] initWithFrame:CGRectMake(250.0,20.0,30.0,30.0)]; 二. 属性设置风格 复制代码 代码

  • 详解iOS开发中的转场动画和组动画以及UIView封装动画

    一.转场动画 CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果.iOS比Mac OS X的转场动画效果少一点 UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果 属性解析: type:动画过渡类型 subtype:动画过渡方向 startProgress:动画起点(在整体动画的百分比) endProgress:动画终点(在整体动画的百分比) 转场动画代码示例 1.界面搭建 2.实现代码 复制代码

  • IOS开发代码分享之获取启动画面图片的string

    本代码支持 iPhone 6 以下. 支持 iPhone 及 iPad +(NSString*)getLaunchImageName {           NSArray* images= @[@"LaunchImage.png", @"LaunchImage@2x.png",@"LaunchImage-700@2x.png",@"LaunchImage-568h@2x.png",@"LaunchImage-700

  • IOS等待时动画效果的实现

    查询时间或长或短,为了提升用户体验,目前用的比较多的手段之一就是查询等待时添加一个动态等待效果.当我们在请求网络时加载页面时有个动作效果,效果图如下: 源代码可以网上找开源项目Coding.net,上面的效果原理为两张图片组合,外面那个则为动画转动,里面的图标则是透明度的变化:主要代码如下: 1:把它封装在EaseLoadingView里面 @interface EaseLoadingView : UIView @property (strong, nonatomic) UIImageView

  • IOS实战之自定义转场动画详解

    转场动画这事,说简单也简单,可以通过presentViewController:animated:completion:和dismissViewControllerAnimated:completion:这一组函数以模态视图的方式展现.隐藏视图.如果用到了navigationController,还可以调用pushViewController:animated:和popViewController这一组函数将新的视图控制器压栈.弹栈. 下图中所有转场动画都是自定义的动画,这些效果如果不用自定义动

  • IOS绘制动画颜色渐变折线条

    先给大家展示下效果图: 概述 现状 折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图.折线图可以更加直观的表示数据的变化.网络上有很多绘制折线图的demo,有的也使用了动画,但是线条颜色渐变的折线图的demo少之又少,甚至可以说没有.该Blog阐述了动画绘制线条颜色渐变的折线图的实现方案,以及折线图线条颜色渐变的实现原理,并附以完整的示例. 成果 本人已将折线图封装到了一个UIView子类中,并提供了相应的接口.该自定义折线图视图,基本上可以适用于大部分需要集成折线图的项目.若你

  • IOS CoreAnimation中layer动画闪烁的解决方法

    网上有一段Core Animation层动画的例子,是将view中的云朵从左向右移动,直到移出屏幕,这时再将云朵移到最左端然后重复移动动画. 所有动画在layer上完成,不过有个小问题,就是第一次每朵云动画完成时,会在其原位置处有一个闪烁,然后才会移动到屏幕最右端,而随后的运动动画都没有这个问题了: 由于录制gif文件时使用的帧率比较低,所以上图较难展示这个问题.不过实际App运行的时候每朵云在第一次移出屏幕的时候都会在初始位置闪烁一下再运行随后的动画.这是为什么呢? 因为layer动画不同于v

随机推荐