Android魔术(第五弹)—— 一步步实现滑动折叠列表
目录
1、效果展示
2、效果分析
3、Item布局
3、实现Adapter
4、监听滑动
5、回弹效果
6、总结一下
源码:
1、效果展示
2、效果分析
3、Item布局
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><RelativeLayoutandroid:id="@+id/item_content"android:layout_width="match_parent"android:layout_height="@dimen/scroll_fold_item_height"><ImageViewandroid:id="@+id/item_img"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"/><ImageViewandroid:id="@+id/item_img_shade"android:layout_width="match_parent"android:layout_height="match_parent"android:src="#000000"/><LinearLayoutandroid:id="@+id/scale_item_content"android:layout_width="200dp"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:orientation="vertical">...</LinearLayout>...</RelativeLayout>
</FrameLayout>
最外层用FrameLayout,这样当FrameLayout高度变小时,item_content可以超出FrameLayout的范围,产生折叠的效果。
item_content的高度是固定不变的,真正改变的是外层的FrameLayout。
scale_item_content中是那些大小可变的文字内容
布局比较简单,后面会讲到如何使用这些layout达到效果。
另外还有一个footer的布局,因为很简单就不贴出代码了。
3、实现Adapter
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if(viewType == 0) {View item = LayoutInflater.from(mContext).inflate(R.layout.scroll_fold_list_item, null);return new ItemViewHolder(item);}else{View bottom = LayoutInflater.from(mContext).inflate(R.layout.scroll_fold_list_footer, null);return new BottomViewHolder(bottom);}
}@Override
public void onBindViewHolder(ViewHolder holder, int position) {holder.initData(position);
}
这里使用viewType来区分普通的item和footer(通过getItemViewType方法)。BottomViewHolder和ItemViewHolder继承同一个类,代码如下:
abstract class ViewHolder extends RecyclerView.ViewHolder{View item;public ViewHolder(View itemView) {super(itemView);item = itemView;}abstract void initData(int position);
}class BottomViewHolder extends ViewHolder{public BottomViewHolder(View itemView) {super(itemView);}@Overridevoid initData(int position) {ViewGroup.LayoutParams bottomParams = itemView.getLayoutParams();if(bottomParams == null){bottomParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);}bottomParams.height = recyclerView.getHeight() - itemHeight + 10;itemView.setLayoutParams(bottomParams);}
}class ItemViewHolder extends ViewHolder{View content;ImageView image;TextView name;public ItemViewHolder(View itemView) {super(itemView);item = itemView;content = itemView.findViewById(R.id.item_content);image = (ImageView)itemView.findViewById(R.id.item_img);name = (TextView) itemView.findViewById(R.id.item_name);}void initData(int position){image.setImageResource(IMGS[position]);name.setText(NAMES[position]);ViewGroup.LayoutParams params = item.getLayoutParams();if(params == null){params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);}params.height = itemSmallHeight;content.findViewById(R.id.item_img_shade).setAlpha(ITEM_SHADE_DARK_ALPHA);item.setLayoutParams(params);}
}
我们先看BottomViewHolder,动态的设置footer的高度为列表高度减去itemHeight,再加上10像素。这个itemHeight是展开后item的高度,即置顶的item的高度。这里之所以再加上10像素,是因为如果设置高度正好是余下的高度,当快速滑动到底部的时候有几率会出现问题,所以这里让高度略大于实际展示的高度。
然后来看ItemViewHolder,也是动态的设置高度为ItemSmallHeight,这个高度是收缩后item的高度,而且将遮罩设置为最暗。注意这里全部初始化为收缩状态,没有单独设置一个置顶展开的状态,这个我们后面会解释为什么。
4、监听滑动
list.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {changeItemState();}@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {...}
});可以看到在滑动过程(onScrolled)中调用changeItemState()这个函数,代码如下:
private void changeItemState(){int firstVisibleIndex = linearLayoutManager.findFirstVisibleItemPosition();ViewGroup first = (ViewGroup) linearLayoutManager.findViewByPosition(firstVisibleIndex);int firstVisibleOffset = -first.getTop();int changeheight = (int) (firstVisibleOffset * (ScrollFoldAdapter.ITEM_CONTENT_TEXT_SCALE - 1));// 减少当前展示的第一个item的高度。if (first == null) {return;}changeItemHeight(first, itemHeight - changeheight);changeItemState(first, ScrollFoldAdapter.ITEM_CONTENT_TEXT_SCALE, ScrollFoldAdapter.ITEM_SHADE_LIGHT_ALPHA);// 增大当前展示的第二个item的高度,改变内容大小,改变透明度if (firstVisibleIndex + 1 < adapter.getItemCount() - 1) {ViewGroup second = (ViewGroup) linearLayoutManager.findViewByPosition(firstVisibleIndex + 1);changeItemHeight(second, itemSmallHeight + changeheight);float scale = (float) firstVisibleOffset / itemSmallHeight* (ScrollFoldAdapter.ITEM_CONTENT_TEXT_SCALE - 1) + 1.0f;float alpha = (ScrollFoldAdapter.ITEM_SHADE_DARK_ALPHA - ScrollFoldAdapter.ITEM_SHADE_LIGHT_ALPHA)* (1 - (float) firstVisibleOffset / itemSmallHeight)+ ScrollFoldAdapter.ITEM_SHADE_LIGHT_ALPHA;changeItemState(second, scale, alpha);}/*** 由于快速滑动,导致计算及状态有误 所以下面就是消除这种误差,校准状态。具体如下* 将第一个item上面(存在的)的和第二个Item下面的都变为收缩的高度,内容缩放到最小,透明度为0。65*/for (int i = 0; i <= linearLayoutManager.findLastVisibleItemPosition(); i++) {if (i < adapter.getItemCount() - 1 && i != firstVisibleIndex && i != firstVisibleIndex + 1) {ViewGroup item = (ViewGroup) linearLayoutManager.findViewByPosition(i);if(item == null){continue;}changeItemHeight(item, itemSmallHeight);float scale = 1;float alpha = ScrollFoldAdapter.ITEM_SHADE_DARK_ALPHA;changeItemState(item, scale, alpha);}}
}
整体思路如下:
获取当前置顶展示的item,计算该item相对于列表顶端的偏移。这个偏移是关键参数,通过这个偏移计算出第一个item收缩的高度和第二个item展开的高度,并且计算第二个item遮罩的透明度和文字内容的大小。
这里调用了另外两个函数changeItemHeight(view, int)和changeItemState(view, float, float)。其中changeItemHeight(view, int)用来改变item的高度实现展开或折叠;而changeItemState(view, float, float)用来改变遮罩透明度和文字内容大小。两个函数代码如下:
/*** 改变一个item的高度。** @param item* @param height*/
private void changeItemHeight(View item, int height) {ViewGroup.LayoutParams itemParams = item.getLayoutParams();itemParams.height = height;item.setLayoutParams(itemParams);
}/*** 改变一个item的状态,包括透明度,大小等* @param item* @param scale* @param alpha*/
private void changeItemState(ViewGroup item, float scale, float alpha) {if (item.getChildCount() > 0) {View changeView = item.findViewById(R.id.scale_item_content);changeView.setScaleX(scale);changeView.setScaleY(scale);View shade = item.findViewById(R.id.item_img_shade);shade.setAlpha(alpha);}
}
改变高度很简单,没必要解释了。改变遮罩透明度就是改变其alpha,而文字内容大小的改变则是利用setScaleX和setScaleY两个函数,实际上是将scale_item_content这个layout整个进行缩放,其内容就会随着变大/小。
回到changeItemState()函数,改变了第一个和第二个item后,可以看到又将其他的item置为收缩状态。这是因为快速滑动会造成某些item处于中间的状态,做这一步操作就是校正快速滑动导致的一些问题。
上面我们提到过,所有的item都初始化成收缩状态了。其实当RecyclerView添加到屏幕上时,是一定会产生滑动的。所以我们进入页面的时候,我们什么都没有操作,滑动监听的函数却被调用了。这样通过changeItemState()函数就可以将置顶的item变为展开状态,所以初始的展示状态是正确的。
5、回弹效果
list.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {changeItemState();}@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {if (newState == RecyclerView.SCROLL_STATE_IDLE) {int firstVisibleIndex = linearLayoutManager.findFirstVisibleItemPosition();View first = linearLayoutManager.findViewByPosition(firstVisibleIndex);int firstVisibleOffset = -first.getTop();if (firstVisibleOffset == 0) {return;}if (firstVisibleOffset < itemSmallHeight / 2) {list.scrollBy(0, -firstVisibleOffset);} else {list.scrollBy(0, itemSmallHeight - firstVisibleOffset);}changeItemState();}}
});
上面是完整的滑动监听的代码。在onScrollStateChanged中,判断状态是否是滑动结束(SCROLL_STATE_IDLE)。如果滑动结束,判断顶部显示的item的偏移,根据偏移的大小选择回弹方向。如果偏移很小(第一个item大部分内容显示出来了),则下滚至第一个item置顶的状态;否则上滚至第二个item置顶的状态。
这样保证了静止状态下一定有一个item完全置顶高亮显示。
最后又调用了changeItemState函数,主要目的是校正一些误差。
6、总结一下
源码:
Android魔术(第五弹)—— 一步步实现滑动折叠列表相关推荐
- Android UI(五)云通讯录项目之联系人列表,带侧滑选择,带搜索框
作者:泥沙砖瓦浆木匠 网站:http://blog.csdn.net/jeffli1993 个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节. 交流QQ群:[编程之美 36523458 ...
- Android 开发第七弹:简易时钟(秒表)
本文承接,Android 开发第五弹:简易时钟(闹钟) 和 Android 开发第六弹:简易时钟(计时器),这一部分是关于秒表的. 布局 同样是新建一个类(StopWatchView)并扩展自Line ...
- Android基础知识~入门进阶,一步步走到高手
Android基础知识~入门进阶,一步步走到高手 2011年09月01日 [b]希望新入手ANDROID设备的朋友认真阅读本帖,一些简单的问题就可以自己解决了!!! 一:基础知识[/b] [b]1.什 ...
- Android判断软键盘弹出并隐藏的简单完美解决方案
Android判断软键盘弹出并隐藏的简单完美解决方案 参考文章: (1)Android判断软键盘弹出并隐藏的简单完美解决方案 (2)https://www.cnblogs.com/thare1307/ ...
- Android 颜色渲染(五) LinearGradient线性渲染
版权声明:本文为博主原创文章,未经博主允许不得转载. Android 颜色处理(五) LinearGradient线性渲染 相信很多人都看过歌词同步的效果, 一是竖直方向的滚动,另一方面是水平方面的歌 ...
- android系统动态切换输入法,一种动态切换Android系统输入法的弹出模式的方法与流程...
技术特征: 1.一种动态切换Android系统输入法的弹出模式的方法,其特征在于,包括步骤如下: (1)初始状态下,即软键盘和表情面板都未展开时,为表情面板设置一个默认高度,默认高度的取值范围是230 ...
- 高压五防计算机软件安装,基于Android的新一代五防钥匙软件系统
摘要: 由于智能电网的发展,对于变电站运行方式在效率和功能方面有了更高的要求.借助计算机软硬件技术的发展,我们研制了基于android的新一代五防钥匙系统.新的系统主要为了解决原有系统中存在的无法在五 ...
- Android RecyclerView(五)封装Holder与Adapter(Android 5.0 新特性)
Android RecyclerView(五)封装Holder与Adapter(Android 5.0 新特性) 1 效果 2 BaseHolder的封装 public class BaseViewH ...
- android电池(五):电池 充电IC(PM2301)驱动分析篇
android电池(五):电池 充电IC(PM2301)驱动分析篇 关键词:android 电池 电量计 PL2301任务初始化宏 power_supply 中断线程化 平台信息: 内核:linu ...
最新文章
- mysql-4.0.20 use on scounix 5.0.7 error :dynamic linker:..:could not open libgthreads.so help!!!!
- python中文文本分析_python--文本分析
- 限时抢订!价值4800元TechNet Plus赠阅一年!今天己到哈~~~
- 记一次因坏块引起的dataguard恢复
- linux shell 计算器 除0,用shell写一个简易计算器,可以实现加、减、乘、除运算,假如脚本名字为1.sh,执行示例:./1....
- Horizon View 6-虚拟桌面模板制作⑶
- Boost::context模块callcc的throw测试程序
- list java removeif_java – removeIf()方法.从List中删除所有元素
- mysql的一些函数
- 基于以太坊发布属于自己的数字货币(代币)完整版
- python结巴_python结巴(jieba)分词
- Hystrix 简介和使用
- Bootstrap自适应居中问题
- ionic 日期选择控件
- 中文表示什么_中文分词是个伪问题
- android流量显示插件,安卓状态栏显示网速(安卓网速显示插件)
- pandas读取xls文件
- quoted string not properly terminated错误提示
- 祝所有的考生考试顺利!!!
- 大学英语综合教程三 Unit 5 课文内容英译中 中英翻译