热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

仿IOS效果带弹簧动画的ListView

这篇文章主要介绍了仿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;

 

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<&#63;> 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));

 }
}

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


推荐阅读
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • 深入理解 Oracle 存储函数:计算员工年收入
    本文介绍如何使用 Oracle 存储函数查询特定员工的年收入。我们将详细解释存储函数的创建过程,并提供完整的代码示例。 ... [详细]
  • 本文总结了2018年的关键成就,包括职业变动、购车、考取驾照等重要事件,并分享了读书、工作、家庭和朋友方面的感悟。同时,展望2019年,制定了健康、软实力提升和技术学习的具体目标。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • CSS 布局:液态三栏混合宽度布局
    本文介绍了如何使用 CSS 实现液态的三栏布局,其中各栏具有不同的宽度设置。通过调整容器和内容区域的属性,可以实现灵活且响应式的网页设计。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 本文介绍了如何使用jQuery根据元素的类型(如复选框)和标签名(如段落)来获取DOM对象。这有助于更高效地操作网页中的特定元素。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 本文介绍了在Windows环境下使用pydoc工具的方法,并详细解释了如何通过命令行和浏览器查看Python内置函数的文档。此外,还提供了关于raw_input和open函数的具体用法和功能说明。 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 解决微信电脑版无法刷朋友圈问题:使用安卓远程投屏方案
    在工作期间想要浏览微信和朋友圈却不太方便?虽然微信电脑版目前不支持直接刷朋友圈,但通过远程投屏技术,可以轻松实现在电脑上操作安卓设备的功能。 ... [详细]
  • SQLite 动态创建多个表的需求在网络上有不少讨论,但很少有详细的解决方案。本文将介绍如何在 Qt 环境中使用 QString 类轻松实现 SQLite 表的动态创建,并提供详细的步骤和示例代码。 ... [详细]
author-avatar
宝贝娜娜121_562
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有